sort marks by rating

This commit is contained in:
Your Name 2023-12-30 22:20:15 -05:00 committed by Henri Dickson
parent 1e3e409a7e
commit c7445f8f61
15 changed files with 170 additions and 168 deletions

View file

@ -141,7 +141,7 @@ def search(request):
{ {
"items": items, "items": items,
"dup_items": dup_items, "dup_items": dup_items,
"pagination": PageLinksGenerator(PAGE_LINK_NUMBER, p, num_pages), "pagination": PageLinksGenerator(p, num_pages, request.GET),
"sites": SiteName.labels, "sites": SiteName.labels,
"hide_category": hide_category, "hide_category": hide_category,
}, },

View file

@ -157,7 +157,7 @@ def mark_list(request, item_path, item_uuid, following_only=False):
paginator = Paginator(queryset, NUM_REVIEWS_ON_LIST_PAGE) paginator = Paginator(queryset, NUM_REVIEWS_ON_LIST_PAGE)
page_number = request.GET.get("page", default=1) page_number = request.GET.get("page", default=1)
marks = paginator.get_page(page_number) marks = paginator.get_page(page_number)
pagination = PageLinksGenerator(PAGE_LINK_NUMBER, page_number, paginator.num_pages) pagination = PageLinksGenerator(page_number, paginator.num_pages, request.GET)
return render( return render(
request, request,
"item_mark_list.html", "item_mark_list.html",
@ -179,7 +179,7 @@ def review_list(request, item_path, item_uuid):
paginator = Paginator(queryset, NUM_REVIEWS_ON_LIST_PAGE) paginator = Paginator(queryset, NUM_REVIEWS_ON_LIST_PAGE)
page_number = request.GET.get("page", default=1) page_number = request.GET.get("page", default=1)
reviews = paginator.get_page(page_number) reviews = paginator.get_page(page_number)
pagination = PageLinksGenerator(PAGE_LINK_NUMBER, page_number, paginator.num_pages) pagination = PageLinksGenerator(page_number, paginator.num_pages, request.GET)
return render( return render(
request, request,
"item_review_list.html", "item_review_list.html",

View file

@ -184,3 +184,26 @@ form img {
margin: 0 0.5em; margin: 0 0.5em;
} }
} }
.pagination {
text-align: center;
width: 100%;
margin-top: 1em;
a {
margin: 0 0.3em;
}
.s, .prev, .next {
font-size: 1.4em;
}
.prev {
margin-right: 0.5em;
}
.next {
margin-left: 0.5em;
}
.current {
font-weight: bold;
font-size: 1.1em;
color: var(--pico-secondary);
}
}

View file

@ -954,57 +954,10 @@ $panel-padding : 0
padding: 0 padding: 0
list-style: none list-style: none
.pagination
// position: absolute
// bottom: 30px
// left: 50%
// transform: translateX(-50%)
text-align: center
width: 100%
& &__page-link
font-weight: normal
margin: 0 5px
&--current
font-weight: bold
font-size: 1.2em
// text-decoration: underline
color: $color-secondary
& &__nav-link
font-size: 1.4em
margin: 0 2px
$nav-link-edge-margin-width: 18px
&--right-margin
margin-right: $nav-link-edge-margin-width
&--left-margin
margin-left: $nav-link-edge-margin-width
&--hidden
display: none
// Small devices (landscape phones, 576px and up) // Small devices (landscape phones, 576px and up)
@media (max-width: $small-devices) @media (max-width: $small-devices)
.pagination pass
& &__page-link
margin: 0 3px
& &__nav-link
font-size: 1.4em
margin: 0 2px
$nav-link-edge-margin-width: 10px
&--right-margin
margin-right: $nav-link-edge-margin-width
&--left-margin
margin-left: $nav-link-edge-margin-width
// Medium devices (tablets, 768px and up) // Medium devices (tablets, 768px and up)
@media (max-width: $medium-devices) @media (max-width: $medium-devices)
pass pass

View file

@ -1,23 +1,20 @@
<div class="pagination"> <div class="pagination">
{% if pagination.has_prev %} {% if pagination.has_prev %}
<a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page=1" <a href="?{{ pagination.query_string }}page=1" class="s">&laquo;</a>
class="pagination__nav-link pagination__nav-link">&laquo;</a> <a href="?{{ pagination.query_string }}page={{ pagination.previous_page }}"
<a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ pagination.previous_page }}" class="prev">&lsaquo;</a>
class="pagination__nav-link pagination__nav-link--right-margin pagination__nav-link">&lsaquo;</a>
{% endif %} {% endif %}
{% for page in pagination.page_range %} {% for page in pagination.page_range %}
{% if page == pagination.current_page %} {% if page == pagination.current_page %}
<a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ page }}" <a href="?{{ pagination.query_string }}page={{ page }}" class="current">{{ page }}</a>
class="pagination__page-link pagination__page-link--current">{{ page }}</a>
{% else %} {% else %}
<a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ page }}" <a href="?{{ pagination.query_string }}page={{ page }}">{{ page }}</a>
class="pagination__page-link">{{ page }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if pagination.has_next %} {% if pagination.has_next %}
<a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ pagination.next_page }}" <a href="?{{ pagination.query_string }}page={{ pagination.next_page }}"
class="pagination__nav-link pagination__nav-link--left-margin">&rsaquo;</a> class="next">&rsaquo;</a>
<a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ pagination.last_page }}" <a href="?{{ pagination.query_string }}page={{ pagination.last_page }}"
class="pagination__nav-link">&raquo;</a> class="s">&raquo;</a>
{% endif %} {% endif %}
</div> </div>

