diff --git a/books/models.py b/books/models.py index a13ec298..020c7f16 100644 --- a/books/models.py +++ b/books/models.py @@ -1,12 +1,9 @@ -import uuid import django.contrib.postgres.fields as postgres from django.utils.translation import gettext_lazy as _ from django.db import models -from django.core.serializers.json import DjangoJSONEncoder from django.shortcuts import reverse from common.models import Entity, Mark, Review, Tag from common.utils import GenerateDateUUIDMediaFilePath -from django.utils import timezone from django.conf import settings from django.db.models import Q @@ -18,9 +15,11 @@ def book_cover_path(instance, filename): class Book(Entity): # widely recognized name, usually in Chinese title = models.CharField(_("title"), max_length=200) - subtitle = models.CharField(_("subtitle"), blank=True, default='', max_length=200) + subtitle = models.CharField( + _("subtitle"), blank=True, default='', max_length=200) # original name, for books in foreign language - orig_title = models.CharField(_("original title"), blank=True, default='', max_length=200) + orig_title = models.CharField( + _("original title"), blank=True, default='', max_length=200) author = postgres.ArrayField( models.CharField(_("author"), blank=True, default='', max_length=100), @@ -29,40 +28,45 @@ class Book(Entity): default=list, ) translator = postgres.ArrayField( - models.CharField(_("translator"), blank=True, default='', max_length=100), + models.CharField(_("translator"), blank=True, + default='', max_length=100), null=True, blank=True, default=list, ) - language = models.CharField(_("language"), blank=True, default='', max_length=10) - pub_house = models.CharField(_("publishing house"), blank=True, default='', max_length=200) + language = models.CharField( + _("language"), blank=True, default='', max_length=10) + pub_house = models.CharField( + _("publishing house"), blank=True, default='', max_length=200) pub_year = models.IntegerField(_("published year"), null=True, blank=True) - pub_month = models.IntegerField(_("published month"), null=True, blank=True) - binding = models.CharField(_("binding"), blank=True, default='', max_length=50) + pub_month = models.IntegerField( + _("published month"), null=True, blank=True) + binding = models.CharField( + _("binding"), blank=True, default='', max_length=50) # since data origin is not formatted and might be CNY USD or other currency, use char instead - price = models.CharField(_("pricing"), blank=True, default='', max_length=50) + price = models.CharField(_("pricing"), blank=True, + default='', max_length=50) pages = models.PositiveIntegerField(_("pages"), null=True, blank=True) - isbn = models.CharField(_("ISBN"), blank=True, null=False, max_length=20, db_index=True, default='') - # to store previously scrapped data - cover = models.ImageField(_("cover picture"), upload_to=book_cover_path, default=settings.DEFAULT_BOOK_IMAGE, blank=True) + isbn = models.CharField(_("ISBN"), blank=True, null=False, + max_length=20, db_index=True, default='') + # to store previously scrapped data + cover = models.ImageField(_("cover picture"), upload_to=book_cover_path, + default=settings.DEFAULT_BOOK_IMAGE, blank=True) contents = models.TextField(blank=True, default="") class Meta: - # more info: https://docs.djangoproject.com/en/2.2/ref/models/options/ - # set managed=False if the model represents an existing table or - # a database view that has been created by some other means. - # check the link above for further info - # managed = True - # db_table = 'book' constraints = [ - models.CheckConstraint(check=models.Q(pub_year__gte=0), name='pub_year_lowerbound'), - models.CheckConstraint(check=models.Q(pub_month__lte=12), name='pub_month_upperbound'), - models.CheckConstraint(check=models.Q(pub_month__gte=1), name='pub_month_lowerbound'), + models.CheckConstraint(check=models.Q( + pub_year__gte=0), name='pub_year_lowerbound'), + models.CheckConstraint(check=models.Q( + pub_month__lte=12), name='pub_month_upperbound'), + models.CheckConstraint(check=models.Q( + pub_month__gte=1), name='pub_month_lowerbound'), ] def __str__(self): return self.title - + def get_json(self): r = { 'subtitle': self.subtitle, @@ -85,40 +89,59 @@ class Book(Entity): return self.book_tags def get_related_books(self): - qs = Q(orig_title = self.title) + qs = Q(orig_title=self.title) if self.isbn: - qs = qs | Q(isbn = self.isbn) + qs = qs | Q(isbn=self.isbn) if self.orig_title: - qs = qs | Q(title = self.orig_title) - qs = qs | Q(orig_title = self.orig_title) - qs = qs & ~Q(id = self.id) + qs = qs | Q(title=self.orig_title) + qs = qs | Q(orig_title=self.orig_title) + qs = qs & ~Q(id=self.id) return Book.objects.filter(qs) + def get_identicals(self): + qs = Q(orig_title=self.title) + if self.isbn: + qs = Q(isbn=self.isbn) + # qs = qs & ~Q(id=self.id) + return Book.objects.filter(qs) + else: + return [self] # Book.objects.filter(id=self.id) + @property def verbose_category_name(self): return _("书籍") class BookMark(Mark): - book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='book_marks', null=True) + book = models.ForeignKey( + Book, on_delete=models.CASCADE, related_name='book_marks', null=True) + class Meta: constraints = [ - models.UniqueConstraint(fields=['owner', 'book'], name="unique_book_mark") + models.UniqueConstraint( + fields=['owner', 'book'], name="unique_book_mark") ] class BookReview(Review): - book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='book_reviews', null=True) + book = models.ForeignKey( + Book, on_delete=models.CASCADE, related_name='book_reviews', null=True) + class Meta: constraints = [ - models.UniqueConstraint(fields=['owner', 'book'], name="unique_book_review") - ] + models.UniqueConstraint( + fields=['owner', 'book'], name="unique_book_review") + ] class BookTag(Tag): - book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='book_tags', null=True) - mark = models.ForeignKey(BookMark, on_delete=models.CASCADE, related_name='bookmark_tags', null=True) + book = models.ForeignKey( + Book, on_delete=models.CASCADE, related_name='book_tags', null=True) + mark = models.ForeignKey( + BookMark, on_delete=models.CASCADE, related_name='bookmark_tags', null=True) + class Meta: constraints = [ - models.UniqueConstraint(fields=['content', 'mark'], name="unique_bookmark_tag") + models.UniqueConstraint( + fields=['content', 'mark'], name="unique_bookmark_tag") ] diff --git a/books/templates/books/detail.html b/books/templates/books/detail.html index 042e3290..937e455e 100644 --- a/books/templates/books/detail.html +++ b/books/templates/books/detail.html @@ -155,9 +155,7 @@
{{ others_mark.text }}
{% endif %} @@ -196,6 +197,9 @@ {% endif %} {{ others_review.edited_time }} + {% if others_review.book != book %} + {{ others_review.book.get_source_site_display }} + {% endif %} {{ others_review.title }} {{ others_review.get_plain_content | truncate:100 }} diff --git a/books/templates/books/mark_list.html b/books/templates/books/mark_list.html index ecc819e0..15ae7892 100644 --- a/books/templates/books/mark_list.html +++ b/books/templates/books/mark_list.html @@ -53,6 +53,10 @@ {% endif %} {{ mark.edited_time }} + {% if mark.book != book %} + {{ mark.book.get_source_site_display }} + {% endif %} + {% if mark.text %}{{ mark.text }}
{% endif %} diff --git a/books/templates/books/review_list.html b/books/templates/books/review_list.html index 2ec2ef51..f395f9f8 100644 --- a/books/templates/books/review_list.html +++ b/books/templates/books/review_list.html @@ -44,7 +44,9 @@ {% endif %} {{ review.edited_time }} - + {% if review.book != book %} + {{ review.book.get_source_site_display }} + {% endif %} {{ review.title }} diff --git a/books/views.py b/books/views.py index 219afb99..c316ce85 100644 --- a/books/views.py +++ b/books/views.py @@ -186,8 +186,8 @@ def retrieve(request, id): mark_list_more = None review_list_more = None else: - mark_list = BookMark.get_available(book, request.user) - review_list = BookReview.get_available(book, request.user) + mark_list = BookMark.get_available_for_identicals(book, request.user) + review_list = BookReview.get_available_for_identicals(book, request.user) mark_list_more = True if len(mark_list) > MARK_NUMBER else False mark_list = mark_list[:MARK_NUMBER] for m in mark_list: @@ -284,7 +284,7 @@ def create_update_mark(request): form.instance.owner = request.user form.instance.edited_time = timezone.now() book = form.instance.book - + try: with transaction.atomic(): # update book rating @@ -340,7 +340,7 @@ def create_update_mark(request): def retrieve_mark_list(request, book_id, following_only=False): if request.method == 'GET': book = get_object_or_404(Book, pk=book_id) - queryset = BookMark.get_available(book, request.user, following_only=following_only) + queryset = BookMark.get_available_for_identicals(book, request.user, following_only=following_only) paginator = Paginator(queryset, MARK_PER_PAGE) page_number = request.GET.get('page', default=1) marks = paginator.get_page(page_number) @@ -546,7 +546,7 @@ def retrieve_review(request, id): def retrieve_review_list(request, book_id): if request.method == 'GET': book = get_object_or_404(Book, pk=book_id) - queryset = BookReview.get_available(book, request.user) + queryset = BookReview.get_available_for_identicals(book, request.user) paginator = Paginator(queryset, REVIEW_PER_PAGE) page_number = request.GET.get('page', default=1) reviews = paginator.get_page(page_number) diff --git a/common/models.py b/common/models.py index a2ca383c..15035746 100644 --- a/common/models.py +++ b/common/models.py @@ -203,12 +203,20 @@ class UserOwnedEntity(models.Model): @classmethod def get_available(cls, entity, request_user, following_only=False): - # e.g. SongMark.get_available(song, request.user, request.user.mastodon_token) + # e.g. SongMark.get_available(song, request.user) query_kwargs = {entity.__class__.__name__.lower(): entity} all_entities = cls.objects.filter(**query_kwargs).order_by("-edited_time") # get all marks for song visible_entities = list(filter(lambda _entity: _entity.is_visible_to(request_user) and (_entity.owner.mastodon_username in request_user.mastodon_following if following_only else True), all_entities)) return visible_entities + @classmethod + def get_available_for_identicals(cls, entity, request_user, following_only=False): + # e.g. SongMark.get_available(song, request.user) + query_kwargs = {entity.__class__.__name__.lower() + '__in': entity.get_identicals()} + all_entities = cls.objects.filter(**query_kwargs).order_by("-edited_time") # get all marks for song + visible_entities = list(filter(lambda _entity: _entity.is_visible_to(request_user) and (_entity.owner.mastodon_username in request_user.mastodon_following if following_only else True), all_entities)) + return visible_entities + @classmethod def get_available_by_user(cls, owner, is_following): # FIXME """