From c2615cb43892d95e5c86bb7cd19d075fe51b1818 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Mon, 19 Oct 2026 17:37:27 +0330 Subject: [PATCH] feat(reference): support canonical dates and BookAuthorDate model for book authors - Convert birth_year_hijri and death_year_hijri to CharField and add age_at_death - Introduce BookAuthorDate model linking BookAuthor with parsed YearValue records - Add sync_author_dates and date formatting methods to BookAuthor - Support century filtering via YearValue min/max ranges and prefetch author dates - Expose live preview fields and canonical date validation in AdminBookAuthorSerializer - Add migration 0056 to convert fields and populate BookAuthorDate records --- ...okauthor_date_fields_and_bookauthordate.py | 117 ++++++++++++++ apps/hadis/models/reference.py | 144 +++++++++++++++++- apps/hadis/serializers/reference_v2.py | 84 +++++++++- apps/hadis/serializers/serializers_admin.py | 65 +++++++- apps/hadis/views/reference_v2.py | 46 +++++- apps/hadis/views_admin.py | 7 +- 6 files changed, 443 insertions(+), 20 deletions(-) create mode 100644 apps/hadis/migrations/0056_bookauthor_date_fields_and_bookauthordate.py diff --git a/apps/hadis/migrations/0056_bookauthor_date_fields_and_bookauthordate.py b/apps/hadis/migrations/0056_bookauthor_date_fields_and_bookauthordate.py new file mode 100644 index 0000000..9a55eb7 --- /dev/null +++ b/apps/hadis/migrations/0056_bookauthor_date_fields_and_bookauthordate.py @@ -0,0 +1,117 @@ +# Generated by Django 4.2.30 on 2026-10-19 17:08 + +from django.db import migrations, models +import django.db.models.deletion + + +def populate_book_author_dates(apps, schema_editor): + BookAuthor = apps.get_model('hadis', 'BookAuthor') + YearValue = apps.get_model('hadis', 'YearValue') + BookAuthorDate = apps.get_model('hadis', 'BookAuthorDate') + + from apps.hadis.utils.date_engine import ( + parse_canonical_date, + build_translations_for_parsed, + ) + + yv_cache = {} + for author in BookAuthor.objects.all().iterator(): + for date_type, raw_val in ( + ('birth', author.birth_year_hijri), + ('death', author.death_year_hijri), + ('age', getattr(author, 'age_at_death', None)), + ): + if raw_val is None or str(raw_val).strip() == '': + continue + raw_str = str(raw_val).strip() + parsed = parse_canonical_date(raw_str, category=date_type) + canonical_str = parsed.raw_value if parsed.is_valid else raw_str + + cache_key = (date_type, canonical_str) + yv = yv_cache.get(cache_key) + if not yv: + yv = YearValue.objects.filter(category=date_type, raw_value=canonical_str).first() + if not yv: + if parsed.is_valid: + translations = build_translations_for_parsed(parsed, category=date_type) + else: + translations = [ + {"language_code": "ru", "text": canonical_str}, + {"language_code": "en", "text": canonical_str}, + {"language_code": "ar", "text": canonical_str}, + ] + yv = YearValue.objects.create( + category=date_type, + raw_value=canonical_str, + parse_type=parsed.parse_type if parsed.is_valid else 'unparsed', + value=parsed.value if parsed.is_valid else None, + value_min=parsed.value_min if parsed.is_valid else None, + value_max=parsed.value_max if parsed.is_valid else None, + values_list=parsed.values_list if parsed.is_valid else [], + translations=translations, + ) + yv_cache[cache_key] = yv + + BookAuthorDate.objects.update_or_create( + author_id=author.id, + date_type=date_type, + defaults={ + 'year_value_id': yv.id, + 'raw_value': yv.raw_value, + } + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('hadis', '0055_yearvalue_narratordate_and_transmitters_date_fields'), + ] + + operations = [ + migrations.AlterField( + model_name='bookauthor', + name='birth_year_hijri', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Birth Year (Hijri)'), + ), + migrations.AlterField( + model_name='bookauthor', + name='death_year_hijri', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Death Year (Hijri)'), + ), + migrations.AddField( + model_name='bookauthor', + name='age_at_death', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Age at Death'), + ), + migrations.CreateModel( + name='BookAuthorDate', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_type', models.CharField(choices=[('birth', 'Birth'), ('death', 'Death'), ('age', 'Age')], db_index=True, max_length=20, verbose_name='Date Type')), + ('raw_value', models.CharField(max_length=100, verbose_name='Canonical Raw Value')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='author_dates', to='hadis.bookauthor', verbose_name='Book Author')), + ('year_value', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='author_dates', to='hadis.yearvalue', verbose_name='Year Value')), + ], + options={ + 'verbose_name': 'Book Author Date', + 'verbose_name_plural': 'Book Author Dates', + 'ordering': ('author_id', 'date_type'), + 'unique_together': {('author', 'date_type')}, + }, + ), + migrations.AddIndex( + model_name='bookauthordate', + index=models.Index(fields=['date_type', 'year_value'], name='hadis_booka_date_ty_9a1e3c_idx'), + ), + migrations.AddIndex( + model_name='bookauthordate', + index=models.Index(fields=['author', 'date_type'], name='hadis_booka_author__6c2f1b_idx'), + ), + migrations.RunPython( + populate_book_author_dates, + reverse_code=migrations.RunPython.noop, + ), + ] diff --git a/apps/hadis/models/reference.py b/apps/hadis/models/reference.py index feb0a98..cedebaf 100644 --- a/apps/hadis/models/reference.py +++ b/apps/hadis/models/reference.py @@ -469,8 +469,9 @@ class BookAuthor(LowercaseSlugMixin, models.Model): ) features = models.JSONField(default=list, blank=True, verbose_name=_('Features')) - birth_year_hijri = models.IntegerField(verbose_name=_("Birth Year (Hijri)"), null=True, blank=True) - death_year_hijri = models.IntegerField(verbose_name=_("Death Year (Hijri)"), null=True, blank=True) + birth_year_hijri = models.CharField(max_length=100, verbose_name=_("Birth Year (Hijri)"), null=True, blank=True) + death_year_hijri = models.CharField(max_length=100, verbose_name=_("Death Year (Hijri)"), null=True, blank=True) + age_at_death = models.CharField(max_length=100, verbose_name=_("Age at Death"), null=True, blank=True) birth_year_miladi = models.IntegerField(verbose_name=_("Birth Year (Miladi)"), null=True, blank=True) death_year_miladi = models.IntegerField(verbose_name=_("Death Year (Miladi)"), null=True, blank=True) @@ -513,8 +514,146 @@ class BookAuthor(LowercaseSlugMixin, models.Model): def save(self, *args, **kwargs): if self.rutba_status_id and self.rutba_status: self.rutba = self.rutba_status.title or [] + + for date_field in ('birth_year_hijri', 'death_year_hijri', 'age_at_death'): + val = getattr(self, date_field, None) + if val is not None: + val_str = str(val).strip() + setattr(self, date_field, val_str if val_str else None) + super().save(*args, **kwargs) self.researcher_profiles.all().update(name=self.name, slug=self.slug) + try: + self.sync_author_dates() + except Exception: + pass + + def sync_author_dates(self): + """ + Synchronizes birth_year_hijri, death_year_hijri, and age_at_death + with YearValue and BookAuthorDate tables. + """ + if not self.pk: + return + + from .transmitter import YearValue + + date_mapping = { + 'birth': self.birth_year_hijri, + 'death': self.death_year_hijri, + 'age': self.age_at_death, + } + + for date_type, raw_val in date_mapping.items(): + if not raw_val or not str(raw_val).strip(): + BookAuthorDate.objects.filter(author=self, date_type=date_type).delete() + continue + + raw_str = str(raw_val).strip() + yv = YearValue.get_or_create_from_canonical(category=date_type, raw_value=raw_str) + BookAuthorDate.objects.update_or_create( + author=self, + date_type=date_type, + defaults={ + 'year_value': yv, + 'raw_value': yv.raw_value, + } + ) + + def get_formatted_date(self, date_type: str, lang: str = 'ru') -> Optional[str]: + field_map = { + 'birth': 'birth_year_hijri', + 'death': 'death_year_hijri', + 'age': 'age_at_death', + } + field_name = field_map.get(date_type) + if not field_name: + return None + raw_val = getattr(self, field_name, None) + if raw_val is None or str(raw_val).strip() == '': + return None + raw_str = str(raw_val).strip() + + target_lang = (lang or 'ru').split('-')[0].lower() + + # Check prefetched author_dates first if available + if hasattr(self, '_prefetched_objects_cache') and 'author_dates' in self._prefetched_objects_cache: + for ad in self._prefetched_objects_cache['author_dates']: + if ad.date_type == date_type and ad.year_value_id and ad.year_value: + txt = ad.year_value.get_translation(target_lang) + if txt: + return txt + + # If context tag (#...), check YearValue in DB for custom translations + if raw_str.startswith('#'): + from .transmitter import YearValue + yv = YearValue.objects.filter(raw_value=raw_str).first() + if yv: + txt = yv.get_translation(target_lang) + if txt: + return txt + + from ..utils.date_engine import format_canonical_date + engine_lang = target_lang if target_lang in ('ru', 'en', 'ar') else 'en' + return format_canonical_date(raw_str, category=date_type, lang=engine_lang) + + def get_formatted_birth_year(self, lang: str = 'ru') -> Optional[str]: + return self.get_formatted_date('birth', lang=lang) + + def get_formatted_death_year(self, lang: str = 'ru') -> Optional[str]: + return self.get_formatted_date('death', lang=lang) + + def get_formatted_age(self, lang: str = 'ru') -> Optional[str]: + return self.get_formatted_date('age', lang=lang) + + +class BookAuthorDate(models.Model): + """ + Links a BookAuthor to a parsed YearValue for birth, death, or age. + """ + class DateTypeChoices(models.TextChoices): + BIRTH = 'birth', _('Birth') + DEATH = 'death', _('Death') + AGE = 'age', _('Age') + + author = models.ForeignKey( + BookAuthor, + on_delete=models.CASCADE, + related_name='author_dates', + verbose_name=_('Book Author') + ) + date_type = models.CharField( + max_length=20, + choices=DateTypeChoices.choices, + db_index=True, + verbose_name=_('Date Type') + ) + year_value = models.ForeignKey( + 'hadis.YearValue', + on_delete=models.CASCADE, + related_name='author_dates', + verbose_name=_('Year Value') + ) + raw_value = models.CharField( + max_length=100, + verbose_name=_('Canonical Raw Value') + ) + + created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) + updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) + + class Meta: + verbose_name = _('Book Author Date') + verbose_name_plural = _('Book Author Dates') + unique_together = ('author', 'date_type') + indexes = [ + models.Index(fields=['date_type', 'year_value']), + models.Index(fields=['author', 'date_type']), + ] + ordering = ('author_id', 'date_type') + + def __str__(self): + return f"{self.author_id} - {self.date_type}: {self.raw_value}" class BookAttribute(models.Model): @@ -649,4 +788,3 @@ class BookEditor(LowercaseSlugMixin, models.Model): return self.name[0]['text'] except: return self.slug or f"Editor {self.id}" - diff --git a/apps/hadis/serializers/reference_v2.py b/apps/hadis/serializers/reference_v2.py index ab9d651..c2107d4 100644 --- a/apps/hadis/serializers/reference_v2.py +++ b/apps/hadis/serializers/reference_v2.py @@ -293,13 +293,58 @@ class BookReferenceV2SyncSerializer(serializers.ModelSerializer): from apps.hadis.models.reference import BookAuthor, BookReference from .category import LocalizedField +def _get_request_lang_v2(serializer_instance) -> str: + from django.utils.translation import get_language + request = serializer_instance.context.get("request") if hasattr(serializer_instance, "context") and serializer_instance.context else None + lang = None + if request: + lang = getattr(request, "LANGUAGE_CODE", None) or request.headers.get("Accept-Language") or request.query_params.get("lang") + if not lang: + lang = get_language() or "ru" + return str(lang).split(",")[0].split("-")[0].strip().lower() or "ru" + + class BookAuthorListSerializer(serializers.ModelSerializer): """سریالایزر برای لیست نویسنده‌ها""" name = LocalizedField() + known_as = LocalizedField() + scholarity = LocalizedField() + tabaqa = LocalizedField() + rutba = serializers.SerializerMethodField() + features = LocalizedField() + related_narrator_slug = serializers.CharField(source='related_narrator.slug', read_only=True, allow_null=True) + birth_year_hijri = serializers.SerializerMethodField() + death_year_hijri = serializers.SerializerMethodField() + age_at_death = serializers.SerializerMethodField() + birth_year_canonical = serializers.CharField(source='birth_year_hijri', read_only=True) + death_year_canonical = serializers.CharField(source='death_year_hijri', read_only=True) + age_at_death_canonical = serializers.CharField(source='age_at_death', read_only=True) class Meta: model = BookAuthor - fields = ['id', 'slug', 'name', 'image', 'birth_year_hijri', 'birth_year_miladi'] + fields = [ + 'id', 'slug', 'name', 'image', + 'known_as', 'scholarity', 'tabaqa', 'rutba', 'features', + 'birth_year_hijri', 'death_year_hijri', 'age_at_death', + 'birth_year_canonical', 'death_year_canonical', 'age_at_death_canonical', + 'birth_year_miladi', 'death_year_miladi', + 'related_narrator_slug', + ] + + def get_rutba(self, obj): + request = self.context.get('request') + if obj.rutba_status: + return get_localized_text(obj.rutba_status.title, request) + return get_localized_text(obj.rutba, request) if obj.rutba else None + + def get_birth_year_hijri(self, obj): + return obj.get_formatted_birth_year(lang=_get_request_lang_v2(self)) + + def get_death_year_hijri(self, obj): + return obj.get_formatted_death_year(lang=_get_request_lang_v2(self)) + + def get_age_at_death(self, obj): + return obj.get_formatted_age(lang=_get_request_lang_v2(self)) class BookAuthorDetailSerializer(serializers.ModelSerializer): @@ -312,17 +357,33 @@ class BookAuthorDetailSerializer(serializers.ModelSerializer): rutba_detail = serializers.SerializerMethodField() features = LocalizedField() related_narrator_slug = serializers.CharField(source='related_narrator.slug', read_only=True, allow_null=True) + birth_year_hijri = serializers.SerializerMethodField() + death_year_hijri = serializers.SerializerMethodField() + age_at_death = serializers.SerializerMethodField() + birth_year_canonical = serializers.CharField(source='birth_year_hijri', read_only=True) + death_year_canonical = serializers.CharField(source='death_year_hijri', read_only=True) + age_at_death_canonical = serializers.CharField(source='age_at_death', read_only=True) class Meta: model = BookAuthor fields = [ 'id', 'slug', 'name', 'image', 'known_as', 'scholarity', 'tabaqa', 'rutba', 'rutba_detail', 'features', - 'birth_year_hijri', 'death_year_hijri', + 'birth_year_hijri', 'death_year_hijri', 'age_at_death', + 'birth_year_canonical', 'death_year_canonical', 'age_at_death_canonical', 'birth_year_miladi', 'death_year_miladi', 'related_narrator_slug' ] + def get_birth_year_hijri(self, obj): + return obj.get_formatted_birth_year(lang=_get_request_lang_v2(self)) + + def get_death_year_hijri(self, obj): + return obj.get_formatted_death_year(lang=_get_request_lang_v2(self)) + + def get_age_at_death(self, obj): + return obj.get_formatted_age(lang=_get_request_lang_v2(self)) + def get_rutba(self, obj): request = self.context.get('request') if obj.rutba_status: @@ -389,6 +450,12 @@ class BookAuthorSyncSerializer(serializers.ModelSerializer): rutba_detail = serializers.SerializerMethodField() features = LocalizedField() related_narrator_slug = serializers.CharField(source='related_narrator.slug', read_only=True, allow_null=True) + birth_year_hijri = serializers.SerializerMethodField() + death_year_hijri = serializers.SerializerMethodField() + age_at_death = serializers.SerializerMethodField() + birth_year_canonical = serializers.CharField(source='birth_year_hijri', read_only=True) + death_year_canonical = serializers.CharField(source='death_year_hijri', read_only=True) + age_at_death_canonical = serializers.CharField(source='age_at_death', read_only=True) # اضافه کردن لیست کتاب‌ها books = BookAuthorSyncReferenceSerializer(source='book_references', many=True, read_only=True) @@ -407,12 +474,25 @@ class BookAuthorSyncSerializer(serializers.ModelSerializer): 'features', 'birth_year_hijri', 'death_year_hijri', + 'age_at_death', + 'birth_year_canonical', + 'death_year_canonical', + 'age_at_death_canonical', 'birth_year_miladi', 'death_year_miladi', 'books', # 👈 لیست کتاب‌های هر نویسنده 'related_narrator_slug' ] + def get_birth_year_hijri(self, obj): + return obj.get_formatted_birth_year(lang=_get_request_lang_v2(self)) + + def get_death_year_hijri(self, obj): + return obj.get_formatted_death_year(lang=_get_request_lang_v2(self)) + + def get_age_at_death(self, obj): + return obj.get_formatted_age(lang=_get_request_lang_v2(self)) + def get_rutba(self, obj): request = self.context.get('request') if obj.rutba_status: diff --git a/apps/hadis/serializers/serializers_admin.py b/apps/hadis/serializers/serializers_admin.py index 5e7a445..7cfdeb3 100644 --- a/apps/hadis/serializers/serializers_admin.py +++ b/apps/hadis/serializers/serializers_admin.py @@ -2316,6 +2316,9 @@ class AdminBookAuthorSerializer(serializers.ModelSerializer): required=False, queryset=BookReference.objects.all(), ) + birth_year_preview = serializers.SerializerMethodField(read_only=True) + death_year_preview = serializers.SerializerMethodField(read_only=True) + age_at_death_preview = serializers.SerializerMethodField(read_only=True) class Meta: model = BookAuthor @@ -2327,6 +2330,10 @@ class AdminBookAuthorSerializer(serializers.ModelSerializer): "remove_image", "birth_year_hijri", "death_year_hijri", + "age_at_death", + "birth_year_preview", + "death_year_preview", + "age_at_death_preview", "birth_year_miladi", "death_year_miladi", "related_narrator", @@ -2339,7 +2346,22 @@ class AdminBookAuthorSerializer(serializers.ModelSerializer): "features", "book_references", ] - read_only_fields = ["id", "slug"] + read_only_fields = [ + "id", + "slug", + "birth_year_preview", + "death_year_preview", + "age_at_death_preview", + ] + + def get_birth_year_preview(self, obj): + return _build_admin_date_preview(obj, "birth", obj.birth_year_hijri) + + def get_death_year_preview(self, obj): + return _build_admin_date_preview(obj, "death", obj.death_year_hijri) + + def get_age_at_death_preview(self, obj): + return _build_admin_date_preview(obj, "age", getattr(obj, "age_at_death", None)) def to_internal_value(self, data): import json @@ -2352,13 +2374,44 @@ class AdminBookAuthorSerializer(serializers.ModelSerializer): mutable_data[json_field] = json.loads(val) except ValueError: pass - for int_field in ["birth_year_hijri", "death_year_hijri", "birth_year_miladi", "death_year_miladi", "related_narrator", "rutba_status"]: - if int_field in mutable_data: - val = mutable_data.get(int_field) - if val == "" or val == "null" or val == "undefined" or val == "none": - mutable_data[int_field] = None + for field in [ + "birth_year_hijri", + "death_year_hijri", + "age_at_death", + "birth_year_miladi", + "death_year_miladi", + "related_narrator", + "rutba_status", + ]: + if field in mutable_data: + val = mutable_data.get(field) + if val == "" or val is None or val == "null" or val == "undefined" or val == "none" or (isinstance(val, str) and not val.strip()): + mutable_data[field] = None + elif field in ("birth_year_hijri", "death_year_hijri", "age_at_death") and val is not None: + mutable_data[field] = str(val).strip() return super().to_internal_value(mutable_data) + def validate(self, attrs): + from ..utils.date_engine import parse_canonical_date + date_checks = [ + ("birth_year_hijri", "birth"), + ("death_year_hijri", "death"), + ("age_at_death", "age"), + ] + errors = {} + for field_name, category in date_checks: + if field_name in attrs and attrs[field_name] is not None: + raw_val = str(attrs[field_name]).strip() + if raw_val: + parsed = parse_canonical_date(raw_val, category=category) + if not parsed.is_valid: + errors[field_name] = parsed.error + else: + attrs[field_name] = parsed.raw_value + if errors: + raise serializers.ValidationError(errors) + return super().validate(attrs) + def create(self, validated_data): validated_data.pop("remove_image", False) book_references = validated_data.pop("book_references", None) diff --git a/apps/hadis/views/reference_v2.py b/apps/hadis/views/reference_v2.py index 4914287..9b75f0f 100644 --- a/apps/hadis/views/reference_v2.py +++ b/apps/hadis/views/reference_v2.py @@ -85,12 +85,19 @@ class BookReferenceV2SyncView(generics.ListAPIView): century = self.request.query_params.get('century') if century: if century == 'before_islam': - qs = qs.filter(author__death_year_hijri__lt=0) + qs = qs.filter( + author__author_dates__date_type='death', + author__author_dates__year_value__value_min__lt=0, + ).distinct() elif century.isdigit(): century_num = int(century) start_year = (century_num - 1) * 100 + 1 end_year = century_num * 100 - qs = qs.filter(author__death_year_hijri__gte=start_year, author__death_year_hijri__lte=end_year) + qs = qs.filter( + author__author_dates__date_type='death', + author__author_dates__year_value__value_min__lte=end_year, + author__author_dates__year_value__value_max__gte=start_year, + ).distinct() return qs @@ -209,14 +216,21 @@ class BookReferenceV2ListView(generics.ListAPIView): century_q = Q() for c in centuries: if c == 'before_islam': - century_q |= Q(author__death_year_hijri__lt=0) + century_q |= Q( + author__author_dates__date_type='death', + author__author_dates__year_value__value_min__lt=0, + ) elif c.isdigit(): century_num = int(c) start_year = (century_num - 1) * 100 + 1 end_year = century_num * 100 - century_q |= Q(author__death_year_hijri__gte=start_year, author__death_year_hijri__lte=end_year) + century_q |= Q( + author__author_dates__date_type='death', + author__author_dates__year_value__value_min__lte=end_year, + author__author_dates__year_value__value_max__gte=start_year, + ) if century_q: - qs = qs.filter(century_q) + qs = qs.filter(century_q).distinct() return qs @@ -249,6 +263,7 @@ class BookReferenceV2ListView(generics.ListAPIView): }) + from rest_framework.generics import ListAPIView, RetrieveAPIView from rest_framework.permissions import AllowAny from rest_framework.filters import SearchFilter @@ -265,7 +280,12 @@ class BookAuthorListView(ListAPIView): """ لیست تمام نویسندگان رفرنس """ - queryset = BookAuthor.objects.all().order_by('-created_at') + queryset = ( + BookAuthor.objects + .select_related('related_narrator', 'rutba_status') + .prefetch_related('author_dates__year_value') + .order_by('-created_at') + ) serializer_class = BookAuthorListSerializer pagination_class = StandardResultsSetPagination permission_classes = [AllowAny] @@ -285,7 +305,12 @@ class BookAuthorDetailView(RetrieveAPIView): """ دریافت اطلاعات دقیق یک نویسنده بر اساس slug """ - queryset = BookAuthor.objects.select_related('related_narrator', 'rutba_status').all() + queryset = ( + BookAuthor.objects + .select_related('related_narrator', 'rutba_status') + .prefetch_related('author_dates__year_value') + .all() + ) serializer_class = BookAuthorDetailSerializer lookup_field = 'slug' lookup_url_kwarg = 'author_slug' @@ -330,7 +355,12 @@ class BookAuthorSyncView(generics.ListAPIView): Returns complete author data and their associated books without pagination. """ # 👇 استفاده از prefetch_related برای جلوگیری از کرش کردن دیتابیس به خاطر N+1 Query - queryset = BookAuthor.objects.select_related('related_narrator', 'rutba_status').prefetch_related('book_references').order_by('id') + queryset = ( + BookAuthor.objects + .select_related('related_narrator', 'rutba_status') + .prefetch_related('book_references', 'author_dates__year_value') + .order_by('id') + ) serializer_class = BookAuthorSyncSerializer pagination_class = NoPagination permission_classes = [AllowAny] diff --git a/apps/hadis/views_admin.py b/apps/hadis/views_admin.py index 5af478b..ab9123c 100644 --- a/apps/hadis/views_admin.py +++ b/apps/hadis/views_admin.py @@ -908,7 +908,12 @@ class AdminBookAuthorViewSet(ModelViewSet): parser_classes = (MultiPartParser, FormParser, JSONParser) def get_queryset(self): - queryset = BookAuthor.objects.select_related("rutba_status").all() + queryset = ( + BookAuthor.objects + .select_related("rutba_status") + .prefetch_related("author_dates__year_value") + .all() + ) search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter(name__icontains=search_query)