rewrite Mark.update()

This commit is contained in:
Her Email 2023-11-20 01:59:26 -05:00 committed by Henri Dickson
parent 0d5613a100
commit cf4d5525b5
9 changed files with 251 additions and 107 deletions

View file

@ -10,7 +10,7 @@ from oauth2_provider.decorators import protected_resource
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"}

View file

@ -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"
),
),
]

View file

@ -197,6 +197,9 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
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

View file

@ -20,7 +20,7 @@ from catalog.collection.models import Collection as CatalogCollection
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 @@ class Mark:
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 @@ class Mark:
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"
)

View file

@ -8,7 +8,7 @@ from django.utils.translation import gettext_lazy as _
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 @@ class ShelfMember(ListMember):
"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 @@ class ShelfMember(ListMember):
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 @@ class ShelfLogEntry(models.Model):
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 @@ class ShelfManager:
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,

View file

@ -13,7 +13,7 @@ from django.utils.translation import gettext_lazy as _
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()

View file

@ -14,7 +14,7 @@ from django.utils.translation import gettext_lazy as _
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()

View file

@ -5,6 +5,7 @@ import re
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,

View file

@ -438,7 +438,7 @@ class Takahe:
)
if not post:
return
comment.link_post(post)
comment.link_post_id(post.pk)
return post
@staticmethod
@ -485,7 +485,7 @@ class Takahe:
)
if not post:
return
review.link_post(post)
review.link_post_id(post.pk)
return post
@staticmethod
@ -540,7 +540,7 @@ class Takahe:
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