diff --git a/Dockerfile b/Dockerfile index 1db9c23d..d2f1d237 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,9 @@ RUN --mount=type=cache,sharing=locked,target=/var/cache/apt-run apt-get update \ gettext-base RUN busybox --install +# postgresql and redis cli are not required, but install for development convenience +RUN --mount=type=cache,sharing=locked,target=/var/cache/apt-run apt-get install -y --no-install-recommends postgresql-client redis-tools + COPY . /neodb WORKDIR /neodb COPY --from=build /neodb-venv /neodb-venv diff --git a/catalog/templates/_item_comments.html b/catalog/templates/_item_comments.html index 706e3a0e..07dc3622 100644 --- a/catalog/templates/_item_comments.html +++ b/catalog/templates/_item_comments.html @@ -46,10 +46,10 @@ data-uuid="{{ comment.item.uuid }}"> {% endif %} - {% if comment.post %} - {% include "action_reply_post.html" with post=comment.post %} - {% include "action_like_post.html" with post=comment.post %} - {% include "action_open_post.html" with post=comment.post %} + {% if comment.latest_post %} + {% include "action_reply_piece.html" with post=comment.latest_post piece=comment %} + {% include "action_like_post.html" with post=comment.latest_post %} + {% include "action_open_post.html" with post=comment.latest_post %} {% endif %} @@ -66,7 +66,7 @@ {% if comment.item != item %}{{ comment.item.title }}{% endif %}
{{ comment.html|safe }}
- {% if comment.post_id %}
{% endif %} + {% if comment.latest_post %}
{% endif %} {% else %} = timezone.now(): created_time = None post_as_new = shelf_type != self.shelf_type or visibility != self.visibility diff --git a/journal/models/rating.py b/journal/models/rating.py index b9034ab2..80479c93 100644 --- a/journal/models/rating.py +++ b/journal/models/rating.py @@ -59,12 +59,12 @@ class Rating(Content): "grade": value, "local": False, "remote_id": obj["id"], - "post_id": post_id, "visibility": visibility, "created_time": datetime.fromisoformat(obj["published"]), "edited_time": datetime.fromisoformat(obj["updated"]), } p, _ = cls.objects.update_or_create(owner=owner, item=item, defaults=d) + p.link_post_id(post_id) return p @staticmethod diff --git a/journal/models/shelf.py b/journal/models/shelf.py index d7b28852..8f9352ab 100644 --- a/journal/models/shelf.py +++ b/journal/models/shelf.py @@ -92,12 +92,12 @@ class ShelfMember(ListMember): "position": 0, "local": False, # "remote_id": obj["id"], - "post_id": post_id, "visibility": visibility, "created_time": datetime.fromisoformat(obj["published"]), "edited_time": datetime.fromisoformat(obj["updated"]), } p, _ = cls.objects.update_or_create(owner=owner, item=item, defaults=d) + p.link_post_id(post_id) return p @cached_property @@ -277,12 +277,11 @@ class ShelfManager: @classmethod def get_action_label( - cls, shelf_type: ShelfType, item_category: ItemCategory + cls, shelf_type: ShelfType | str, item_category: ItemCategory ) -> str: - sts = [ - n[2] for n in ShelfTypeNames if n[0] == item_category and n[1] == shelf_type - ] - return sts[0] if sts else str(shelf_type) + st = str(shelf_type) + sts = [n[2] for n in ShelfTypeNames if n[0] == item_category and n[1] == st] + return sts[0] if sts else st @classmethod def get_label(cls, shelf_type: ShelfType, item_category: ItemCategory): diff --git a/journal/templates/action_reply_piece.html b/journal/templates/action_reply_piece.html new file mode 100644 index 00000000..3d3a22ea --- /dev/null +++ b/journal/templates/action_reply_piece.html @@ -0,0 +1,9 @@ + + + + {% if post.stats.replies %}{{ post.stats.replies }}{% endif %} + + diff --git a/journal/templatetags/user_actions.py b/journal/templatetags/user_actions.py index 528a553b..d0d33a89 100644 --- a/journal/templatetags/user_actions.py +++ b/journal/templatetags/user_actions.py @@ -23,9 +23,9 @@ def wish_item_action(context, item): def like_piece_action(context, piece): user = context["request"].user action = {} - if user and user.is_authenticated and piece and piece.post_id: + if user and user.is_authenticated and piece and piece.latest_post: action = { - "taken": Takahe.post_liked_by(piece.post_id, user), + "taken": Takahe.post_liked_by(piece.latest_post.pk, user), "url": reverse("journal:like", args=[piece.uuid]), } return action diff --git a/journal/views/mark.py b/journal/views/mark.py index 1e24269c..e30d89c9 100644 --- a/journal/views/mark.py +++ b/journal/views/mark.py @@ -51,8 +51,9 @@ def like(request: AuthedHttpRequest, piece_uuid): piece = get_object_or_404(Piece, uid=get_uuid_or_404(piece_uuid)) if not piece: raise Http404() - if piece.post_id: - Takahe.like_post(piece.post_id, request.user.identity.pk) + post = piece.latest_post + if post: + Takahe.like_post(post.pk, request.user.identity.pk) if request.GET.get("back"): return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) elif request.GET.get("stats"): @@ -76,8 +77,9 @@ def unlike(request: AuthedHttpRequest, piece_uuid): piece = get_object_or_404(Piece, uid=get_uuid_or_404(piece_uuid)) if not piece: raise Http404() - if piece.post_id: - Takahe.unlike_post(piece.post_id, request.user.identity.pk) + post = piece.latest_post + if post: + Takahe.unlike_post(post.pk, request.user.identity.pk) if request.GET.get("back"): return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) elif request.GET.get("stats"): diff --git a/journal/views/post.py b/journal/views/post.py index 96779d7b..cc10e615 100644 --- a/journal/views/post.py +++ b/journal/views/post.py @@ -4,7 +4,6 @@ from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from catalog.models import * from common.utils import ( AuthedHttpRequest, PageLinksGenerator, @@ -23,12 +22,14 @@ def piece_replies(request: AuthedHttpRequest, piece_uuid: str): if not piece.is_visible_to(request.user): raise PermissionDenied() replies = piece.get_replies(request.user.identity) - return render(request, "replies.html", {"post": piece.post, "replies": replies}) + return render( + request, "replies.html", {"post": piece.latest_post, "replies": replies} + ) @login_required def post_replies(request: AuthedHttpRequest, post_id: int): - replies = Takahe.get_post_replies(post_id, request.user.identity.pk) + replies = Takahe.get_replies_for_posts([post_id], request.user.identity.pk) return render( request, "replies.html", {"post": Takahe.get_post(post_id), "replies": replies} ) @@ -41,7 +42,7 @@ def post_reply(request: AuthedHttpRequest, post_id: int): if request.method != "POST" or not content: raise BadRequest() Takahe.reply_post(post_id, request.user.identity.pk, content, visibility) - replies = Takahe.get_post_replies(post_id, request.user.identity.pk) + replies = Takahe.get_replies_for_posts([post_id], request.user.identity.pk) return render( request, "replies.html", {"post": Takahe.get_post(post_id), "replies": replies} ) diff --git a/takahe/ap_handlers.py b/takahe/ap_handlers.py index 5fdc87f1..3c9ce8cd 100644 --- a/takahe/ap_handlers.py +++ b/takahe/ap_handlers.py @@ -97,7 +97,7 @@ def post_updated(pk, obj): def post_deleted(pk, obj): - Piece.objects.filter(post_id=pk, local=False).delete() + Piece.objects.filter(posts__id=pk, local=False).delete() def user_follow_updated(source_identity_pk, target_identity_pk): diff --git a/takahe/utils.py b/takahe/utils.py index 8048167d..a0931ced 100644 --- a/takahe/utils.py +++ b/takahe/utils.py @@ -377,18 +377,21 @@ class Takahe: return post @staticmethod - def get_post(post_pk: int) -> str | None: + def get_post(post_pk: int) -> Post | None: return Post.objects.filter(pk=post_pk).first() + @staticmethod + def get_posts(post_pks: list[int]): + return Post.objects.filter(pk__in=post_pks) + @staticmethod def get_post_url(post_pk: int) -> str | None: post = Post.objects.filter(pk=post_pk).first() if post_pk else None return post.object_uri if post else None @staticmethod - def delete_mark(mark): - if mark.shelfmember and mark.shelfmember.post_id: - Post.objects.filter(pk=mark.shelfmember.post_id).update(state="deleted") + def delete_posts(post_pks): + Post.objects.filter(pk__in=post_pks).update(state="deleted") @staticmethod def post_mark(mark, share_as_new_post: bool) -> Post | None: @@ -428,26 +431,21 @@ class Takahe: v = Takahe.Visibilities.public else: v = Takahe.Visibilities.unlisted + existing_post = None if share_as_new_post else mark.shelfmember.latest_post post = Takahe.post( mark.owner.pk, pre_conetent, content, v, data, - None if share_as_new_post else mark.shelfmember.post_id, + existing_post.pk if existing_post else None, mark.shelfmember.created_time, ) if not post: return - if post.pk != mark.shelfmember.post_id: - mark.shelfmember.post_id = post.pk - mark.shelfmember.save(update_fields=["post_id"]) - if mark.comment and post.pk != mark.comment.post_id: - mark.comment.post_id = post.pk - mark.comment.save(update_fields=["post_id"]) - if mark.rating and post.pk != mark.rating.post_id: - mark.rating.post_id = post.pk - mark.rating.save(update_fields=["post_id"]) + for piece in [mark.shelfmember, mark.comment, mark.rating]: + if piece: + piece.link_post(post) return post @staticmethod @@ -522,11 +520,11 @@ class Takahe: return post.stats or {} @staticmethod - def get_post_replies(post_pk: int | None, identity_pk: int | None): - if not post_pk: - return Post.objects.none() - node = Post.objects.filter(pk=post_pk).first() - if not node: + def get_replies_for_posts(post_pks: list[int], identity_pk: int | None): + post_uris = Post.objects.filter(pk__in=post_pks).values_list( + "object_uri", flat=True + ) + if not post_uris.exists(): return Post.objects.none() identity = ( Identity.objects.filter(pk=identity_pk).first() if identity_pk else None @@ -542,7 +540,7 @@ class Takahe: "author", "author__domain", ) - .filter(in_reply_to=node.object_uri) + .filter(in_reply_to__in=post_uris) .order_by("published") ) if identity: