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,
"dup_items": dup_items,
"pagination": PageLinksGenerator(PAGE_LINK_NUMBER, p, num_pages),
"pagination": PageLinksGenerator(p, num_pages, request.GET),
"sites": SiteName.labels,
"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)
page_number = request.GET.get("page", default=1)
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(
request,
"item_mark_list.html",
@ -179,7 +179,7 @@ def review_list(request, item_path, item_uuid):
paginator = Paginator(queryset, NUM_REVIEWS_ON_LIST_PAGE)
page_number = request.GET.get("page", default=1)
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(
request,
"item_review_list.html",

View file

@ -184,3 +184,26 @@ form img {
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
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)
@media (max-width: $small-devices)
.pagination
& &__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
pass
// Medium devices (tablets, 768px and up)
@media (max-width: $medium-devices)
pass

View file

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

View file

@ -1,11 +1,15 @@
import functools
import re
import uuid
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.baseconv import base62
from .config import PAGE_LINK_NUMBER
if TYPE_CHECKING:
from users.models import APIdentity, User
@ -82,8 +86,19 @@ class PageLinksGenerator:
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)
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.previous_page = current_page - 1 if current_page > 1 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 typing import Any
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
from django.db import connection, models
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import Avg, Count, Q
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 .common import Content
@ -116,24 +117,22 @@ class Rating(Content):
@staticmethod
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):
raise ValueError(f"Invalid rating grade: {rating_grade}")
rating = Rating.objects.filter(owner=owner, item=item).first()
if not rating_grade:
if rating:
rating.delete()
rating = None
elif rating is None:
rating = Rating.objects.create(
owner=owner, item=item, grade=rating_grade, visibility=visibility
)
elif rating.grade != rating_grade or rating.visibility != visibility:
rating.visibility = visibility
rating.grade = rating_grade
rating.save()
return rating
Rating.objects.filter(owner=owner, item=item).delete()
else:
d: dict[str, Any] = {"grade": rating_grade, "visibility": visibility}
if created_time:
d["created_time"] = created_time
r, _ = Rating.objects.update_or_create(owner=owner, item=item, defaults=d)
return r
@staticmethod
def get_item_rating(item: Item, owner: APIdentity) -> int | None:

View file

@ -259,6 +259,15 @@ class ShelfManager:
else:
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):
# shelf = (
# self.owner.shelf_set.all()

View file

@ -5,17 +5,18 @@
<section class="filter">
<script>
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>
<form role="search" method="get" action="">
<select name="year" id="year" onchange="filter()">
<option value="">全部</option>
{% for y in years %}
<option value="{{ y }}" {% if y == year %}selected{% endif %}>{{ y }}</option>
{% endfor %}
</select>
<form method="get" action="">
<fieldset role="search" style="width: 100%; padding;">
<select name="shelf" id="shelf" onchange="filter()">
{% for typ, label in shelf_labels %}
{% if label %}
@ -51,6 +52,18 @@
value="performance">演出</option>
{% endif %}
</select>
<select name="year" id="year" onchange="filter()">
<option value="">全部</option>
{% for y in years %}
<option value="{{ y }}" {% if y == year %}selected{% endif %}>{{ y }}</option>
{% endfor %}
</select>
<select name="sort" id="sort" onchange="filter()">
<option value="" disabled>排列顺序</option>
<option value="">时间</option>
<option value="rating" {% if 'rating' == sort %}selected{% endif %}>评分</option>
</select>
</fieldset>
</form>
<!--
<style type="text/css">

View file

@ -101,15 +101,6 @@ urlpatterns = [
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(
r"^users/(?P<user_name>[~A-Za-z0-9_\-.@]+)/reviews/(?P<item_category>"
+ _get_all_categories()
@ -117,13 +108,6 @@ urlpatterns = [
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(
r"^users/(?P<user_name>[~A-Za-z0-9_\-.@]+)/tags/(?P<tag_title>.+)/$",
user_tag_member_list,

View file

@ -3,7 +3,7 @@ import datetime
from django.contrib.auth.decorators import login_required
from django.core.exceptions import BadRequest, ObjectDoesNotExist, PermissionDenied
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.urls import reverse
from django.utils.translation import gettext_lazy as _
@ -57,12 +57,15 @@ def render_list(
item_category=None,
tag_title=None,
year=None,
sort="time",
):
target = request.target_identity
viewer = request.user.identity
tag = None
sort = request.GET.get("sort")
year = request.GET.get("year")
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":
tag = Tag.objects.filter(owner=target, title=tag_title).first()
if not tag:
@ -74,6 +77,15 @@ def render_list(
queryset = Review.objects.filter(q_item_in_category(item_category))
else:
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"]
if start_date:
start_year = start_date.year
@ -81,16 +93,14 @@ def render_list(
years = reversed(range(start_year, current_year + 1))
else:
years = []
queryset = queryset.filter(
q_owned_piece_visible_to_user(request.user, target)
).order_by("-created_time")
queryset = queryset.filter(q_owned_piece_visible_to_user(request.user, target))
if year:
year = int(year)
queryset = queryset.filter(created_time__year=year)
paginator = Paginator(queryset, PAGE_SIZE)
page_number = int(request.GET.get("page", default=1))
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 []
return render(
request,
@ -103,6 +113,7 @@ def render_list(
"pagination": pagination,
"years": years,
"year": year,
"sort": sort,
"shelf": shelf_type,
"shelf_labels": shelf_labels,
"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 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 takahe.utils import Takahe
@ -184,14 +184,7 @@ def comment(request: AuthedHttpRequest, item_uuid):
raise BadRequest()
def user_mark_list(
request: AuthedHttpRequest, user_name, shelf_type, item_category, year=None
):
def user_mark_list(request: AuthedHttpRequest, user_name, shelf_type, item_category):
return render_list(
request,
user_name,
"mark",
shelf_type=shelf_type,
item_category=item_category,
year=year,
request, user_name, "mark", shelf_type=shelf_type, item_category=item_category
)

View file

@ -5,12 +5,7 @@ from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
from common.utils import (
AuthedHttpRequest,
PageLinksGenerator,
get_uuid_or_404,
target_identity_required,
)
from common.utils import AuthedHttpRequest, get_uuid_or_404, target_identity_required
from mastodon.api import boost_toot_later
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 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 users.models.apidentity import APIdentity
@ -97,10 +97,8 @@ def review_edit(request: AuthedHttpRequest, item_uuid, review_uuid=None):
raise BadRequest()
def user_review_list(request, user_name, item_category, year=None):
return render_list(
request, user_name, "review", item_category=item_category, year=None
)
def user_review_list(request, user_name, item_category):
return render_list(request, user_name, "review", item_category=item_category)
MAX_ITEM_PER_TYPE = 10

View file

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