From 24356559954adc7e4ae3871c0542e1925888a26d Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 4 Feb 2024 18:41:34 -0500 Subject: [PATCH] add debris model for future features --- catalog/migrations/0003_podcast.py | 7 +++- journal/migrations/0023_debris.py | 61 ++++++++++++++++++++++++++++++ journal/models/collection.py | 14 +++++++ journal/models/common.py | 18 ++++++++- journal/models/tag.py | 13 +++++++ journal/models/utils.py | 35 ++++++++++------- journal/tests.py | 33 ++++++++++++++++ 7 files changed, 165 insertions(+), 16 deletions(-) create mode 100644 journal/migrations/0023_debris.py diff --git a/catalog/migrations/0003_podcast.py b/catalog/migrations/0003_podcast.py index 95ce31b8..e61cdc9d 100644 --- a/catalog/migrations/0003_podcast.py +++ b/catalog/migrations/0003_podcast.py @@ -114,7 +114,12 @@ class Migration(migrations.Migration): ("cover_url", models.CharField(max_length=1000, null=True)), ("media_url", models.CharField(max_length=1000, null=True)), ("guid", models.CharField(max_length=1000, null=True)), - ("pub_date", models.DateTimeField()), + ( + "pub_date", + models.DateTimeField( + help_text="yyyy/mm/dd hh:mm", verbose_name="发布时间" + ), + ), ("duration", models.PositiveIntegerField(null=True)), ( "program", diff --git a/journal/migrations/0023_debris.py b/journal/migrations/0023_debris.py new file mode 100644 index 00000000..9a3c7f22 --- /dev/null +++ b/journal/migrations/0023_debris.py @@ -0,0 +1,61 @@ +# Generated by Django 4.2.9 on 2024-02-04 22:37 + +import django.db.models.deletion +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("users", "0019_task"), + ("catalog", "0011_alter_externalresource_id_type_and_more"), + ("journal", "0022_letterboxdimporter"), + ] + + operations = [ + migrations.CreateModel( + name="Debris", + fields=[ + ( + "piece_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="journal.piece", + ), + ), + ("visibility", models.PositiveSmallIntegerField(default=0)), + ( + "created_time", + models.DateTimeField(default=django.utils.timezone.now), + ), + ("edited_time", models.DateTimeField(auto_now=True)), + ("metadata", models.JSONField(default=dict)), + ( + "remote_id", + models.CharField(default=None, max_length=200, null=True), + ), + ("class_name", models.CharField(max_length=50)), + ( + "item", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="catalog.item" + ), + ), + ( + "owner", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="users.apidentity", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("journal.piece",), + ), + ] diff --git a/journal/models/collection.py b/journal/models/collection.py index cc028290..4b260e15 100644 --- a/journal/models/collection.py +++ b/journal/models/collection.py @@ -24,6 +24,20 @@ class CollectionMember(ListMember): note = jsondata.CharField(_("备注"), null=True, blank=True) + @property + def ap_object(self): + return { + "id": self.absolute_url, + "type": "CollectionItem", + "collection": self.parent.absolute_url, + "published": self.created_time.isoformat(), + "updated": self.edited_time.isoformat(), + "attributedTo": self.owner.actor_uri, + "withRegardTo": self.item.absolute_url, + "note": self.note, + "href": self.absolute_url, + } + class Collection(List): url_path = "collection" diff --git a/journal/models/common.py b/journal/models/common.py index 2af033d3..06679d0d 100644 --- a/journal/models/common.py +++ b/journal/models/common.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from django.conf import settings from django.db import connection, models -from django.db.models import Avg, Count, Q +from django.db.models import Avg, CharField, Count, Q from django.utils import timezone from django.utils.baseconv import base62 from django.utils.translation import gettext_lazy as _ @@ -264,3 +264,19 @@ class Content(Piece): class Meta: abstract = True + + +class Debris(Content): + class_name = CharField(max_length=50) + + @classmethod + def create_from_piece(cls, c: Piece): + return cls.objects.create( + class_name=c.__class__.__name__, + owner=c.owner, + visibility=c.visibility, + created_time=c.created_time, + metadata=c.ap_object, + item=c.item, + remote_id=c.remote_id if hasattr(c, "remote_id") else None, + ) diff --git a/journal/models/tag.py b/journal/models/tag.py index 28d43adc..a1f5959a 100644 --- a/journal/models/tag.py +++ b/journal/models/tag.py @@ -19,6 +19,19 @@ class TagMember(ListMember): class Meta: unique_together = [["parent", "item"]] + @property + def ap_object(self): + return { + "id": self.absolute_url, + "type": "Tag", + "tag": self.parent.title, + "published": self.created_time.isoformat(), + "updated": self.edited_time.isoformat(), + "attributedTo": self.owner.actor_uri, + "withRegardTo": self.item.absolute_url, + "href": self.absolute_url, + } + TagValidators = [RegexValidator(regex=r"\s+", inverse_match=True)] diff --git a/journal/models/utils.py b/journal/models/utils.py index 0ab8b4b0..bb109ae7 100644 --- a/journal/models/utils.py +++ b/journal/models/utils.py @@ -1,3 +1,5 @@ +from django.db import transaction +from django.db.utils import IntegrityError from django.utils.translation import gettext_lazy as _ from loguru import logger @@ -6,7 +8,7 @@ from users.models import APIdentity from .collection import Collection, CollectionMember, FeaturedCollection from .comment import Comment -from .common import Content +from .common import Content, Debris from .itemlist import ListMember from .rating import Rating from .review import Review @@ -42,21 +44,26 @@ def update_journal_for_merged_item( logger.error("update_journal_for_merged_item: unable to find item") return new_item = legacy_item.merged_to_item + delete_q = [] for cls in list(Content.__subclasses__()) + list(ListMember.__subclasses__()): for p in cls.objects.filter(item=legacy_item): - try: - p.item = new_item - p.save(update_fields=["item_id"]) - except: - if delete_duplicated: - logger.warning( - f"deleted piece {p} when merging {cls.__name__}: {legacy_item} -> {new_item}" - ) - p.delete() - else: - logger.warning( - f"skip piece {p} when merging {cls.__name__}: {legacy_item} -> {new_item}" - ) + with transaction.atomic(): + try: + p.item = new_item + p.save(update_fields=["item_id"]) + except IntegrityError: + if delete_duplicated: + logger.warning( + f"deleted piece {p.id} when merging {cls.__name__}: {legacy_item_uuid} -> {new_item.uuid}" + ) + delete_q.append(p) + else: + logger.warning( + f"skip piece {p.id} when merging {cls.__name__}: {legacy_item_uuid} -> {new_item.uuid}" + ) + for p in delete_q: + Debris.create_from_piece(p) + p.delete() def journal_exists_for_item(item: Item) -> bool: diff --git a/journal/tests.py b/journal/tests.py index 1cf8caaa..e2643598 100644 --- a/journal/tests.py +++ b/journal/tests.py @@ -3,6 +3,7 @@ import time from django.test import TestCase from catalog.models import * +from journal.models.common import Debris from users.models import User from .models import * @@ -188,3 +189,35 @@ class MarkTest(TestCase): TagManager.tag_item(self.book1, self.user1.identity, [" Sci-Fi ", " fic "]) mark = Mark(self.user1.identity, self.book1) self.assertEqual(mark.tags, ["Sci-Fi", "fic"]) + + +class DebrisTest(TestCase): + databases = "__all__" + + def setUp(self): + self.book1 = Edition.objects.create(title="Hyperion") + self.book2 = Edition.objects.create(title="Hyperion clone") + self.book3 = Edition.objects.create(title="Hyperion clone 2") + self.user1 = User.register(email="test@test", username="test") + + def test_journal_migration(self): + TagManager.tag_item(self.book1, self.user1.identity, ["Sci-Fi", "fic"]) + mark = Mark(self.user1.identity, self.book1) + mark.update(ShelfType.WISHLIST, "a gentle comment", 9, 1) + Review.update_item_review(self.book1, self.user1.identity, "Critic", "Review") + collection = Collection.objects.create(title="test", owner=self.user1.identity) + collection.append_item(self.book1) + self.book1.merge_to(self.book2) + update_journal_for_merged_item(self.book1.uuid, delete_duplicated=True) + cnt = Debris.objects.all().count() + self.assertEqual(cnt, 0) + + TagManager.tag_item(self.book3, self.user1.identity, ["Sci-Fi", "fic"]) + mark = Mark(self.user1.identity, self.book3) + mark.update(ShelfType.WISHLIST, "a gentle comment", 9, 1) + Review.update_item_review(self.book3, self.user1.identity, "Critic", "Review") + collection.append_item(self.book3) + self.book3.merge_to(self.book2) + update_journal_for_merged_item(self.book3.uuid, delete_duplicated=True) + cnt = Debris.objects.all().count() + self.assertEqual(cnt, 4) # Rating, Shelf, 2x TagMember