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: