From baf678e4656f3d604e5287b69caa5797fc47f5bc Mon Sep 17 00:00:00 2001 From: Her Email Date: Mon, 20 Nov 2023 01:59:26 -0500 Subject: [PATCH 1/8] rewrite Mark.update() --- journal/api.py | 13 +- ...gentrypost_shelflogentry_posts_and_more.py | 59 ++++++ journal/models/common.py | 3 + journal/models/mark.py | 182 +++++++++++------- journal/models/shelf.py | 59 +++++- journal/views/mark.py | 14 +- journal/views/review.py | 14 +- mastodon/api.py | 8 + takahe/utils.py | 6 +- 9 files changed, 251 insertions(+), 107 deletions(-) create mode 100644 journal/migrations/0018_shelflogentrypost_shelflogentry_posts_and_more.py diff --git a/journal/api.py b/journal/api.py index ac0dd094..8fc6dd98 100644 --- a/journal/api.py +++ b/journal/api.py @@ -10,7 +10,7 @@ from catalog.common.models import * from common.api import * -from mastodon.api import boost_toot +from mastodon.api import boost_toot_later from .models import Mark, Review, ShelfType, TagManager, q_item_in_category @@ -195,7 +195,7 @@ def review_item(request, item_uuid: str, review: ReviewInSchema): item = Item.get_by_url(item_uuid) if not item: return 404, {"message": "Item not found"} - r, p = Review.update_item_review( + r, post = Review.update_item_review( item, request.user, review.title, @@ -203,13 +203,8 @@ def review_item(request, item_uuid: str, review: ReviewInSchema): review.visibility, created_time=review.created_time, ) - if p and review.post_to_fediverse and request.user.mastodon_username: - boost_toot( - request.user.mastodon_site, - request.user.mastodon_token, - p.url, - ) - + if post and review.post_to_fediverse: + boost_toot_later(request.user, post.url) return 200, {"message": "OK"} diff --git a/journal/migrations/0018_shelflogentrypost_shelflogentry_posts_and_more.py b/journal/migrations/0018_shelflogentrypost_shelflogentry_posts_and_more.py new file mode 100644 index 00000000..ac8face1 --- /dev/null +++ b/journal/migrations/0018_shelflogentrypost_shelflogentry_posts_and_more.py @@ -0,0 +1,59 @@ +# Generated by Django 4.2.7 on 2023-11-20 06:52 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("takahe", "0001_initial"), + ("journal", "0017_alter_piece_options_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="ShelfLogEntryPost", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "log_entry", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="journal.shelflogentry", + ), + ), + ( + "post", + models.ForeignKey( + db_constraint=False, + on_delete=django.db.models.deletion.CASCADE, + to="takahe.post", + ), + ), + ], + ), + migrations.AddField( + model_name="shelflogentry", + name="posts", + field=models.ManyToManyField( + related_name="log_entries", + through="journal.ShelfLogEntryPost", + to="takahe.post", + ), + ), + migrations.AddConstraint( + model_name="shelflogentrypost", + constraint=models.UniqueConstraint( + fields=("log_entry", "post"), name="unique_log_entry_post" + ), + ), + ] diff --git a/journal/models/common.py b/journal/models/common.py index c5024f62..5406daea 100644 --- a/journal/models/common.py +++ b/journal/models/common.py @@ -197,6 +197,9 @@ def link_post_id(self, post_id: int): def link_post(self, post: "Post"): return self.link_post_id(post.pk) + def clear_post_ids(self): + PiecePost.objects.filter(piece=self).delete() + @cached_property def latest_post(self): # local post id is ordered by their created time diff --git a/journal/models/mark.py b/journal/models/mark.py index 3657efc5..113b934f 100644 --- a/journal/models/mark.py +++ b/journal/models/mark.py @@ -20,7 +20,7 @@ from catalog.common import jsondata from catalog.common.models import Item, ItemCategory from catalog.common.utils import DEFAULT_ITEM_COVER, piece_cover_path -from mastodon.api import boost_toot +from mastodon.api import boost_toot_later from takahe.utils import Takahe from users.models import APIdentity @@ -119,86 +119,134 @@ def comment_html(self) -> str | None: def review(self) -> Review | None: return Review.objects.filter(owner=self.owner, item=self.item).first() + @property + def logs(self): + return ShelfLogEntry.objects.filter(owner=self.owner, item=self.item).order_by( + "timestamp" + ) + + """ + log entries + log entry will be created when item is added to shelf + log entry will be created when item is moved to another shelf + log entry will be created when item is removed from shelf (TODO change this to DEFERRED shelf) + timestamp of log entry will be updated whenever created_time of shelfmember is updated + any log entry can be deleted by user arbitrarily + + posts + post will be created and set as current when item added to shelf + current post will be updated when comment or rating is updated + post will not be updated if only created_time is changed + post will be deleted, re-created and set as current if visibility changed + when item is moved to another shelf, a new post will be created + when item is removed from shelf, all post will be deleted + + boost + post will be boosted to mastodon if user has mastodon token and site configured + """ + + @property + def all_post_ids(self): + """all post ids for this user and item""" + pass + + @property + def current_post_ids(self): + """all post ids for this user and item for its current shelf""" + pass + + @property + def latest_post_id(self): + """latest post id for this user and item for its current shelf""" + pass + + def wish(self): + """add to wishlist if not on shelf""" + if self.shelfmember: + logger.warning("item already on shelf, cannot wishlist again") + return False + self.shelfmember = ShelfMember.objects.create( + owner=self.owner, + item=self.item, + parent=Shelf.objects.get(owner=self.owner, shelf_type=ShelfType.WISHLIST), + visibility=self.owner.preference.default_visibility, + ) + self.shelfmember.create_log_entry() + post = Takahe.post_mark(self, True) + if post and not self.owner.preference.default_no_share: + boost_toot_later(self.owner, post.url) + return True + def update( self, - shelf_type, - comment_text, - rating_grade, - visibility, + shelf_type: ShelfType | None, + comment_text: str | None, + rating_grade: int | None, + visibility: int, metadata=None, created_time=None, share_to_mastodon=False, ): - post_to_feed = shelf_type is not None and ( - shelf_type != self.shelf_type - or comment_text != self.comment_text - or rating_grade != self.rating_grade - or visibility != self.visibility - ) - if shelf_type is None or visibility != self.visibility: - if self.shelfmember: - Takahe.delete_posts(self.shelfmember.all_post_ids) + """change shelf, comment or rating""" if created_time and created_time >= timezone.now(): created_time = None - post_as_new = shelf_type != self.shelf_type or visibility != self.visibility - original_visibility = self.visibility - if shelf_type != self.shelf_type or visibility != original_visibility: - self.shelfmember = self.owner.shelf_manager.move_item( - self.item, - shelf_type, - visibility=visibility, - metadata=metadata, + last_shelf_type = self.shelf_type + last_visibility = self.visibility if last_shelf_type else None + if shelf_type is None: # TODO change this use case to DEFERRED status + # take item off shelf + if last_shelf_type: + Takahe.delete_posts(self.shelfmember.all_post_ids) + self.shelfmember.log_and_delete() + return + # create/update shelf member and shelf log if necessary + if last_shelf_type == shelf_type: + shelfmember_changed = False + if last_visibility != visibility: + self.shelfmember.visibility = visibility + shelfmember_changed = True + # retract most recent post about this status when visibility changed + Takahe.delete_posts([self.shelfmember.latest_post_id]) + if created_time and created_time != self.shelfmember.created_time: + self.shelfmember.created_time = created_time + log_entry = self.shelfmember.ensure_log_entry() + log_entry.timestamp = created_time + log_entry.save(update_fields=["timestamp"]) + self.shelfmember.change_timestamp(created_time) + shelfmember_changed = True + if shelfmember_changed: + self.shelfmember.save() + else: + shelf = Shelf.objects.get(owner=self.owner, shelf_type=shelf_type) + d = {"parent": shelf, "visibility": visibility, "position": 0} + if metadata: + d["metadata"] = metadata + if created_time: + d["created_time"] = created_time + self.shelfmember, _ = ShelfMember.objects.update_or_create( + owner=self.owner, item=self.item, defaults=d ) - if self.shelfmember and created_time: - # if it's an update(not delete) and created_time is specified, - # update the timestamp of the shelfmember and log - log = ShelfLogEntry.objects.filter( - owner=self.owner, - item=self.item, - timestamp=self.shelfmember.created_time, - ).first() - self.shelfmember.created_time = created_time - self.shelfmember.save(update_fields=["created_time"]) - if log: - log.timestamp = created_time - log.save(update_fields=["timestamp"]) - else: - ShelfLogEntry.objects.create( - owner=self.owner, - shelf_type=shelf_type, - item=self.item, - metadata=self.metadata, - timestamp=created_time, - ) - if comment_text != self.comment_text or visibility != original_visibility: + self.shelfmember.create_log_entry() + self.shelfmember.clear_post_ids() + # create/update/detele comment if necessary + if comment_text != self.comment_text or visibility != last_visibility: self.comment = Comment.comment_item( self.item, self.owner, comment_text, visibility, - self.shelfmember.created_time if self.shelfmember else None, + self.shelfmember.created_time, ) - if rating_grade != self.rating_grade or visibility != original_visibility: + # create/update/detele rating if necessary + if rating_grade != self.rating_grade or visibility != last_visibility: Rating.update_item_rating(self.item, self.owner, rating_grade, visibility) self.rating_grade = rating_grade - - post = Takahe.post_mark(self, post_as_new) if post_to_feed else None - if share_to_mastodon and post: - if ( - self.owner.user - and self.owner.user.mastodon_token - and self.owner.user.mastodon_site - ): - # TODO: make this a async task, given post to mastodon is slow and takahe post fanout may take time - if boost_toot( - self.owner.user.mastodon_site, - self.owner.user.mastodon_token, - post.url, - ): - return True - return False - else: - return True + # publish a new or updated ActivityPub post + post_as_new = shelf_type != self.shelf_type or visibility != self.visibility + post = Takahe.post_mark(self, post_as_new) + # async boost to mastodon + if post and share_to_mastodon: + boost_toot_later(self.owner, post.url) + return True def delete(self): # self.logs.delete() # When deleting a mark, all logs of the mark are deleted first. @@ -211,9 +259,3 @@ def delete_log(self, log_id): def delete_all_logs(self): self.logs.delete() - - @property - def logs(self): - return ShelfLogEntry.objects.filter(owner=self.owner, item=self.item).order_by( - "timestamp" - ) diff --git a/journal/models/shelf.py b/journal/models/shelf.py index fd6abb9a..a9fd8b56 100644 --- a/journal/models/shelf.py +++ b/journal/models/shelf.py @@ -8,7 +8,7 @@ from loguru import logger from catalog.models import Item, ItemCategory -from takahe.models import Identity +from takahe.models import Identity, Post from users.models import APIdentity from .common import q_item_in_category @@ -92,7 +92,6 @@ def update_by_ap_object( "parent": shelf, "position": 0, "local": False, - # "remote_id": obj["id"], "visibility": visibility, "created_time": datetime.fromisoformat(obj["published"]), "edited_time": datetime.fromisoformat(obj["updated"]), @@ -129,6 +128,38 @@ def comment_text(self): def tags(self): return self.mark.tags + def get_log_entry(self): + return ShelfLogEntry.objects.filter( + owner=self.owner, + item=self.item, + timestamp=self.created_time, + ).first() + + def create_log_entry(self): + return ShelfLogEntry.objects.create( + owner=self.owner, + shelf_type=self.shelf_type, + item=self.item, + metadata=self.metadata, + timestamp=self.created_time, + ) + + def ensure_log_entry(self): + return self.get_log_entry() or self.create_log_entry() + + def log_and_delete(self): + ShelfLogEntry.objects.create( + owner=self.owner, + shelf_type=None, + item=self.item, + timestamp=timezone.now(), + ) + self.delete() + + def link_post_id(self, post_id: int): + self.ensure_log_entry().link_post_id(post_id) + return super().link_post_id(post_id) + class Shelf(List): """ @@ -153,9 +184,12 @@ class ShelfLogEntry(models.Model): shelf_type = models.CharField(choices=ShelfType.choices, max_length=100, null=True) item = models.ForeignKey(Item, on_delete=models.PROTECT) timestamp = models.DateTimeField() # this may later be changed by user - metadata = models.JSONField(default=dict) + metadata = models.JSONField(default=dict) # TODO Remove this field created_time = models.DateTimeField(auto_now_add=True) edited_time = models.DateTimeField(auto_now=True) + posts = models.ManyToManyField( + "takahe.Post", related_name="log_entries", through="ShelfLogEntryPost" + ) def __str__(self): return f"{self.owner}:{self.shelf_type}:{self.item.uuid}:{self.timestamp}:{self.metadata}" @@ -167,6 +201,23 @@ def action_label(self): else: return _("移除标记") + def link_post_id(self, post_id: int): + ShelfLogEntryPost.objects.get_or_create(log_entry=self, post_id=post_id) + + +class ShelfLogEntryPost(models.Model): + log_entry = models.ForeignKey(ShelfLogEntry, on_delete=models.CASCADE) + post = models.ForeignKey( + "takahe.Post", db_constraint=False, db_index=True, on_delete=models.CASCADE + ) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["log_entry", "post"], name="unique_log_entry_post" + ), + ] + class ShelfManager: """ @@ -190,7 +241,7 @@ def initialize(self): def locate_item(self, item: Item) -> ShelfMember | None: return ShelfMember.objects.filter(item=item, owner=self.owner).first() - def move_item( + def move_item( # TODO remove this method self, item: Item, shelf_type: ShelfType, diff --git a/journal/views/mark.py b/journal/views/mark.py index f5821ece..8b44ff1d 100644 --- a/journal/views/mark.py +++ b/journal/views/mark.py @@ -13,7 +13,7 @@ from catalog.models import * from common.utils import AuthedHttpRequest, PageLinksGenerator, get_uuid_or_404 -from mastodon.api import boost_toot +from mastodon.api import boost_toot_later from takahe.utils import Takahe from ..models import Comment, Mark, Piece, ShelfType, ShelfTypeNames, TagManager @@ -32,7 +32,7 @@ def wish(request: AuthedHttpRequest, item_uuid): item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid)) if not item: raise Http404() - request.user.identity.shelf_manager.move_item(item, ShelfType.WISHLIST) + Mark(request.user.identity, item).wish() if request.GET.get("back"): return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) return HttpResponse(_checkmark) @@ -221,14 +221,8 @@ def comment(request: AuthedHttpRequest, item_uuid): ) post = Takahe.post_comment(comment, False) share_to_mastodon = bool(request.POST.get("share_to_mastodon", default=False)) - if post and share_to_mastodon and request.user.mastodon_username: - boost_toot( - request.user.mastodon_site, - request.user.mastodon_token, - post.url, - ) - # if post_error: - # return render_relogin(request) + if post and share_to_mastodon: + boost_toot_later(request.user, post.url) return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) raise BadRequest() diff --git a/journal/views/review.py b/journal/views/review.py index f68d0b21..137b1f99 100644 --- a/journal/views/review.py +++ b/journal/views/review.py @@ -14,7 +14,7 @@ from catalog.models import * from common.utils import AuthedHttpRequest, PageLinksGenerator, get_uuid_or_404 from journal.models.renderers import convert_leading_space_in_md, render_md -from mastodon.api import boost_toot +from mastodon.api import boost_toot_later from users.models import User from users.models.apidentity import APIdentity @@ -84,16 +84,8 @@ def review_edit(request: AuthedHttpRequest, item_uuid, review_uuid=None): ) if not review: raise BadRequest() - if ( - form.cleaned_data["share_to_mastodon"] - and request.user.mastodon_username - and post - ): - boost_toot( - request.user.mastodon_site, - request.user.mastodon_token, - post.url, - ) + if form.cleaned_data["share_to_mastodon"] and post: + boost_toot_later(request.user, post.url) return redirect(reverse("journal:review_retrieve", args=[review.uuid])) else: raise BadRequest() diff --git a/mastodon/api.py b/mastodon/api.py index bf260349..e378272a 100644 --- a/mastodon/api.py +++ b/mastodon/api.py @@ -5,6 +5,7 @@ import string from urllib.parse import quote +import django_rq import requests from django.conf import settings from loguru import logger @@ -117,6 +118,13 @@ def boost_toot(site, token, toot_url): return None +def boost_toot_later(user, post_url): + if user and user.mastodon_token and user.mastodon_site and post_url: + django_rq.get_queue("fetch").enqueue( + boost_toot, user.mastodon_site, user.mastodon_token, post_url + ) + + def post_toot( site, content, diff --git a/takahe/utils.py b/takahe/utils.py index 4fbc543e..7af1bece 100644 --- a/takahe/utils.py +++ b/takahe/utils.py @@ -438,7 +438,7 @@ def post_comment(comment, share_as_new_post: bool) -> Post | None: ) if not post: return - comment.link_post(post) + comment.link_post_id(post.pk) return post @staticmethod @@ -485,7 +485,7 @@ def post_review(review, share_as_new_post: bool) -> Post | None: ) if not post: return - review.link_post(post) + review.link_post_id(post.pk) return post @staticmethod @@ -540,7 +540,7 @@ def post_mark(mark, share_as_new_post: bool) -> Post | None: return for piece in [mark.shelfmember, mark.comment, mark.rating]: if piece: - piece.link_post(post) + piece.link_post_id(post.pk) return post @staticmethod From 0dfde79a1a6d540f85495e9d245c9581ef8d19c5 Mon Sep 17 00:00:00 2001 From: Her Email Date: Mon, 20 Nov 2023 12:13:43 -0500 Subject: [PATCH 2/8] further streamline mark.update() --- journal/models/common.py | 3 -- journal/models/mark.py | 80 ++++++++++++++++------------------ journal/models/shelf.py | 93 +++++++++------------------------------- journal/tests.py | 79 +++++++++++++++++++--------------- journal/views/mark.py | 4 +- social/tests.py | 12 ++---- takahe/utils.py | 46 ++++++++++---------- 7 files changed, 131 insertions(+), 186 deletions(-) diff --git a/journal/models/common.py b/journal/models/common.py index 5406daea..53b4b5ed 100644 --- a/journal/models/common.py +++ b/journal/models/common.py @@ -194,9 +194,6 @@ def ap_object(self): def link_post_id(self, post_id: int): PiecePost.objects.get_or_create(piece=self, post_id=post_id) - def link_post(self, post: "Post"): - return self.link_post_id(post.pk) - def clear_post_ids(self): PiecePost.objects.filter(piece=self).delete() diff --git a/journal/models/mark.py b/journal/models/mark.py index 113b934f..893e68f4 100644 --- a/journal/models/mark.py +++ b/journal/models/mark.py @@ -88,7 +88,7 @@ def visibility(self) -> int: if self.shelfmember: return self.shelfmember.visibility else: - # mark not saved yet, return default visibility for editing ui + # mark not created/saved yet, use user's default visibility return self.owner.preference.default_visibility @cached_property @@ -148,41 +148,24 @@ def logs(self): @property def all_post_ids(self): """all post ids for this user and item""" - pass + return self.logs.values_list("posts", flat=True) @property def current_post_ids(self): - """all post ids for this user and item for its current shelf""" - pass + """all post ids for this user and item for its current status""" + return self.shelfmember.all_post_ids if self.shelfmember else [] @property def latest_post_id(self): - """latest post id for this user and item for its current shelf""" - pass - - def wish(self): - """add to wishlist if not on shelf""" - if self.shelfmember: - logger.warning("item already on shelf, cannot wishlist again") - return False - self.shelfmember = ShelfMember.objects.create( - owner=self.owner, - item=self.item, - parent=Shelf.objects.get(owner=self.owner, shelf_type=ShelfType.WISHLIST), - visibility=self.owner.preference.default_visibility, - ) - self.shelfmember.create_log_entry() - post = Takahe.post_mark(self, True) - if post and not self.owner.preference.default_no_share: - boost_toot_later(self.owner, post.url) - return True + """latest post id for this user and item for its current status""" + return self.shelfmember.latest_post_id if self.shelfmember else None def update( self, shelf_type: ShelfType | None, - comment_text: str | None, - rating_grade: int | None, - visibility: int, + comment_text: str | None = None, + rating_grade: int | None = None, + visibility: int | None = None, metadata=None, created_time=None, share_to_mastodon=False, @@ -190,6 +173,8 @@ def update( """change shelf, comment or rating""" if created_time and created_time >= timezone.now(): created_time = None + if visibility is None: + visibility = self.visibility last_shelf_type = self.shelf_type last_visibility = self.visibility if last_shelf_type else None if shelf_type is None: # TODO change this use case to DEFERRED status @@ -201,14 +186,19 @@ def update( # create/update shelf member and shelf log if necessary if last_shelf_type == shelf_type: shelfmember_changed = False + log_entry = self.shelfmember.ensure_log_entry() + if metadata is not None and metadata != self.shelfmember.metadata: + self.shelfmember.metadata = metadata + shelfmember_changed = True if last_visibility != visibility: self.shelfmember.visibility = visibility shelfmember_changed = True # retract most recent post about this status when visibility changed - Takahe.delete_posts([self.shelfmember.latest_post_id]) + latest_post = self.shelfmember.latest_post + if latest_post: + Takahe.delete_posts([latest_post.pk]) if created_time and created_time != self.shelfmember.created_time: self.shelfmember.created_time = created_time - log_entry = self.shelfmember.ensure_log_entry() log_entry.timestamp = created_time log_entry.save(update_fields=["timestamp"]) self.shelfmember.change_timestamp(created_time) @@ -225,24 +215,28 @@ def update( self.shelfmember, _ = ShelfMember.objects.update_or_create( owner=self.owner, item=self.item, defaults=d ) - self.shelfmember.create_log_entry() + self.shelfmember.ensure_log_entry() self.shelfmember.clear_post_ids() # create/update/detele comment if necessary - if comment_text != self.comment_text or visibility != last_visibility: - self.comment = Comment.comment_item( - self.item, - self.owner, - comment_text, - visibility, - self.shelfmember.created_time, - ) + if comment_text is not None: + if comment_text != self.comment_text or visibility != last_visibility: + self.comment = Comment.comment_item( + self.item, + self.owner, + comment_text, + visibility, + self.shelfmember.created_time, + ) # create/update/detele rating if necessary - if rating_grade != self.rating_grade or visibility != last_visibility: - Rating.update_item_rating(self.item, self.owner, rating_grade, visibility) - self.rating_grade = rating_grade + if rating_grade is not None: + if rating_grade != self.rating_grade or visibility != last_visibility: + Rating.update_item_rating( + self.item, self.owner, rating_grade, visibility + ) + self.rating_grade = rating_grade # publish a new or updated ActivityPub post - post_as_new = shelf_type != self.shelf_type or visibility != self.visibility - post = Takahe.post_mark(self, post_as_new) + post_as_new = shelf_type != last_shelf_type or visibility != last_visibility + post = Takahe.post_mark(self, post_as_new) # this will update linked post # async boost to mastodon if post and share_to_mastodon: boost_toot_later(self.owner, post.url) @@ -250,7 +244,7 @@ def update( def delete(self): # self.logs.delete() # When deleting a mark, all logs of the mark are deleted first. - self.update(None, None, None, 0) + self.update(None) def delete_log(self, log_id): ShelfLogEntry.objects.filter( diff --git a/journal/models/shelf.py b/journal/models/shelf.py index a9fd8b56..aae96c24 100644 --- a/journal/models/shelf.py +++ b/journal/models/shelf.py @@ -128,24 +128,32 @@ def comment_text(self): def tags(self): return self.mark.tags - def get_log_entry(self): - return ShelfLogEntry.objects.filter( - owner=self.owner, - item=self.item, - timestamp=self.created_time, - ).first() + # def get_log_entry(self): + # return ShelfLogEntry.objects.filter( + # owner=self.owner, + # item=self.item, + # shelf_type=self.shelf_type, + # timestamp=self.created_time, + # ).first() + + # def create_log_entry(self): + # return ShelfLogEntry.objects.create( + # owner=self.owner, + # shelf_type=self.shelf_type, + # item=self.item, + # metadata=self.metadata, + # timestamp=self.created_time, + # ) - def create_log_entry(self): - return ShelfLogEntry.objects.create( + def ensure_log_entry(self): + log, _ = ShelfLogEntry.objects.get_or_create( owner=self.owner, shelf_type=self.shelf_type, item=self.item, - metadata=self.metadata, timestamp=self.created_time, + defaults={"metadata": self.metadata}, ) - - def ensure_log_entry(self): - return self.get_log_entry() or self.create_log_entry() + return log def log_and_delete(self): ShelfLogEntry.objects.create( @@ -241,67 +249,6 @@ def initialize(self): def locate_item(self, item: Item) -> ShelfMember | None: return ShelfMember.objects.filter(item=item, owner=self.owner).first() - def move_item( # TODO remove this method - self, - item: Item, - shelf_type: ShelfType, - visibility: int = 0, - metadata: dict | None = None, - ): - # shelf_type=None means remove from current shelf - # metadata=None means no change - if not item: - raise ValueError("empty item") - new_shelfmember = None - last_shelfmember = self.locate_item(item) - last_shelf = last_shelfmember.parent if last_shelfmember else None - last_metadata = last_shelfmember.metadata if last_shelfmember else None - last_visibility = last_shelfmember.visibility if last_shelfmember else None - shelf = self.shelf_list[shelf_type] if shelf_type else None - changed = False - if last_shelf != shelf: # change shelf - changed = True - if last_shelf: - last_shelf.remove_item(item) - if shelf: - new_shelfmember = shelf.append_item( - item, visibility=visibility, metadata=metadata or {} - ) - elif last_shelf is None: - raise ValueError("empty shelf") - else: - new_shelfmember = last_shelfmember - if last_shelfmember: - if ( - metadata is not None and metadata != last_metadata - ): # change metadata - changed = True - last_shelfmember.metadata = metadata - last_shelfmember.visibility = visibility - last_shelfmember.save() - elif visibility != last_visibility: # change visibility - last_shelfmember.visibility = visibility - last_shelfmember.save() - if changed: - if metadata is None: - metadata = last_metadata or {} - log_time = ( - new_shelfmember.created_time - if new_shelfmember and new_shelfmember != last_shelfmember - else timezone.now() - ) - ShelfLogEntry.objects.create( - owner=self.owner, - shelf_type=shelf_type, - item=item, - metadata=metadata, - timestamp=log_time, - ) - return new_shelfmember - - def get_log(self): - return ShelfLogEntry.objects.filter(owner=self.owner).order_by("timestamp") - def get_log_for_item(self, item: Item): return ShelfLogEntry.objects.filter(owner=self.owner, item=item).order_by( "timestamp" diff --git a/journal/tests.py b/journal/tests.py index e505206e..dee0988d 100644 --- a/journal/tests.py +++ b/journal/tests.py @@ -56,61 +56,72 @@ def test_shelf(self): self.assertIsNotNone(q2) self.assertEqual(q1.members.all().count(), 0) self.assertEqual(q2.members.all().count(), 0) - shelf_manager.move_item(book1, ShelfType.WISHLIST) - time.sleep(0.001) - shelf_manager.move_item(book2, ShelfType.WISHLIST) + Mark(user.identity, book1).update(ShelfType.WISHLIST) + time.sleep(0.001) # add a little delay to make sure the timestamp is different + Mark(user.identity, book2).update(ShelfType.WISHLIST) time.sleep(0.001) self.assertEqual(q1.members.all().count(), 2) - shelf_manager.move_item(book1, ShelfType.PROGRESS) + Mark(user.identity, book1).update(ShelfType.PROGRESS) time.sleep(0.001) self.assertEqual(q1.members.all().count(), 1) self.assertEqual(q2.members.all().count(), 1) + self.assertEqual(len(Mark(user.identity, book1).all_post_ids), 2) log = shelf_manager.get_log_for_item(book1) self.assertEqual(log.count(), 2) last_log = log.last() self.assertEqual(last_log.metadata if last_log else 42, {}) - shelf_manager.move_item(book1, ShelfType.PROGRESS, metadata={"progress": 1}) + Mark(user.identity, book1).update(ShelfType.PROGRESS, metadata={"progress": 1}) time.sleep(0.001) self.assertEqual(q1.members.all().count(), 1) self.assertEqual(q2.members.all().count(), 1) log = shelf_manager.get_log_for_item(book1) - self.assertEqual(log.count(), 3) - last_log = log.last() - self.assertEqual(last_log.metadata if last_log else 42, {"progress": 1}) - shelf_manager.move_item(book1, ShelfType.PROGRESS, metadata={"progress": 1}) - time.sleep(0.001) - log = shelf_manager.get_log_for_item(book1) - self.assertEqual(log.count(), 3) - last_log = log.last() - self.assertEqual(last_log.metadata if last_log else 42, {"progress": 1}) - shelf_manager.move_item(book1, ShelfType.PROGRESS, metadata={"progress": 10}) - time.sleep(0.001) - log = shelf_manager.get_log_for_item(book1) - self.assertEqual(log.count(), 4) + self.assertEqual(log.count(), 2) + self.assertEqual(len(Mark(user.identity, book1).all_post_ids), 2) + + # theses tests are not relevant anymore, bc we don't use log to track metadata changes + # last_log = log.last() + # self.assertEqual(last_log.metadata if last_log else 42, {"progress": 1}) + # Mark(user.identity, book1).update(ShelfType.PROGRESS, metadata={"progress": 1}) + # time.sleep(0.001) + # log = shelf_manager.get_log_for_item(book1) + # self.assertEqual(log.count(), 3) + # last_log = log.last() + # self.assertEqual(last_log.metadata if last_log else 42, {"progress": 1}) + # Mark(user.identity, book1).update(ShelfType.PROGRESS, metadata={"progress": 10}) + # time.sleep(0.001) + # log = shelf_manager.get_log_for_item(book1) + # self.assertEqual(log.count(), 4) + # last_log = log.last() + # self.assertEqual(last_log.metadata if last_log else 42, {"progress": 10}) + # shelf_manager.move_item(book1, ShelfType.PROGRESS) + # time.sleep(0.001) + # log = shelf_manager.get_log_for_item(book1) + # self.assertEqual(log.count(), 4) + # last_log = log.last() + # self.assertEqual(last_log.metadata if last_log else 42, {"progress": 10}) + # shelf_manager.move_item(book1, ShelfType.PROGRESS, metadata={"progress": 90}) + # time.sleep(0.001) + # log = shelf_manager.get_log_for_item(book1) + # self.assertEqual(log.count(), 5) - last_log = log.last() - self.assertEqual(last_log.metadata if last_log else 42, {"progress": 10}) - shelf_manager.move_item(book1, ShelfType.PROGRESS) - time.sleep(0.001) - log = shelf_manager.get_log_for_item(book1) - self.assertEqual(log.count(), 4) - last_log = log.last() - self.assertEqual(last_log.metadata if last_log else 42, {"progress": 10}) - shelf_manager.move_item(book1, ShelfType.PROGRESS, metadata={"progress": 90}) - time.sleep(0.001) - log = shelf_manager.get_log_for_item(book1) - self.assertEqual(log.count(), 5) self.assertEqual(Mark(user.identity, book1).visibility, 0) - shelf_manager.move_item( - book1, ShelfType.PROGRESS, metadata={"progress": 90}, visibility=1 + self.assertEqual(len(Mark(user.identity, book1).current_post_ids), 1) + Mark(user.identity, book1).update( + ShelfType.PROGRESS, metadata={"progress": 90}, visibility=1 ) + self.assertEqual(len(Mark(user.identity, book1).current_post_ids), 2) + self.assertEqual(len(Mark(user.identity, book1).all_post_ids), 3) time.sleep(0.001) + Mark(user.identity, book1).update( + ShelfType.COMPLETE, metadata={"progress": 100} + ) self.assertEqual(Mark(user.identity, book1).visibility, 1) - self.assertEqual(shelf_manager.get_log_for_item(book1).count(), 5) + self.assertEqual(shelf_manager.get_log_for_item(book1).count(), 3) + self.assertEqual(len(Mark(user.identity, book1).all_post_ids), 4) # test delete mark -> one more log Mark(user.identity, book1).delete() - self.assertEqual(log.count(), 6) + self.assertEqual(log.count(), 4) class TagTest(TestCase): diff --git a/journal/views/mark.py b/journal/views/mark.py index 8b44ff1d..f8ff53ef 100644 --- a/journal/views/mark.py +++ b/journal/views/mark.py @@ -32,7 +32,9 @@ def wish(request: AuthedHttpRequest, item_uuid): item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid)) if not item: raise Http404() - Mark(request.user.identity, item).wish() + mark = Mark(request.user.identity, item) + if not mark.shelf_type: + mark.update(ShelfType.WISHLIST) if request.GET.get("back"): return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) return HttpResponse(_checkmark) diff --git a/social/tests.py b/social/tests.py index b881977e..a9e5d100 100644 --- a/social/tests.py +++ b/social/tests.py @@ -30,17 +30,15 @@ def test_timeline(self): self.assertEqual(len(alice_feed.get_timeline()), 0) # 1 activity after adding first book to shelf - self.alice.identity.shelf_manager.move_item( - self.book1, ShelfType.WISHLIST, visibility=1 - ) + Mark(self.alice.identity, self.book1).update(ShelfType.WISHLIST, visibility=1) self.assertEqual(len(alice_feed.get_timeline()), 1) # 2 activities after adding second book to shelf - self.alice.identity.shelf_manager.move_item(self.book2, ShelfType.WISHLIST) + Mark(self.alice.identity, self.book2).update(ShelfType.WISHLIST) self.assertEqual(len(alice_feed.get_timeline()), 2) # 2 activities after change first mark - self.alice.identity.shelf_manager.move_item(self.book1, ShelfType.PROGRESS) + Mark(self.alice.identity, self.book1).update(ShelfType.PROGRESS) self.assertEqual(len(alice_feed.get_timeline()), 2) # bob see 0 activity in timeline in the beginning @@ -60,9 +58,7 @@ def test_timeline(self): self.assertEqual(len(bob_feed.get_timeline()), 2) # alice:3 bob:2 after alice adding second book to shelf as private - self.alice.identity.shelf_manager.move_item( - self.movie, ShelfType.WISHLIST, visibility=2 - ) + Mark(self.alice.identity, self.movie).update(ShelfType.WISHLIST, visibility=2) self.assertEqual(len(alice_feed.get_timeline()), 3) self.assertEqual(len(bob_feed.get_timeline()), 2) diff --git a/takahe/utils.py b/takahe/utils.py index 7af1bece..499c4d9c 100644 --- a/takahe/utils.py +++ b/takahe/utils.py @@ -396,6 +396,19 @@ def get_post_url(post_pk: int) -> str | None: @staticmethod def delete_posts(post_pks): Post.objects.filter(pk__in=post_pks).update(state="deleted") + # TimelineEvent.objects.filter(subject_post__in=[post.pk]).delete() + PostInteraction.objects.filter(post__in=post_pks).update(state="undone") + + @staticmethod + def visibility_n2t(visibility: int, default_public) -> Visibilities: + if visibility == 1: + return Takahe.Visibilities.followers + elif visibility == 2: + return Takahe.Visibilities.mentioned + elif default_public: + return Takahe.Visibilities.public + else: + return Takahe.Visibilities.unlisted @staticmethod def post_comment(comment, share_as_new_post: bool) -> Post | None: @@ -418,14 +431,9 @@ def post_comment(comment, share_as_new_post: bool) -> Post | None: "relatedWith": [comment.ap_object], } } - if comment.visibility == 1: - v = Takahe.Visibilities.followers - elif comment.visibility == 2: - v = Takahe.Visibilities.mentioned - elif user.preference.mastodon_publish_public: - v = Takahe.Visibilities.public - else: - v = Takahe.Visibilities.unlisted + v = Takahe.visibility_n2t( + comment.visibility, user.preference.mastodon_publish_public + ) existing_post = None if share_as_new_post else comment.latest_post post = Takahe.post( # TODO post as Article? comment.owner.pk, @@ -465,14 +473,9 @@ def post_review(review, share_as_new_post: bool) -> Post | None: "relatedWith": [review.ap_object], } } - if review.visibility == 1: - v = Takahe.Visibilities.followers - elif review.visibility == 2: - v = Takahe.Visibilities.mentioned - elif user.preference.mastodon_publish_public: - v = Takahe.Visibilities.public - else: - v = Takahe.Visibilities.unlisted + v = Takahe.visibility_n2t( + review.visibility, user.preference.mastodon_publish_public + ) existing_post = None if share_as_new_post else review.latest_post post = Takahe.post( # TODO post as Article? review.owner.pk, @@ -518,14 +521,9 @@ def post_mark(mark, share_as_new_post: bool) -> Post | None: data["object"]["relatedWith"].append(mark.comment.ap_object) if mark.rating: data["object"]["relatedWith"].append(mark.rating.ap_object) - if mark.visibility == 1: - v = Takahe.Visibilities.followers - elif mark.visibility == 2: - v = Takahe.Visibilities.mentioned - elif user.preference.mastodon_publish_public: - v = Takahe.Visibilities.public - else: - v = Takahe.Visibilities.unlisted + v = Takahe.visibility_n2t( + mark.visibility, user.preference.mastodon_publish_public + ) existing_post = None if share_as_new_post else mark.shelfmember.latest_post post = Takahe.post( mark.owner.pk, From a67fbc39e256abaf084a4ab671f2e2c05ca559c9 Mon Sep 17 00:00:00 2001 From: Her Email Date: Mon, 20 Nov 2023 14:15:10 -0500 Subject: [PATCH 3/8] add submodule check before docker build step --- .github/workflows/check.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index e88219ad..8895cf9a 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -28,6 +28,8 @@ jobs: python-version: ['3.11'] steps: - uses: actions/checkout@v3 + with: + submodules: 'true' - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: From a9c7318908e41059dc1e6ce7d2f1b33cc3a51a0c Mon Sep 17 00:00:00 2001 From: Her Email Date: Mon, 20 Nov 2023 14:24:28 -0500 Subject: [PATCH 4/8] remove metadata from ShelfLogEntry --- ...gentrypost_shelflogentry_posts_and_more.py | 4 ++++ journal/models/shelf.py | 21 +------------------ journal/tests.py | 2 -- 3 files changed, 5 insertions(+), 22 deletions(-) diff --git a/journal/migrations/0018_shelflogentrypost_shelflogentry_posts_and_more.py b/journal/migrations/0018_shelflogentrypost_shelflogentry_posts_and_more.py index ac8face1..d71bddce 100644 --- a/journal/migrations/0018_shelflogentrypost_shelflogentry_posts_and_more.py +++ b/journal/migrations/0018_shelflogentrypost_shelflogentry_posts_and_more.py @@ -12,6 +12,10 @@ class Migration(migrations.Migration): ] operations = [ + migrations.RemoveField( + model_name="shelflogentry", + name="metadata", + ), migrations.CreateModel( name="ShelfLogEntryPost", fields=[ diff --git a/journal/models/shelf.py b/journal/models/shelf.py index aae96c24..35318412 100644 --- a/journal/models/shelf.py +++ b/journal/models/shelf.py @@ -128,30 +128,12 @@ def comment_text(self): def tags(self): return self.mark.tags - # def get_log_entry(self): - # return ShelfLogEntry.objects.filter( - # owner=self.owner, - # item=self.item, - # shelf_type=self.shelf_type, - # timestamp=self.created_time, - # ).first() - - # def create_log_entry(self): - # return ShelfLogEntry.objects.create( - # owner=self.owner, - # shelf_type=self.shelf_type, - # item=self.item, - # metadata=self.metadata, - # timestamp=self.created_time, - # ) - def ensure_log_entry(self): log, _ = ShelfLogEntry.objects.get_or_create( owner=self.owner, shelf_type=self.shelf_type, item=self.item, timestamp=self.created_time, - defaults={"metadata": self.metadata}, ) return log @@ -192,7 +174,6 @@ class ShelfLogEntry(models.Model): shelf_type = models.CharField(choices=ShelfType.choices, max_length=100, null=True) item = models.ForeignKey(Item, on_delete=models.PROTECT) timestamp = models.DateTimeField() # this may later be changed by user - metadata = models.JSONField(default=dict) # TODO Remove this field created_time = models.DateTimeField(auto_now_add=True) edited_time = models.DateTimeField(auto_now=True) posts = models.ManyToManyField( @@ -200,7 +181,7 @@ class ShelfLogEntry(models.Model): ) def __str__(self): - return f"{self.owner}:{self.shelf_type}:{self.item.uuid}:{self.timestamp}:{self.metadata}" + return f"{self.owner}:{self.shelf_type}:{self.item.uuid}:{self.timestamp}" @property def action_label(self): diff --git a/journal/tests.py b/journal/tests.py index dee0988d..66ccbea0 100644 --- a/journal/tests.py +++ b/journal/tests.py @@ -68,8 +68,6 @@ def test_shelf(self): self.assertEqual(len(Mark(user.identity, book1).all_post_ids), 2) log = shelf_manager.get_log_for_item(book1) self.assertEqual(log.count(), 2) - last_log = log.last() - self.assertEqual(last_log.metadata if last_log else 42, {}) Mark(user.identity, book1).update(ShelfType.PROGRESS, metadata={"progress": 1}) time.sleep(0.001) self.assertEqual(q1.members.all().count(), 1) From cc048721e45a1cc04c53272ba6b9e705d01861c3 Mon Sep 17 00:00:00 2001 From: Her Email Date: Mon, 20 Nov 2023 18:08:17 -0500 Subject: [PATCH 5/8] fix type detection when fetching fedi item --- catalog/sites/fedi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/catalog/sites/fedi.py b/catalog/sites/fedi.py index 958b33ec..a2ee5632 100644 --- a/catalog/sites/fedi.py +++ b/catalog/sites/fedi.py @@ -21,6 +21,7 @@ class FediverseInstance(AbstractSite): } supported_types = { "Book": Edition, + "Edition": Edition, "Movie": Movie, "TVShow": TVShow, "TVSeason": TVSeason, From 54ecb28e6a0e3bc7ae5f8e1fa81afceb0f0bb778 Mon Sep 17 00:00:00 2001 From: Her Email Date: Mon, 20 Nov 2023 18:11:16 -0500 Subject: [PATCH 6/8] allow HEAD request for item pages --- catalog/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/catalog/views.py b/catalog/views.py index 480d86fc..dc4e3785 100644 --- a/catalog/views.py +++ b/catalog/views.py @@ -66,8 +66,6 @@ def embed(request, item_path, item_uuid): def retrieve(request, item_path, item_uuid): - if request.method != "GET": - raise BadRequest() # item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid)) item = Item.get_by_url(item_uuid) if item is None: @@ -75,13 +73,15 @@ def retrieve(request, item_path, item_uuid): item_url = f"/{item_path}/{item_uuid}" if item.url != item_url: return redirect(item.url) - if request.headers.get("Accept", "").endswith("json"): - return redirect(item.api_url) skipcheck = request.GET.get("skipcheck", False) and request.user.is_authenticated if not skipcheck and item.merged_to_item: return redirect(item.merged_to_item.url) if not skipcheck and item.is_deleted: raise Http404() + if request.headers.get("Accept", "").endswith("json"): + return redirect(item.api_url) + if request.method != "GET": + raise BadRequest() focus_item = None if request.GET.get("focus"): focus_item = get_object_or_404( From c88aa160f8136fd70c5726df5fba4086fdb0cb48 Mon Sep 17 00:00:00 2001 From: Her Email Date: Mon, 20 Nov 2023 19:11:02 -0500 Subject: [PATCH 7/8] delete from remote post --- journal/models/comment.py | 3 +++ journal/models/common.py | 12 +++++++++--- journal/models/rating.py | 3 +++ journal/models/review.py | 3 +++ journal/models/shelf.py | 10 +++++----- journal/templatetags/user_actions.py | 2 +- neodb-takahe | 2 +- social/templates/activity/mark_item.html | 10 ++++------ social/templates/feed_data.html | 2 +- takahe/ap_handlers.py | 17 +++++++++++++++-- 10 files changed, 45 insertions(+), 19 deletions(-) diff --git a/journal/models/comment.py b/journal/models/comment.py index 29453dc8..8f488fbe 100644 --- a/journal/models/comment.py +++ b/journal/models/comment.py @@ -34,6 +34,9 @@ def ap_object(self): @classmethod def update_by_ap_object(cls, owner, item, obj, post_id, visibility): + p = cls.objects.filter(owner=owner, item=item).first() + if p and p.edited_time >= datetime.fromisoformat(obj["updated"]): + return p # incoming ap object is older than what we have, no update needed content = obj.get("content", "").strip() if obj else "" if not content: cls.objects.filter(owner=owner, item=item).delete() diff --git a/journal/models/common.py b/journal/models/common.py index 53b4b5ed..8d01aae0 100644 --- a/journal/models/common.py +++ b/journal/models/common.py @@ -198,10 +198,15 @@ def clear_post_ids(self): PiecePost.objects.filter(piece=self).delete() @cached_property - def latest_post(self): - # local post id is ordered by their created time + def latest_post_id(self): + # post id is ordered by their created time pp = PiecePost.objects.filter(piece=self).order_by("-post_id").first() - return Takahe.get_post(pp.post_id) if pp else None # type: ignore + return pp.post_id if pp else None + + @cached_property + def latest_post(self): + pk = self.latest_post_id + return Takahe.get_post(pk) if pk else None @cached_property def all_post_ids(self): @@ -212,6 +217,7 @@ def all_post_ids(self): class PiecePost(models.Model): + post_id: int piece = models.ForeignKey(Piece, on_delete=models.CASCADE) post = models.ForeignKey( "takahe.Post", db_constraint=False, db_index=True, on_delete=models.CASCADE diff --git a/journal/models/rating.py b/journal/models/rating.py index bd7505ec..fe8b114e 100644 --- a/journal/models/rating.py +++ b/journal/models/rating.py @@ -39,6 +39,9 @@ def ap_object(self): @classmethod def update_by_ap_object(cls, owner, item, obj, post_id, visibility): + p = cls.objects.filter(owner=owner, item=item).first() + if p and p.edited_time >= datetime.fromisoformat(obj["updated"]): + return p # incoming ap object is older than what we have, no update needed value = obj.get("value", 0) if obj else 0 if not value: cls.objects.filter(owner=owner, item=item).delete() diff --git a/journal/models/review.py b/journal/models/review.py index aa268a6b..0a2afaa7 100644 --- a/journal/models/review.py +++ b/journal/models/review.py @@ -53,6 +53,9 @@ def ap_object(self): @classmethod def update_by_ap_object(cls, owner, item, obj, post_id, visibility): + p = cls.objects.filter(owner=owner, item=item).first() + if p and p.edited_time >= datetime.fromisoformat(obj["updated"]): + return p # incoming ap object is older than what we have, no update needed content = ( obj["content"] if obj.get("mediaType") == "text/markdown" diff --git a/journal/models/shelf.py b/journal/models/shelf.py index 35318412..58d41ba1 100644 --- a/journal/models/shelf.py +++ b/journal/models/shelf.py @@ -80,10 +80,9 @@ def ap_object(self): def update_by_ap_object( cls, owner: APIdentity, item: Identity, obj: dict, post_id: int, visibility: int ): - # TODO check timestamp? (update may come in with inconsistent sequence) - if not obj: - cls.objects.filter(owner=owner, item=item).delete() - return + p = cls.objects.filter(owner=owner, item=item).first() + if p and p.edited_time >= datetime.fromisoformat(obj["updated"]): + return p # incoming ap object is older than what we have, no update needed shelf = owner.shelf_manager.get_shelf(obj["status"]) if not shelf: logger.warning(f"unable to locate shelf for {owner}, {obj}") @@ -147,7 +146,8 @@ def log_and_delete(self): self.delete() def link_post_id(self, post_id: int): - self.ensure_log_entry().link_post_id(post_id) + if self.local: + self.ensure_log_entry().link_post_id(post_id) return super().link_post_id(post_id) diff --git a/journal/templatetags/user_actions.py b/journal/templatetags/user_actions.py index 7483d6c2..cc817319 100644 --- a/journal/templatetags/user_actions.py +++ b/journal/templatetags/user_actions.py @@ -25,7 +25,7 @@ def like_piece_action(context, piece): action = {} if user and user.is_authenticated and piece and piece.latest_post: action = { - "taken": Takahe.post_liked_by(piece.latest_post.pk, user), + "taken": Takahe.post_liked_by(piece.latest_post.pk, user.identity.pk), "url": reverse("journal:like", args=[piece.uuid]), } return action diff --git a/neodb-takahe b/neodb-takahe index a8f8f9d5..bb145fa4 160000 --- a/neodb-takahe +++ b/neodb-takahe @@ -1 +1 @@ -Subproject commit a8f8f9d5931b062d1b8bb9455db7e7f0ad79ff41 +Subproject commit bb145fa4ae8d68810ee1963e9b24b3e7623a7069 diff --git a/social/templates/activity/mark_item.html b/social/templates/activity/mark_item.html index 8a52f79a..c9c5abc3 100644 --- a/social/templates/activity/mark_item.html +++ b/social/templates/activity/mark_item.html @@ -11,12 +11,10 @@ {% load duration %} {% wish_item_action activity.action_object.item as action %} - {% if activity.action_object.mark.comment_text %} - - {% liked_piece activity.action_object.mark.comment as liked %} - {% include 'like_stats.html' with liked=liked piece=activity.action_object.mark.comment %} - - {% endif %} + + {% liked_piece activity.action_object as liked %} + {% include 'like_stats.html' with liked=liked piece=activity.action_object %} + {% comment %} diff --git a/social/templates/feed_data.html b/social/templates/feed_data.html index 94dfedb2..5c458205 100644 --- a/social/templates/feed_data.html +++ b/social/templates/feed_data.html @@ -24,7 +24,7 @@ {{ activity.owner.display_name }} - @{{ activity.owner.handler }} + {{ activity.owner.handler }} {% with "activity/"|add:activity.template|add:".html" as template %} diff --git a/takahe/ap_handlers.py b/takahe/ap_handlers.py index 029a6118..3c55b229 100644 --- a/takahe/ap_handlers.py +++ b/takahe/ap_handlers.py @@ -61,12 +61,16 @@ def _get_or_create_item(item_obj): if typ in ["TVEpisode", "PodcastEpisode"]: # TODO support episode item # match and fetch parent item first + logger.debug(f"{typ}:{url} not supported yet") return None site = SiteManager.get_site_by_url(url) if not site: + logger.warning(f"Site not found for {url}") return None site.get_resource_ready() item = site.get_item() + if not item: + logger.warning(f"Item not fetched for {url}") return item @@ -100,12 +104,21 @@ def post_fetched(pk, obj): logger.warning(f"Post {post} has no local item matched or created") return for p in pieces: - cls = _supported_ap_journal_types[p["type"]] + cls = _supported_ap_journal_types.get(p["type"]) + if not cls: + logger.warning(f'Unknown link type {p["type"]}') + continue cls.update_by_ap_object(owner, item, p, pk, _get_visibility(post.visibility)) def post_deleted(pk, obj): - Piece.objects.filter(posts__id=pk, local=False).delete() + for piece in Piece.objects.filter(posts__id=pk, local=False): + # delete piece if the deleted post is the most recent one for the piece + if piece.latest_post_id == pk: + logger.debug(f"Deleting remote piece {piece}") + piece.delete() + else: + logger.debug(f"Matched remote piece {piece} has newer posts, not deleting") def identity_fetched(pk): From 1b4a2b8f6a0ac73beab7641f7a0ba908cd4e1af5 Mon Sep 17 00:00:00 2001 From: Her Email Date: Mon, 20 Nov 2023 19:31:31 -0500 Subject: [PATCH 8/8] fix edited_time --- ...9_alter_collection_edited_time_and_more.py | 63 +++++++++++++++++++ journal/models/common.py | 4 +- journal/models/itemlist.py | 4 +- journal/models/like.py | 2 +- journal/views/collection.py | 1 - requirements.txt | 4 +- takahe/urls.py | 2 +- 7 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 journal/migrations/0019_alter_collection_edited_time_and_more.py diff --git a/journal/migrations/0019_alter_collection_edited_time_and_more.py b/journal/migrations/0019_alter_collection_edited_time_and_more.py new file mode 100644 index 00000000..8d457011 --- /dev/null +++ b/journal/migrations/0019_alter_collection_edited_time_and_more.py @@ -0,0 +1,63 @@ +# Generated by Django 4.2.7 on 2023-11-21 00:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("journal", "0018_shelflogentrypost_shelflogentry_posts_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="collection", + name="edited_time", + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name="collectionmember", + name="edited_time", + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name="comment", + name="edited_time", + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name="like", + name="edited_time", + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name="rating", + name="edited_time", + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name="review", + name="edited_time", + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name="shelf", + name="edited_time", + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name="shelfmember", + name="edited_time", + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name="tag", + name="edited_time", + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name="tagmember", + name="edited_time", + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/journal/models/common.py b/journal/models/common.py index 8d01aae0..9a72d631 100644 --- a/journal/models/common.py +++ b/journal/models/common.py @@ -235,9 +235,7 @@ class Content(Piece): default=0 ) # 0: Public / 1: Follower only / 2: Self only created_time = models.DateTimeField(default=timezone.now) - edited_time = models.DateTimeField( - default=timezone.now - ) # auto_now=True FIXME revert this after migration + edited_time = models.DateTimeField(auto_now=True) metadata = models.JSONField(default=dict) item = models.ForeignKey(Item, on_delete=models.PROTECT) remote_id = models.CharField(max_length=200, null=True, default=None) diff --git a/journal/models/itemlist.py b/journal/models/itemlist.py index f70a0497..f06c00a7 100644 --- a/journal/models/itemlist.py +++ b/journal/models/itemlist.py @@ -23,7 +23,7 @@ class List(Piece): default=0 ) # 0: Public / 1: Follower only / 2: Self only created_time = models.DateTimeField(default=timezone.now) - edited_time = models.DateTimeField(default=timezone.now) + edited_time = models.DateTimeField(auto_now=True) metadata = models.JSONField(default=dict) class Meta: @@ -148,7 +148,7 @@ class ListMember(Piece): default=0 ) # 0: Public / 1: Follower only / 2: Self only created_time = models.DateTimeField(default=timezone.now) - edited_time = models.DateTimeField(default=timezone.now) + edited_time = models.DateTimeField(auto_now=True) metadata = models.JSONField(default=dict) item = models.ForeignKey(Item, on_delete=models.PROTECT) position = models.PositiveIntegerField() diff --git a/journal/models/like.py b/journal/models/like.py index 1601edfb..6fc53499 100644 --- a/journal/models/like.py +++ b/journal/models/like.py @@ -14,7 +14,7 @@ class Like(Piece): # TODO remove default=0 ) # 0: Public / 1: Follower only / 2: Self only created_time = models.DateTimeField(default=timezone.now) - edited_time = models.DateTimeField(default=timezone.now) + edited_time = models.DateTimeField(auto_now=True) target = models.ForeignKey(Piece, on_delete=models.CASCADE, related_name="likes") @staticmethod diff --git a/journal/views/collection.py b/journal/views/collection.py index a15558e9..723d0398 100644 --- a/journal/views/collection.py +++ b/journal/views/collection.py @@ -279,7 +279,6 @@ def collection_edit(request: AuthedHttpRequest, collection_uuid=None): if form.is_valid(): if not collection: form.instance.owner = request.user.identity - form.instance.edited_time = timezone.now() form.save() return redirect( reverse("journal:collection_retrieve", args=[form.instance.uuid]) diff --git a/requirements.txt b/requirements.txt index 7b3c46cc..f2aa239e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ cachetools dateparser discord.py -django~=4.2.6 +django~=4.2.7 django-anymail -django-auditlog>=3.0.0-beta.2 +django-auditlog>=3.0.0-beta.3 django-bleach django-compressor django-cors-headers diff --git a/takahe/urls.py b/takahe/urls.py index 03b40de9..c010c8bb 100644 --- a/takahe/urls.py +++ b/takahe/urls.py @@ -2,7 +2,7 @@ from .views import * -app_name = "users" +app_name = "takahe" urlpatterns = [ path("auth/login/", auth_login, name="auth_login"), path("auth/logout/", auth_logout, name="auth_logout"),