View file

@ -1,11 +1,15 @@
import functools import functools
import re
import uuid import uuid
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from django.http import Http404, HttpRequest, HttpResponseRedirect from django.db.models import query
from django.http import Http404, HttpRequest, HttpResponseRedirect, QueryDict
from django.utils import timezone from django.utils import timezone
from django.utils.baseconv import base62 from django.utils.baseconv import base62
from .config import PAGE_LINK_NUMBER
if TYPE_CHECKING: if TYPE_CHECKING:
from users.models import APIdentity, User from users.models import APIdentity, User
@ -82,8 +86,19 @@ class PageLinksGenerator:
length -- the number of page links in pagination length -- the number of page links in pagination
""" """
def __init__(self, length: int, current_page: int, total_pages: int): def __init__(
self, current_page: int, total_pages: int, query: QueryDict | None = None
):
length = PAGE_LINK_NUMBER
current_page = int(current_page) current_page = int(current_page)
self.query_string = ""
if query:
q = query.copy()
if q.get("page"):
q.pop("page")
self.query_string = q.urlencode()
if self.query_string:
self.query_string += "&"
self.current_page = current_page self.current_page = current_page
self.previous_page = current_page - 1 if current_page > 1 else None self.previous_page = current_page - 1 if current_page > 1 else None
self.next_page = current_page + 1 if current_page < total_pages else None self.next_page = current_page + 1 if current_page < total_pages else None

View file

@ -1,11 +1,12 @@
from datetime import datetime from datetime import datetime
from typing import Any
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import connection, models from django.db import models
from django.db.models import Avg, Count, Q from django.db.models import Avg, Count, Q
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from catalog.models import Item, ItemCategory from catalog.models import Item
from users.models import APIdentity from users.models import APIdentity
from .common import Content from .common import Content
@ -116,24 +117,22 @@ class Rating(Content):
@staticmethod @staticmethod
def update_item_rating( def update_item_rating(
item: Item, owner: APIdentity, rating_grade: int | None, visibility: int = 0 item: Item,
owner: APIdentity,
rating_grade: int | None,
visibility: int = 0,
created_time: datetime | None = None,
): ):
if rating_grade and (rating_grade < 1 or rating_grade > 10): if rating_grade and (rating_grade < 1 or rating_grade > 10):
raise ValueError(f"Invalid rating grade: {rating_grade}") raise ValueError(f"Invalid rating grade: {rating_grade}")
rating = Rating.objects.filter(owner=owner, item=item).first()
if not rating_grade: if not rating_grade:
if rating: Rating.objects.filter(owner=owner, item=item).delete()
rating.delete() else:
rating = None d: dict[str, Any] = {"grade": rating_grade, "visibility": visibility}
elif rating is None: if created_time:
rating = Rating.objects.create( d["created_time"] = created_time
owner=owner, item=item, grade=rating_grade, visibility=visibility r, _ = Rating.objects.update_or_create(owner=owner, item=item, defaults=d)
) return r
elif rating.grade != rating_grade or rating.visibility != visibility:
rating.visibility = visibility
rating.grade = rating_grade
rating.save()
return rating
@staticmethod @staticmethod
def get_item_rating(item: Item, owner: APIdentity) -> int | None: def get_item_rating(item: Item, owner: APIdentity) -> int | None:

View file

@ -259,6 +259,15 @@ class ShelfManager:
else: else:
return qs return qs
def get_members(
self, shelf_type: ShelfType, item_category: ItemCategory | None = None
):
qs = self.shelf_list[shelf_type].members.all()
if item_category:
return qs.filter(q_item_in_category(item_category))
else:
return qs
# def get_items_on_shelf(self, item_category, shelf_type): # def get_items_on_shelf(self, item_category, shelf_type):
# shelf = ( # shelf = (
# self.owner.shelf_set.all() # self.owner.shelf_set.all()

View file

@ -5,52 +5,65 @@
<section class="filter"> <section class="filter">
<script> <script>
function filter() { function filter() {
location = '{{ user.identity.url }}' + $('#shelf')[0].value + '/' + $('#category')[0].value + '/' + ( $('#year')[0].value ? $('#year')[0].value + '/' : '' ); var q = [];
if ($('#year')[0].value) {
q.push('year=' + $('#year')[0].value)
}
if ($('#sort')[0].value) {
q.push('sort=' + $('#sort')[0].value)
}
location = '{{ user.identity.url }}' + $('#shelf')[0].value + '/' + $('#category')[0].value + '/' + (q.length ? '?' + q.join('&') : '')
} }
</script> </script>
<form role="search" method="get" action=""> <form method="get" action="">
<select name="year" id="year" onchange="filter()"> <fieldset role="search" style="width: 100%; padding;">
<option value="">全部</option> <select name="shelf" id="shelf" onchange="filter()">
{% for y in years %} {% for typ, label in shelf_labels %}
<option value="{{ y }}" {% if y == year %}selected{% endif %}>{{ y }}</option> {% if label %}
{% endfor %} <option {% if typ in request.path %}selected{% endif %} value="{{ typ }}">{{ label }}</option>
</select> {% endif %}
<select name="shelf" id="shelf" onchange="filter()"> {% endfor %}
{% for typ, label in shelf_labels %} <option {% if '/reviews/' in request.path %}selected{% endif %}
{% if label %} value="reviews">评论</option>
<option {% if typ in request.path %}selected{% endif %} value="{{ typ }}">{{ label }}</option> </select>
<select name="category" id="category" onchange="filter()">
{% visible_categories as cats %}
{% if 'book' in cats %}
<option {% if '/book/' in request.path %}selected{% endif %} value="book">书籍</option>
{% endif %} {% endif %}
{% endfor %} {% if 'movie' in cats %}
<option {% if '/reviews/' in request.path %}selected{% endif %} <option {% if '/movie/' in request.path %}selected{% endif %} value="movie">电影</option>
value="reviews">评论</option> {% endif %}
</select> {% if 'tv' in cats %}
<select name="category" id="category" onchange="filter()"> <option {% if '/tv/' in request.path %}selected{% endif %} value="tv">剧集</option>
{% visible_categories as cats %} {% endif %}
{% if 'book' in cats %} {% if 'podcast' in cats %}
<option {% if '/book/' in request.path %}selected{% endif %} value="book">书籍</option> <option {% if '/podcast/' in request.path %}selected{% endif %}
{% endif %} value="podcast">播客</option>
{% if 'movie' in cats %} {% endif %}
<option {% if '/movie/' in request.path %}selected{% endif %} value="movie">电影</option> {% if 'music' in cats %}
{% endif %} <option {% if '/music/' in request.path %}selected{% endif %} value="music">音乐</option>
{% if 'tv' in cats %} {% endif %}
<option {% if '/tv/' in request.path %}selected{% endif %} value="tv">剧集</option> {% if 'game' in cats %}
{% endif %} <option {% if '/game/' in request.path %}selected{% endif %} value="game">游戏</option>
{% if 'podcast' in cats %} {% endif %}
<option {% if '/podcast/' in request.path %}selected{% endif %} {% if 'performance' in cats %}
value="podcast">播客</option> <option {% if '/performance/' in request.path %}selected{% endif %}
{% endif %} value="performance">演出</option>
{% if 'music' in cats %} {% endif %}
<option {% if '/music/' in request.path %}selected{% endif %} value="music">音乐</option> </select>
{% endif %} <select name="year" id="year" onchange="filter()">
{% if 'game' in cats %} <option value="">全部</option>
<option {% if '/game/' in request.path %}selected{% endif %} value="game">游戏</option> {% for y in years %}
{% endif %} <option value="{{ y }}" {% if y == year %}selected{% endif %}>{{ y }}</option>
{% if 'performance' in cats %} {% endfor %}
<option {% if '/performance/' in request.path %}selected{% endif %} </select>
value="performance">演出</option> <select name="sort" id="sort" onchange="filter()">
{% endif %} <option value="" disabled>排列顺序</option>
</select> <option value="">时间</option>
<option value="rating" {% if 'rating' == sort %}selected{% endif %}>评分</option>
</select>
</fieldset>
</form> </form>
<!-- <!--
<style type="text/css"> <style type="text/css">

