rewrite Mark.update()
This commit is contained in:
parent
0d5613a100
commit
cf4d5525b5
9 changed files with 251 additions and 107 deletions
|
@ -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"}
|
||||
|
||||
|
||||
|
|
|
@ -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"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue