From f24df6c0fd711ea0d1e959715b4d7c9937f46b08 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 29 Dec 2022 16:20:33 -0500 Subject: [PATCH] optimize journal data models --- common/templatetags/thumb.py | 12 ++-- journal/models.py | 94 +++++++++++++++++++++---------- journal/templates/collection.html | 4 +- journal/urls.py | 1 + journal/views.py | 70 ++++++++++++++--------- 5 files changed, 114 insertions(+), 67 deletions(-) diff --git a/common/templatetags/thumb.py b/common/templatetags/thumb.py index f7adbb37..43715be9 100644 --- a/common/templatetags/thumb.py +++ b/common/templatetags/thumb.py @@ -10,10 +10,10 @@ def thumb(source, alias): This filter modifies that from `easy_thumbnails` so that it can neglect .svg file. """ - if source.url.endswith(".svg"): - return source.url - else: - try: + try: + if source.url.endswith(".svg"): + return source.url + else: return thumbnail_url(source, alias) - except Exception: - return "" + except Exception as e: + return "" diff --git a/journal/models.py b/journal/models.py index a48883d6..bc15ba65 100644 --- a/journal/models.py +++ b/journal/models.py @@ -13,9 +13,11 @@ from django.utils.translation import gettext_lazy as _ from django.core.validators import RegexValidator from functools import cached_property from django.db.models import Count, Avg +from django.contrib.contenttypes.models import ContentType import django.dispatch import math import uuid +import re from catalog.common.utils import DEFAULT_ITEM_COVER, item_cover_path from django.utils.baseconv import base62 from django.db.models import Q @@ -69,24 +71,6 @@ def query_item_category(item_category): class Piece(PolymorphicModel, UserOwnedObjectMixin): url_path = "piece" # subclass must specify this uid = models.UUIDField(default=uuid.uuid4, editable=False, db_index=True) - owner = models.ForeignKey(User, on_delete=models.PROTECT) - visibility = models.PositiveSmallIntegerField( - default=0 - ) # 0: Public / 1: Follower only / 2: Self only - created_time = models.DateTimeField( - default=timezone.now - ) # auto_now_add=True FIXME revert this after migration - edited_time = models.DateTimeField( - default=timezone.now - ) # auto_now=True FIXME revert this after migration - metadata = models.JSONField(default=dict) - attached_to = models.ForeignKey( - User, - null=True, - default=None, - on_delete=models.SET_NULL, - related_name="attached_with", - ) @property def uuid(self): @@ -102,10 +86,21 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin): @property def api_url(self): - return ("/api/" + self.url) if self.url_path else None + return f"/api/{self.url}" if self.url_path else None class Content(Piece): + owner = models.ForeignKey(User, on_delete=models.PROTECT) + visibility = models.PositiveSmallIntegerField( + default=0 + ) # 0: Public / 1: Follower only / 2: Self only + created_time = models.DateTimeField( + default=timezone.now + ) # auto_now_add=True FIXME revert this after migration + edited_time = models.DateTimeField( + default=timezone.now + ) # auto_now=True FIXME revert this after migration + metadata = models.JSONField(default=dict) item = models.ForeignKey(Item, on_delete=models.PROTECT) @cached_property @@ -122,8 +117,22 @@ class Content(Piece): class Like(Piece): + owner = models.ForeignKey(User, on_delete=models.PROTECT) + visibility = models.PositiveSmallIntegerField( + default=0 + ) # 0: Public / 1: Follower only / 2: Self only + created_time = models.DateTimeField( + default=timezone.now + ) # auto_now_add=True FIXME revert this after migration + edited_time = models.DateTimeField( + default=timezone.now + ) # auto_now=True FIXME revert this after migration target = models.ForeignKey(Piece, on_delete=models.CASCADE, related_name="likes") + @staticmethod + def user_liked_piece(user, piece): + return Like.objects.filter(owner=user, target=piece).first() + @staticmethod def user_like_piece(user, piece): if not piece or piece.__class__ not in [Collection]: @@ -139,6 +148,11 @@ class Like(Piece): return Like.objects.filter(owner=user, target=piece).delete() + @staticmethod + def user_likes_by_class(user, cls): + ctype_id = ContentType.objects.get_for_model(cls) + return Like.objects.filter(owner=user, target__polymorphic_ctype=ctype_id) + class Memo(Content): pass @@ -272,17 +286,21 @@ list_remove = django.dispatch.Signal() class List(Piece): + owner = models.ForeignKey(User, on_delete=models.PROTECT) + visibility = models.PositiveSmallIntegerField( + default=0 + ) # 0: Public / 1: Follower only / 2: Self only + created_time = models.DateTimeField( + default=timezone.now + ) # auto_now_add=True FIXME revert this after migration + edited_time = models.DateTimeField( + default=timezone.now + ) # auto_now=True FIXME revert this after migration + metadata = models.JSONField(default=dict) + class Meta: abstract = True - _owner = models.ForeignKey( - User, on_delete=models.PROTECT - ) # duplicated owner field to make unique key possible for subclasses - - def save(self, *args, **kwargs): - self._owner = self.owner - super().save(*args, **kwargs) - # MEMBER_CLASS = None # subclass must override this # subclass must add this: # items = models.ManyToManyField(Item, through='ListMember') @@ -375,6 +393,17 @@ class ListMember(Piece): parent = models.ForeignKey('List', related_name='members', on_delete=models.CASCADE) """ + owner = models.ForeignKey(User, on_delete=models.PROTECT) + visibility = models.PositiveSmallIntegerField( + default=0 + ) # 0: Public / 1: Follower only / 2: Self only + created_time = models.DateTimeField( + default=timezone.now + ) # auto_now_add=True FIXME revert this after migration + edited_time = models.DateTimeField( + default=timezone.now + ) # auto_now=True FIXME revert this after migration + metadata = models.JSONField(default=dict) item = models.ForeignKey(Item, on_delete=models.PROTECT) position = models.PositiveIntegerField() @@ -430,7 +459,7 @@ class ShelfMember(ListMember): class Shelf(List): class Meta: - unique_together = [["_owner", "item_category", "shelf_type"]] + unique_together = [["owner", "item_category", "shelf_type"]] MEMBER_CLASS = ShelfMember items = models.ManyToManyField(Item, through="ShelfMember", related_name="+") @@ -508,7 +537,7 @@ class ShelfManager: item=item, parent__in=self.owner.shelf_set.all() ).first() - def _shelf_for_item_and_type(item, shelf_type): + def _shelf_for_item_and_type(self, item, shelf_type): if not item or not shelf_type: return None return self.owner.shelf_set.all().filter( @@ -604,6 +633,9 @@ class CollectionMember(ListMember): note = jsondata.CharField(_("备注"), null=True, blank=True) +_RE_HTML_TAG = re.compile(r"<[^>]*>") + + class Collection(List): url_path = "collection" MEMBER_CLASS = CollectionMember @@ -630,7 +662,7 @@ class Collection(List): @property def plain_description(self): html = markdown(self.brief) - return RE_HTML_TAG.sub(" ", html) + return _RE_HTML_TAG.sub(" ", html) def save(self, *args, **kwargs): if getattr(self, "catalog_item", None) is None: @@ -668,7 +700,7 @@ class Tag(List): # TODO check on save class Meta: - unique_together = [["_owner", "title"]] + unique_together = [["owner", "title"]] @staticmethod def cleanup_title(title): diff --git a/journal/templates/collection.html b/journal/templates/collection.html index 3533c969..40c910e0 100644 --- a/journal/templates/collection.html +++ b/journal/templates/collection.html @@ -96,12 +96,12 @@
{% if following %} -
+ {% csrf_token %}
{% else %} -
+ {% csrf_token %}
diff --git a/journal/urls.py b/journal/urls.py index b1c442ad..82615097 100644 --- a/journal/urls.py +++ b/journal/urls.py @@ -18,6 +18,7 @@ def _get_all_shelf_types(): urlpatterns = [ path("wish/", wish, name="wish"), path("like/", like, name="like"), + path("unlike/", unlike, name="unlike"), path("mark/", mark, name="mark"), path( "add_to_collection/", add_to_collection, name="add_to_collection" diff --git a/journal/views.py b/journal/views.py index 35be91e8..b9d635ae 100644 --- a/journal/views.py +++ b/journal/views.py @@ -33,42 +33,41 @@ _checkmark = "✔️".encode("utf-8") @login_required def wish(request, item_uuid): - if request.method == "POST": - item = get_object_or_404(Item, uid=base62.decode(item_uuid)) - if not item: - return HttpResponseNotFound(b"item not found") - request.user.shelf_manager.move_item(item, ShelfType.WISHLIST) - if request.GET.get("back"): - return HttpResponseRedirect(request.META.get("HTTP_REFERER")) - return HttpResponse(_checkmark) - else: + if request.method != "POST": return HttpResponseBadRequest(b"invalid request") + item = get_object_or_404(Item, uid=base62.decode(item_uuid)) + if not item: + return HttpResponseNotFound(b"item not found") + request.user.shelf_manager.move_item(item, ShelfType.WISHLIST) + if request.GET.get("back"): + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) + return HttpResponse(_checkmark) @login_required def like(request, piece_uuid): - if request.method == "POST": - piece = get_object_or_404(Collection, uid=base62.decode(piece_uuid)) - if not piece: - return HttpResponseNotFound(b"piece not found") - Like.user_like_piece(request.user, piece) - return HttpResponse(_checkmark) - else: + if request.method != "POST": return HttpResponseBadRequest(b"invalid request") + piece = get_object_or_404(Collection, uid=base62.decode(piece_uuid)) + if not piece: + return HttpResponseNotFound(b"piece not found") + Like.user_like_piece(request.user, piece) + if request.GET.get("back"): + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) + return HttpResponse(_checkmark) @login_required def unlike(request, piece_uuid): - if request.method == "POST": - piece = get_object_or_404(Collection, uid=base62.decode(piece_uuid)) - if not piece: - return HttpResponseNotFound(b"piece not found") - Like.user_unlike_piece(request.user, piece) - if request.GET.get("back"): - return HttpResponseRedirect(request.META.get("HTTP_REFERER")) - return HttpResponse(_checkmark) - else: + if request.method != "POST": return HttpResponseBadRequest(b"invalid request") + piece = get_object_or_404(Collection, uid=base62.decode(piece_uuid)) + if not piece: + return HttpResponseNotFound(b"piece not found") + Like.user_unlike_piece(request.user, piece) + if request.GET.get("back"): + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) + return HttpResponse(_checkmark) @login_required @@ -163,7 +162,17 @@ def collection_retrieve(request, collection_uuid): collection = get_object_or_404(Collection, uid=base62.decode(collection_uuid)) if not collection.is_visible_to(request.user): raise PermissionDenied() - return render(request, "collection.html", {"collection": collection}) + follower_count = collection.likes.all().count() + following = Like.user_liked_piece(request.user, collection) is not None + return render( + request, + "collection.html", + { + "collection": collection, + "follower_count": follower_count, + "following": following, + }, + ) def collection_retrieve_items(request, collection_uuid, edit=False): @@ -563,7 +572,9 @@ def home(request, user_name): Collection.objects.filter(owner=user).filter(qv).order_by("-edited_time") ) liked_collections = ( - Collection.objects.none().filter(likes__owner=user).order_by("-edited_time") + Like.user_likes_by_class(user, Collection) + .order_by("-edited_time") + .values_list("target_id", flat=True) ) if user != request.user: liked_collections = liked_collections.filter(query_visible(request.user)) @@ -577,7 +588,10 @@ def home(request, user_name): "shelf_list": shelf_list, "collections": collections[:5], "collections_count": collections.count(), - "liked_collections": liked_collections.order_by("-edited_time")[:5], + "liked_collections": [ + Collection.objects.get(id=i) + for i in liked_collections.order_by("-edited_time")[:5] + ], "liked_collections_count": liked_collections.count(), "layout": layout, "reports": reports,