View file

@ -101,15 +101,6 @@ urlpatterns = [
user_mark_list, user_mark_list,
name="user_mark_list", name="user_mark_list",
), ),
re_path(
r"^users/(?P<user_name>[~A-Za-z0-9_\-.@]+)/(?P<shelf_type>"
+ _get_all_shelf_types()
+ ")/(?P<item_category>"
+ _get_all_categories()
+ r")/(?P<year>\d+)/$",
user_mark_list,
name="user_mark_list_year",
),
re_path( re_path(
r"^users/(?P<user_name>[~A-Za-z0-9_\-.@]+)/reviews/(?P<item_category>" r"^users/(?P<user_name>[~A-Za-z0-9_\-.@]+)/reviews/(?P<item_category>"
+ _get_all_categories() + _get_all_categories()
@ -117,13 +108,6 @@ urlpatterns = [
user_review_list, user_review_list,
name="user_review_list", name="user_review_list",
), ),
re_path(
r"^users/(?P<user_name>[~A-Za-z0-9_\-.@]+)/reviews/(?P<item_category>"
+ _get_all_categories()
+ r")/(?P<year>\d+)/$",
user_review_list,
name="user_review_list_year",
),
re_path( re_path(
r"^users/(?P<user_name>[~A-Za-z0-9_\-.@]+)/tags/(?P<tag_title>.+)/$", r"^users/(?P<user_name>[~A-Za-z0-9_\-.@]+)/tags/(?P<tag_title>.+)/$",
user_tag_member_list, user_tag_member_list,

View file

@ -3,7 +3,7 @@ import datetime
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.exceptions import BadRequest, ObjectDoesNotExist, PermissionDenied from django.core.exceptions import BadRequest, ObjectDoesNotExist, PermissionDenied
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.db.models import Min from django.db.models import F, Min, OuterRef, Subquery
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -57,12 +57,15 @@ def render_list(
item_category=None, item_category=None,
tag_title=None, tag_title=None,
year=None, year=None,
sort="time",
): ):
target = request.target_identity target = request.target_identity
viewer = request.user.identity viewer = request.user.identity
tag = None tag = None
sort = request.GET.get("sort")
year = request.GET.get("year")
if type == "mark" and shelf_type: if type == "mark" and shelf_type:
queryset = target.shelf_manager.get_latest_members(shelf_type, item_category) queryset = target.shelf_manager.get_members(shelf_type, item_category)
elif type == "tagmember": elif type == "tagmember":
tag = Tag.objects.filter(owner=target, title=tag_title).first() tag = Tag.objects.filter(owner=target, title=tag_title).first()
if not tag: if not tag:
@ -74,6 +77,15 @@ def render_list(
queryset = Review.objects.filter(q_item_in_category(item_category)) queryset = Review.objects.filter(q_item_in_category(item_category))
else: else:
raise BadRequest() raise BadRequest()
if sort == "rating":
rating = Rating.objects.filter(
owner_id=OuterRef("owner_id"), item_id=OuterRef("item_id")
)
queryset = queryset.alias(
rating_grade=Subquery(rating.values("grade"))
).order_by(F("rating_grade").desc(nulls_last=True))
else:
queryset = queryset.order_by("-created_time")
start_date = queryset.aggregate(Min("created_time"))["created_time__min"] start_date = queryset.aggregate(Min("created_time"))["created_time__min"]
if start_date: if start_date:
start_year = start_date.year start_year = start_date.year
@ -81,16 +93,14 @@ def render_list(
years = reversed(range(start_year, current_year + 1)) years = reversed(range(start_year, current_year + 1))
else: else:
years = [] years = []
queryset = queryset.filter( queryset = queryset.filter(q_owned_piece_visible_to_user(request.user, target))
q_owned_piece_visible_to_user(request.user, target)
).order_by("-created_time")
if year: if year:
year = int(year) year = int(year)
queryset = queryset.filter(created_time__year=year) queryset = queryset.filter(created_time__year=year)
paginator = Paginator(queryset, PAGE_SIZE) paginator = Paginator(queryset, PAGE_SIZE)
page_number = int(request.GET.get("page", default=1)) page_number = int(request.GET.get("page", default=1))
members = paginator.get_page(page_number) members = paginator.get_page(page_number)
pagination = PageLinksGenerator(PAGE_SIZE, page_number, paginator.num_pages) pagination = PageLinksGenerator(page_number, paginator.num_pages, request.GET)
shelf_labels = get_shelf_labels_for_category(item_category) if item_category else [] shelf_labels = get_shelf_labels_for_category(item_category) if item_category else []
return render( return render(
request, request,
@ -103,6 +113,7 @@ def render_list(
"pagination": pagination, "pagination": pagination,
"years": years, "years": years,
"year": year, "year": year,
"sort": sort,
"shelf": shelf_type, "shelf": shelf_type,
"shelf_labels": shelf_labels, "shelf_labels": shelf_labels,
"category": item_category, "category": item_category,

View file

@ -11,7 +11,7 @@ from django.utils.dateparse import parse_datetime
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from catalog.models import * from catalog.models import *
from common.utils import AuthedHttpRequest, PageLinksGenerator, get_uuid_or_404 from common.utils import AuthedHttpRequest, get_uuid_or_404
from mastodon.api import boost_toot_later, share_comment from mastodon.api import boost_toot_later, share_comment
from takahe.utils import Takahe from takahe.utils import Takahe
@ -184,14 +184,7 @@ def comment(request: AuthedHttpRequest, item_uuid):
raise BadRequest() raise BadRequest()
def user_mark_list( def user_mark_list(request: AuthedHttpRequest, user_name, shelf_type, item_category):
request: AuthedHttpRequest, user_name, shelf_type, item_category, year=None
):
return render_list( return render_list(
request, request, user_name, "mark", shelf_type=shelf_type, item_category=item_category
user_name,
"mark",
shelf_type=shelf_type,
item_category=item_category,
year=year,
) )

View file

@ -5,12 +5,7 @@ from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from common.utils import ( from common.utils import AuthedHttpRequest, get_uuid_or_404, target_identity_required
AuthedHttpRequest,
PageLinksGenerator,
get_uuid_or_404,
target_identity_required,
)
from mastodon.api import boost_toot_later from mastodon.api import boost_toot_later
from takahe.utils import Takahe from takahe.utils import Takahe

View file

@ -13,7 +13,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from catalog.models import * from catalog.models import *
from common.utils import AuthedHttpRequest, PageLinksGenerator, get_uuid_or_404 from common.utils import AuthedHttpRequest, get_uuid_or_404
from journal.models.renderers import convert_leading_space_in_md, render_md from journal.models.renderers import convert_leading_space_in_md, render_md
from users.models.apidentity import APIdentity from users.models.apidentity import APIdentity
@ -97,10 +97,8 @@ def review_edit(request: AuthedHttpRequest, item_uuid, review_uuid=None):
raise BadRequest() raise BadRequest()
def user_review_list(request, user_name, item_category, year=None): def user_review_list(request, user_name, item_category):
return render_list( return render_list(request, user_name, "review", item_category=item_category)
request, user_name, "review", item_category=item_category, year=None
)
MAX_ITEM_PER_TYPE = 10 MAX_ITEM_PER_TYPE = 10

View file

@ -1,2 +1,14 @@
User-agent: CCBot
Disallow: /review/
User-agent: GPTBot User-agent: GPTBot
Disallow: /review/ Disallow: /review/
User-agent: Google-Extended
Disallow: /review/
User-agent: FacebookBot
Disallow: /review/
User-agent: Omgilibot
Disallow: /review/