From 8e0f566ae9b28708c6a82c8482619ead0a853429 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Wed, 21 Oct 2026 13:11:27 +0330 Subject: [PATCH] feat(references): add book priority, edition title, and many-to-many works linking --- ...ence_options_bookedition_books_and_more.py | 42 +++++++++++++++++++ .../0067_populate_book_edition_books.py | 19 +++++++++ apps/hadis/models/reference.py | 28 ++++++++++++- apps/hadis/serializers/serializers_admin.py | 30 +++++++++++++ apps/hadis/views_admin.py | 17 ++++++-- 5 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 apps/hadis/migrations/0066_alter_bookreference_options_bookedition_books_and_more.py create mode 100644 apps/hadis/migrations/0067_populate_book_edition_books.py diff --git a/apps/hadis/migrations/0066_alter_bookreference_options_bookedition_books_and_more.py b/apps/hadis/migrations/0066_alter_bookreference_options_bookedition_books_and_more.py new file mode 100644 index 0000000..9cd44fb --- /dev/null +++ b/apps/hadis/migrations/0066_alter_bookreference_options_bookedition_books_and_more.py @@ -0,0 +1,42 @@ +# Generated by Django 4.2.30 on 2026-10-21 12:54 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('hadis', '0065_country_and_publisher_models'), + ] + + operations = [ + migrations.AlterModelOptions( + name='bookreference', + options={'ordering': ('priority', 'id'), 'verbose_name': 'Book Reference', 'verbose_name_plural': 'Book References'}, + ), + migrations.AddField( + model_name='bookedition', + name='books', + field=models.ManyToManyField(blank=True, related_name='editions_list', to='hadis.bookreference', verbose_name='Connected Books/Works'), + ), + migrations.AddField( + model_name='bookedition', + name='title', + field=models.JSONField(blank=True, default=list, verbose_name='Edition Title'), + ), + migrations.AddField( + model_name='bookreference', + name='priority', + field=models.PositiveIntegerField(db_index=True, default=0, help_text='Display order priority. Lower numbers appear first.', verbose_name='Priority'), + ), + migrations.AlterField( + model_name='bookedition', + name='book_reference', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='editions', to='hadis.bookreference', verbose_name='book reference'), + ), + migrations.AddIndex( + model_name='bookreference', + index=models.Index(fields=['priority'], name='hadis_bookr_priorit_74ae96_idx'), + ), + ] diff --git a/apps/hadis/migrations/0067_populate_book_edition_books.py b/apps/hadis/migrations/0067_populate_book_edition_books.py new file mode 100644 index 0000000..a790ab7 --- /dev/null +++ b/apps/hadis/migrations/0067_populate_book_edition_books.py @@ -0,0 +1,19 @@ +from django.db import migrations + +def populate_books(apps, schema_editor): + BookEdition = apps.get_model('hadis', 'BookEdition') + for edition in BookEdition.objects.filter(book_reference__isnull=False): + edition.books.add(edition.book_reference) + +def reverse_populate(apps, schema_editor): + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('hadis', '0066_alter_bookreference_options_bookedition_books_and_more'), + ] + + operations = [ + migrations.RunPython(populate_books, reverse_populate), + ] diff --git a/apps/hadis/models/reference.py b/apps/hadis/models/reference.py index d75fea3..a5332fe 100644 --- a/apps/hadis/models/reference.py +++ b/apps/hadis/models/reference.py @@ -127,6 +127,12 @@ class BookReference(LowercaseSlugMixin, models.Model): verbose_name=_('Author') ) order = models.IntegerField(default=0, verbose_name=_('Order')) + priority = models.PositiveIntegerField( + default=0, + db_index=True, + verbose_name=_('Priority'), + help_text=_('Display order priority. Lower numbers appear first.') + ) rate = models.DecimalField( max_digits=3, decimal_places=2, @@ -148,10 +154,11 @@ class BookReference(LowercaseSlugMixin, models.Model): class Meta: indexes = [ models.Index(fields=['id']), # Already indexed (PK) + models.Index(fields=['priority']), ] verbose_name = _('Book Reference') verbose_name_plural = _('Book References') - ordering = ('-created_at',) + ordering = ('priority', 'id') def __str__(self): if self.title and isinstance(self.title, list) and len(self.title) > 0 and isinstance(self.title[0], dict): @@ -292,7 +299,14 @@ class BookEdition(models.Model): """ Model representing a specific edition or print of a book reference. """ - book_reference = models.ForeignKey(BookReference, on_delete=models.CASCADE, related_name='editions', verbose_name=_('book reference')) + title = models.JSONField(default=list, blank=True, verbose_name=_('Edition Title')) + book_reference = models.ForeignKey(BookReference, on_delete=models.CASCADE, related_name='editions', verbose_name=_('book reference'), null=True, blank=True) + books = models.ManyToManyField( + BookReference, + related_name='editions_list', + blank=True, + verbose_name=_('Connected Books/Works') + ) publisher = models.JSONField(default=list, blank=True, verbose_name=_('Publisher')) publisher_item = models.ForeignKey( 'hadis.BookPublisher', @@ -344,12 +358,22 @@ class BookEdition(models.Model): ordering = ['-created_at'] def __str__(self): + t_str = "" + if self.title and isinstance(self.title, list): + for item in self.title: + if isinstance(item, dict) and (item.get('text') or item.get('title')): + t_str = (item.get('text') or item.get('title')).strip() + break + if t_str: + return f"{t_str} ({self.edition_number or 'Ed. ' + str(self.id)})" return f"{self.book_reference} - {self.edition_number or 'Edition ' + str(self.id)}" def save(self, *args, **kwargs): if self.city_of_publication_city_id and self.city_of_publication_city: self.city_of_publication = self.city_of_publication_city.title or [] super().save(*args, **kwargs) + if self.book_reference and not self.books.filter(id=self.book_reference_id).exists(): + self.books.add(self.book_reference) class BookVolume(models.Model): """ diff --git a/apps/hadis/serializers/serializers_admin.py b/apps/hadis/serializers/serializers_admin.py index beec26a..909f5d4 100644 --- a/apps/hadis/serializers/serializers_admin.py +++ b/apps/hadis/serializers/serializers_admin.py @@ -2902,6 +2902,7 @@ class AdminBookReferenceListSerializer(serializers.ModelSerializer): "title", "slug", "has_editions", + "priority", "author_id", "author_name", "tag_id", @@ -3123,6 +3124,7 @@ class AdminBookReferenceDetailSerializer(serializers.ModelSerializer): "tag", "tag_detail", "order", + "priority", "city_of_publication", "city_of_publication_city", "city_of_publication_city_detail", @@ -3718,6 +3720,12 @@ class AdminBookEditorSerializer(serializers.ModelSerializer): class AdminBookEditionSerializer(serializers.ModelSerializer): + title = serializers.JSONField(required=False, default=list) + books = serializers.PrimaryKeyRelatedField( + queryset=BookReference.objects.all(), + many=True, + required=False, + ) publisher = serializers.JSONField(required=False, default=list) city_of_publication = serializers.JSONField(required=False, default=list) publisher_item = serializers.PrimaryKeyRelatedField( @@ -3757,7 +3765,10 @@ class AdminBookEditionSerializer(serializers.ModelSerializer): model = BookEdition fields = [ "id", + "title", "book_reference", + "books", + "books_detail", "publisher", "publisher_item", "publisher_item_detail", @@ -3791,6 +3802,15 @@ class AdminBookEditionSerializer(serializers.ModelSerializer): ed_obj = instance.editors.first() ret["editor_author_id"] = ed_obj.author_id if ed_obj and ed_obj.author_id else None + ret["books"] = list(instance.books.values_list('id', flat=True)) + ret["books_detail"] = [ + { + "id": b.id, + "title": b.title, + "slug": b.slug, + } + for b in instance.books.all() + ] return ret def get_number_of_volumes(self, obj): @@ -3835,6 +3855,7 @@ class AdminBookEditionSerializer(serializers.ModelSerializer): pass def create(self, validated_data): + books_data = validated_data.pop("books", None) researchers_data = validated_data.pop("researchers", []) editors_data = validated_data.pop("editors", []) has_res_id = "researcher_author_id" in validated_data @@ -3852,9 +3873,14 @@ class AdminBookEditionSerializer(serializers.ModelSerializer): if has_ed_id: author_links_dict["editor_author_id"] = ed_author_id self._handle_author_links(edition, author_links_dict) + if books_data is not None: + edition.books.set(books_data) + if edition.book_reference and not edition.books.filter(id=edition.book_reference_id).exists(): + edition.books.add(edition.book_reference) return edition def update(self, instance, validated_data): + books_data = validated_data.pop("books", None) researchers_data = validated_data.pop("researchers", None) editors_data = validated_data.pop("editors", None) has_res_id = "researcher_author_id" in validated_data @@ -3883,6 +3909,10 @@ class AdminBookEditionSerializer(serializers.ModelSerializer): # Delete editors no longer in the request payload keep_ids = [e.get("id") for e in editors_data if e.get("id")] instance.editors.exclude(id__in=keep_ids).delete() + if books_data is not None: + edition.books.set(books_data) + if edition.book_reference and not edition.books.filter(id=edition.book_reference_id).exists(): + edition.books.add(edition.book_reference) for ed_data in editors_data: ed_id = ed_data.get("id") if ed_id: diff --git a/apps/hadis/views_admin.py b/apps/hadis/views_admin.py index be6ae80..7e6e62f 100644 --- a/apps/hadis/views_admin.py +++ b/apps/hadis/views_admin.py @@ -2262,7 +2262,10 @@ class AdminBookReferenceViewSet(ModelViewSet): - return queryset.distinct().order_by("-created_at") + ordering = self.request.query_params.get("ordering") + if ordering: + return queryset.distinct().order_by(ordering) + return queryset.distinct().order_by("priority", "-created_at") @@ -2568,13 +2571,21 @@ class AdminBookEditionViewSet(ModelViewSet): def get_queryset(self): - queryset = BookEdition.objects.all() + queryset = BookEdition.objects.all().prefetch_related("books", "volumes") book_reference_id = self.request.query_params.get("book_reference_id") if book_reference_id: + from django.db.models import Q + queryset = queryset.filter(Q(book_reference_id=book_reference_id) | Q(books__id=book_reference_id)).distinct() - queryset = queryset.filter(book_reference_id=book_reference_id) + search_query = self.request.query_params.get("search") + if search_query: + from django.db.models import Q + queryset = queryset.filter( + Q(edition_number__icontains=search_query) | + Q(isbn__icontains=search_query) + ) return queryset.order_by("-created_at")