Browse Source

refrence researchers and author fixed

master
Mohsen Taba 2 weeks ago
parent
commit
a2ea302d5d
  1. 14
      apps/hadis/admin/reference.py
  2. 81
      apps/hadis/management/commands/seed_edition_researchers.py
  3. 51
      apps/hadis/migrations/0030_remove_bookauthor_book_references_and_more.py
  4. 52
      apps/hadis/models/reference.py
  5. 7
      apps/hadis/serializers/hadis.py
  6. 35
      apps/hadis/serializers/reference_v2.py
  7. 60
      apps/hadis/serializers/serializers_admin.py
  8. 21
      apps/hadis/views/hadis.py
  9. 31
      apps/hadis/views/reference_v2.py
  10. 4
      apps/hadis/views/transmitter.py
  11. 5
      apps/hadis/views_admin.py

14
apps/hadis/admin/reference.py

@ -491,7 +491,7 @@ class BookReferenceAdmin(ModelAdmin):
fieldsets = ( fieldsets = (
(_('Basic Info'), { (_('Basic Info'), {
'fields': ('title', 'description', 'slug', 'language', 'tags', 'type')
'fields': ('title', 'description', 'slug', 'language', 'tags', 'type', 'author')
}), }),
(_('Publication Info'), { (_('Publication Info'), {
'fields': ('number_page',) 'fields': ('number_page',)
@ -516,6 +516,12 @@ class BookReferenceAdmin(ModelAdmin):
return '-' return '-'
class BookAuthorAdminForm(forms.ModelForm):
class Meta:
model = BookAuthor
fields = '__all__'
class BookAuthorAdmin(ModelAdmin): class BookAuthorAdmin(ModelAdmin):
"""Admin for BookAuthor model""" """Admin for BookAuthor model"""
form = BookAuthorAdminForm form = BookAuthorAdminForm
@ -525,9 +531,6 @@ class BookAuthorAdmin(ModelAdmin):
readonly_fields = ('created_at', 'updated_at') readonly_fields = ('created_at', 'updated_at')
list_filter = ('birth_year_hijri', 'death_year_hijri', 'birth_year_miladi', 'death_year_miladi', 'created_at') list_filter = ('birth_year_hijri', 'death_year_hijri', 'birth_year_miladi', 'death_year_miladi', 'created_at')
# Use filter_horizontal for ManyToMany fields to make selection easier
filter_horizontal = ('book_references',)
fieldsets = ( fieldsets = (
(_('Basic Info'), { (_('Basic Info'), {
'fields': ('name',) 'fields': ('name',)
@ -538,9 +541,6 @@ class BookAuthorAdmin(ModelAdmin):
(_('Dates (Miladi)'), { (_('Dates (Miladi)'), {
'fields': ('birth_year_miladi', 'death_year_miladi') 'fields': ('birth_year_miladi', 'death_year_miladi')
}), }),
(_('References'), {
'fields': ('book_references',)
}),
(_('Timestamps'), { (_('Timestamps'), {
'fields': ('created_at', 'updated_at'), 'fields': ('created_at', 'updated_at'),
'classes': ('collapse',) 'classes': ('collapse',)

81
apps/hadis/management/commands/seed_edition_researchers.py

@ -0,0 +1,81 @@
# backend/apps/hadis/management/commands/seed_edition_researchers.py
import random
from django.core.management.base import BaseCommand
from django.db import transaction
from django.utils.text import slugify
from apps.hadis.models import BookEdition, BookAuthor, BookResearcher
class Command(BaseCommand):
help = 'Assigns two researchers to each BookEdition: one random text-only, one linked to a random existing BookAuthor.'
def handle(self, *args, **options):
self.stdout.write(self.style.WARNING("Starting to seed edition researchers..."))
editions = list(BookEdition.objects.all())
authors = list(BookAuthor.objects.all())
if not editions:
self.stdout.write(self.style.ERROR("No BookEditions found in the database."))
return
if not authors:
self.stdout.write(self.style.ERROR("No BookAuthors found. Please add some authors first."))
return
# 1. Clean existing researchers
self.stdout.write("Deleting existing BookResearchers...")
BookResearcher.objects.all().delete()
random_researcher_names = [
{"fa": "دکتر سید علی موسوی", "en": "Dr. Seyed Ali Mousavi"},
{"fa": "استاد محمد کریمی", "en": "Professor Mohammad Karimi"},
{"fa": "دکتر رضا علوی", "en": "Dr. Reza Alavi"},
{"fa": "پژوهشگر حمید احمدی", "en": "Researcher Hamid Ahmadi"},
{"fa": "دکتر مریم رضایی", "en": "Dr. Maryam Rezaei"},
{"fa": "استاد فاطمه حسینی", "en": "Professor Fatemeh Hosseini"},
{"fa": "دکتر جعفر صادقی", "en": "Dr. Jafar Sadeghi"},
]
text_only_count = 0
linked_author_count = 0
with transaction.atomic():
for edition in editions:
# 1. First researcher: Text-only (Random Name & Slug)
name_data = random.choice(random_researcher_names)
name_field = [
{"language_code": "fa", "text": name_data["fa"]},
{"language_code": "en", "text": name_data["en"]},
]
# Ensure clean slug
base_slug = slugify(name_data["en"])
slug_val = base_slug
counter = 1
while BookResearcher.objects.filter(slug=slug_val).exists():
slug_val = f"{base_slug}-{counter}"
counter += 1
BookResearcher.objects.create(
book_edition=edition,
name=name_field,
slug=slug_val,
author=None
)
text_only_count += 1
# 2. Second researcher: Linked to a random BookAuthor
random_author = random.choice(authors)
BookResearcher.objects.create(
book_edition=edition,
author=random_author
)
linked_author_count += 1
self.stdout.write(
self.style.SUCCESS(
f"Successfully seeded researchers for {len(editions)} BookEditions!\n"
f"- Created {text_only_count} text-only researchers.\n"
f"- Created {linked_author_count} researchers linked to existing authors."
)
)

51
apps/hadis/migrations/0030_remove_bookauthor_book_references_and_more.py

@ -0,0 +1,51 @@
# Generated by Django 5.2.12 on 2026-07-07 16:02
import django.db.models.deletion
from django.db import migrations, models
def copy_m2m_to_fk(apps, schema_editor):
BookReference = apps.get_model('hadis', 'BookReference')
for book in BookReference.objects.all():
# Get first author from many-to-many relationship
# The relationship is still accessible as 'authors' on BookReference in this state
first_author = book.authors.first() if hasattr(book, 'authors') else None
if first_author:
book.author = first_author
book.save()
class Migration(migrations.Migration):
dependencies = [
('hadis', '0029_booktype_sect'),
]
operations = [
migrations.AddField(
model_name='bookreference',
name='author',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='book_references', to='hadis.bookauthor', verbose_name='Author'),
),
migrations.RunPython(copy_m2m_to_fk, reverse_code=migrations.RunPython.noop),
migrations.RemoveField(
model_name='bookauthor',
name='book_references',
),
migrations.RemoveField(
model_name='bookreference',
name='researcher',
),
migrations.CreateModel(
name='BookResearcher',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.JSONField(blank=True, default=list, verbose_name='Name')),
('slug', models.SlugField(blank=True, max_length=255, null=True, verbose_name='slug')),
('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='researcher_profiles', to='hadis.bookauthor', verbose_name='Author')),
('book_edition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='researchers', to='hadis.bookedition', verbose_name='Book Edition')),
],
options={
'verbose_name': 'Book Researcher',
'verbose_name_plural': 'Book Researchers',
},
),
]

52
apps/hadis/models/reference.py

@ -106,8 +106,15 @@ class BookReference(models.Model):
related_name='book_references' related_name='book_references'
) )
type = models.ForeignKey('BookType', on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Type')) type = models.ForeignKey('BookType', on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Type'))
author = models.ForeignKey(
'BookAuthor',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='book_references',
verbose_name=_('Author')
)
order = models.IntegerField(default=0, verbose_name=_('Order')) order = models.IntegerField(default=0, verbose_name=_('Order'))
researcher = models.JSONField(default=list, blank=True, verbose_name=_('Researcher'))
rate = models.DecimalField( rate = models.DecimalField(
max_digits=3, max_digits=3,
decimal_places=2, decimal_places=2,
@ -388,12 +395,7 @@ class BookAuthor(models.Model):
birth_year_miladi = models.IntegerField(verbose_name=_("Birth Year (Miladi)"), 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) death_year_miladi = models.IntegerField(verbose_name=_("Death Year (Miladi)"), null=True, blank=True)
book_references = models.ManyToManyField(
BookReference,
related_name='authors',
verbose_name=_('book references'),
blank=True
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at'))
@ -451,6 +453,7 @@ class BookAuthor(models.Model):
import time import time
self.slug = f"author-{int(time.time() * 1000) % 1000000}" self.slug = f"author-{int(time.time() * 1000) % 1000000}"
super().save(*args, **kwargs) super().save(*args, **kwargs)
self.researcher_profiles.all().update(name=self.name, slug=self.slug)
class BookAttribute(models.Model): class BookAttribute(models.Model):
@ -513,3 +516,38 @@ class BookAttribute(models.Model):
if isinstance(tr, dict) and tr.get('language_code') == 'en': if isinstance(tr, dict) and tr.get('language_code') == 'en':
return tr.get('text', '') return tr.get('text', '')
return None return None
class BookResearcher(models.Model):
book_edition = models.ForeignKey(
'BookEdition',
on_delete=models.CASCADE,
related_name='researchers',
verbose_name=_('Book Edition')
)
author = models.ForeignKey(
'BookAuthor',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='researcher_profiles',
verbose_name=_('Author')
)
name = models.JSONField(default=list, blank=True, verbose_name=_('Name'))
slug = models.SlugField(max_length=255, verbose_name=_('slug'), null=True, blank=True)
class Meta:
verbose_name = _('Book Researcher')
verbose_name_plural = _('Book Researchers')
def save(self, *args, **kwargs):
if self.author:
self.name = self.author.name
self.slug = self.author.slug
super().save(*args, **kwargs)
def __str__(self):
try:
return self.name[0]['text']
except:
return self.slug or f"Researcher {self.id}"

7
apps/hadis/serializers/hadis.py

@ -127,7 +127,7 @@ class HadisSyncSerializer(serializers.ModelSerializer):
for reference in obj.references.all(): for reference in obj.references.all():
book = reference.book_reference book = reference.book_reference
authors = book.authors.all() if book else []
authors = [book.author] if book and book.author else []
references_block.append({ references_block.append({
'id': reference.id, 'id': reference.id,
'title': get_localized_text(book.title, request) if book else None, 'title': get_localized_text(book.title, request) if book else None,
@ -833,7 +833,7 @@ class HadisReferenceSerializer(serializers.ModelSerializer):
"""Get list of localized author names""" """Get list of localized author names"""
try: try:
# 1. Fetch the BookAuthor objects # 1. Fetch the BookAuthor objects
authors = obj.book_reference.authors.all()
authors = [obj.book_reference.author] if obj.book_reference and obj.book_reference.author else []
if not authors: if not authors:
return [] return []
@ -1111,9 +1111,10 @@ class BaseDetailedReferenceSerializer(serializers.ModelSerializer):
def get_book_authors(self, obj): def get_book_authors(self, obj):
if obj.book_reference: if obj.book_reference:
request = self.context.get('request') request = self.context.get('request')
authors = [obj.book_reference.author] if obj.book_reference.author else []
return [ return [
{"id": author.id, "name": get_localized_text(author.name, request)} {"id": author.id, "name": get_localized_text(author.name, request)}
for author in obj.book_reference.authors.all()
for author in authors
] ]
return [] return []

35
apps/hadis/serializers/reference_v2.py

@ -1,6 +1,6 @@
from rest_framework import serializers from rest_framework import serializers
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
from ..models import BookReference, BookAuthor, BookEdition, BookVolume, BookTag
from ..models import BookReference, BookAuthor, BookEdition, BookVolume, BookTag, BookResearcher
from .category import get_localized_text from .category import get_localized_text
from apps.hadis.serializers import HadisListSerializer, HadisShortSerializer from apps.hadis.serializers import HadisListSerializer, HadisShortSerializer
from ..serializers.category import LocalizedField from ..serializers.category import LocalizedField
@ -18,9 +18,15 @@ class BookAuthorV2Serializer(serializers.ModelSerializer):
model = BookAuthor model = BookAuthor
fields = ['id', 'name', 'slug'] fields = ['id', 'name', 'slug']
class BookResearcherV2Serializer(serializers.ModelSerializer):
name = LocalizedField()
class Meta:
model = BookResearcher
fields = ['id', 'name', 'slug']
class BookReferenceV2ListSerializer(serializers.ModelSerializer): class BookReferenceV2ListSerializer(serializers.ModelSerializer):
title = LocalizedField() title = LocalizedField()
authors = BookAuthorV2Serializer(many=True, read_only=True)
authors = serializers.SerializerMethodField()
tags = BookTagV2Serializer(many=True, read_only=True) tags = BookTagV2Serializer(many=True, read_only=True)
number_of_volumes = serializers.SerializerMethodField() number_of_volumes = serializers.SerializerMethodField()
image = serializers.SerializerMethodField() image = serializers.SerializerMethodField()
@ -29,6 +35,11 @@ class BookReferenceV2ListSerializer(serializers.ModelSerializer):
model = BookReference model = BookReference
fields = ['id', 'slug', 'title', 'authors', 'number_of_volumes', 'tags', 'image'] fields = ['id', 'slug', 'title', 'authors', 'number_of_volumes', 'tags', 'image']
def get_authors(self, obj):
if obj.author:
return [BookAuthorV2Serializer(obj.author, context=self.context).data]
return []
def get_number_of_volumes(self, obj): def get_number_of_volumes(self, obj):
return obj.volumes.count() return obj.volumes.count()
@ -48,12 +59,14 @@ class BookEditionV2Serializer(serializers.ModelSerializer):
notes = LocalizedField() notes = LocalizedField()
tags = LocalizedField() tags = LocalizedField()
publication_place = serializers.SerializerMethodField() publication_place = serializers.SerializerMethodField()
researchers = BookResearcherV2Serializer(many=True, read_only=True)
class Meta: class Meta:
model = BookEdition model = BookEdition
fields = [ fields = [
'id', 'publisher', 'publication_place', 'edition_number', 'id', 'publisher', 'publication_place', 'edition_number',
'year_of_publication', 'number_of_volumes', 'notes', 'source_url', 'tags'
'year_of_publication', 'number_of_volumes', 'notes', 'source_url', 'tags',
'researchers'
] ]
def get_publication_place(self, obj): def get_publication_place(self, obj):
@ -83,7 +96,7 @@ class BookVolumeV2Serializer(serializers.ModelSerializer):
class BookReferenceV2DetailSerializer(serializers.ModelSerializer): class BookReferenceV2DetailSerializer(serializers.ModelSerializer):
title = LocalizedField() title = LocalizedField()
authors = BookAuthorV2Serializer(many=True, read_only=True)
authors = serializers.SerializerMethodField()
image = serializers.SerializerMethodField() image = serializers.SerializerMethodField()
information = serializers.SerializerMethodField() information = serializers.SerializerMethodField()
@ -94,6 +107,11 @@ class BookReferenceV2DetailSerializer(serializers.ModelSerializer):
model = BookReference model = BookReference
fields = ['id', 'title', 'authors', 'image', 'information', 'excerpts', 'volumes'] fields = ['id', 'title', 'authors', 'image', 'information', 'excerpts', 'volumes']
def get_authors(self, obj):
if obj.author:
return [BookAuthorV2Serializer(obj.author, context=self.context).data]
return []
def get_image(self, obj): def get_image(self, obj):
first_image = obj.images.filter(book_volume__isnull=True).order_by('order').first() first_image = obj.images.filter(book_volume__isnull=True).order_by('order').first()
if not first_image: if not first_image:
@ -166,7 +184,7 @@ class BookReferenceV2DetailSerializer(serializers.ModelSerializer):
class BookReferenceV2SyncSerializer(serializers.ModelSerializer): class BookReferenceV2SyncSerializer(serializers.ModelSerializer):
title = LocalizedField() title = LocalizedField()
authors = BookAuthorV2Serializer(many=True, read_only=True)
authors = serializers.SerializerMethodField()
attributes = BookAttributeSerializer(many=True, read_only=True) attributes = BookAttributeSerializer(many=True, read_only=True)
images = BookReferenceImageSerializer(many=True, read_only=True) images = BookReferenceImageSerializer(many=True, read_only=True)
@ -181,6 +199,11 @@ class BookReferenceV2SyncSerializer(serializers.ModelSerializer):
'attributes', 'images', 'information', 'hadises', 'volumes' 'attributes', 'images', 'information', 'hadises', 'volumes'
] ]
def get_authors(self, obj):
if obj.author:
return [BookAuthorV2Serializer(obj.author, context=self.context).data]
return []
def get_information(self, obj): def get_information(self, obj):
request = self.context.get("request") request = self.context.get("request")
summary = get_localized_text(obj.description, request) if obj.description else "" summary = get_localized_text(obj.description, request) if obj.description else ""
@ -265,7 +288,7 @@ class AuthorBookReferenceSerializer(serializers.ModelSerializer):
def get_authors(self, obj): def get_authors(self, obj):
field = LocalizedField() field = LocalizedField()
field._context = self.context field._context = self.context
return [field.to_representation(author.name) for author in obj.authors.all()]
return [field.to_representation(obj.author.name)] if obj.author else []
def get_thumbnail(self, obj): def get_thumbnail(self, obj):
# گرفتن اولین عکس آپلود شده برای این رفرنس # گرفتن اولین عکس آپلود شده برای این رفرنس

60
apps/hadis/serializers/serializers_admin.py

@ -4,7 +4,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from utils.image_compression import maybe_compress_uploaded_file from utils.image_compression import maybe_compress_uploaded_file
from ..models import ( from ..models import (
Hadis, HadisCategory, HadisStatus, HadisTag, HadisCorrection, HadisInterpretation, HadisCollection, HadisSect, HadisInCollection, HadisTransmitter, Hadis, HadisCategory, HadisStatus, HadisTag, HadisCorrection, HadisInterpretation, HadisCollection, HadisSect, HadisInCollection, HadisTransmitter,
BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAttribute, BookSubjectArea, BookType, BookEdition, BookVolume,
BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAttribute, BookSubjectArea, BookType, BookEdition, BookVolume, BookResearcher,
Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, TransmitterOpinion, TransmitterOriginalText, Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, TransmitterOpinion, TransmitterOriginalText,
CorrectionReference, CorrectionReferenceImage, InterpretationReference, InterpretationReferenceImage, CorrectionReference, CorrectionReferenceImage, InterpretationReference, InterpretationReferenceImage,
OriginalTextReference, OriginalTextReferenceImage OriginalTextReference, OriginalTextReferenceImage
@ -823,7 +823,7 @@ class AdminBookReferenceListSerializer(serializers.ModelSerializer):
class AdminBookReferenceDetailSerializer(serializers.ModelSerializer): class AdminBookReferenceDetailSerializer(serializers.ModelSerializer):
subject_area_detail = AdminBookSubjectAreaSerializer(source="subject_area", many=True, read_only=True) subject_area_detail = AdminBookSubjectAreaSerializer(source="subject_area", many=True, read_only=True)
type_detail = AdminBookTypeSerializer(source="type", read_only=True) type_detail = AdminBookTypeSerializer(source="type", read_only=True)
authors_detail = AdminBookAuthorSerializer(source="authors", many=True, read_only=True)
author_detail = AdminBookAuthorSerializer(source="author", read_only=True)
subject_area = serializers.PrimaryKeyRelatedField( subject_area = serializers.PrimaryKeyRelatedField(
queryset=BookSubjectArea.objects.all(), queryset=BookSubjectArea.objects.all(),
@ -835,10 +835,10 @@ class AdminBookReferenceDetailSerializer(serializers.ModelSerializer):
required=False, required=False,
allow_null=True, allow_null=True,
) )
authors = serializers.PrimaryKeyRelatedField(
author = serializers.PrimaryKeyRelatedField(
queryset=BookAuthor.objects.all(), queryset=BookAuthor.objects.all(),
many=True,
required=False, required=False,
allow_null=True,
) )
source_url = serializers.SerializerMethodField() source_url = serializers.SerializerMethodField()
@ -908,13 +908,12 @@ class AdminBookReferenceDetailSerializer(serializers.ModelSerializer):
"type", "type",
"type_detail", "type_detail",
"order", "order",
"researcher",
"city_of_publication", "city_of_publication",
"country_of_publication", "country_of_publication",
"edition_number", "edition_number",
"rate", "rate",
"authors",
"authors_detail",
"author",
"author_detail",
"created_at", "created_at",
"updated_at", "updated_at",
"share_link", "share_link",
@ -923,22 +922,16 @@ class AdminBookReferenceDetailSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
subject_area = validated_data.pop("subject_area", []) subject_area = validated_data.pop("subject_area", [])
authors = validated_data.pop("authors", [])
book = super().create(validated_data) book = super().create(validated_data)
if subject_area: if subject_area:
book.subject_area.set(subject_area) book.subject_area.set(subject_area)
if authors:
book.authors.set(authors)
return book return book
def update(self, instance, validated_data): def update(self, instance, validated_data):
subject_area = validated_data.pop("subject_area", None) subject_area = validated_data.pop("subject_area", None)
authors = validated_data.pop("authors", None)
book = super().update(instance, validated_data) book = super().update(instance, validated_data)
if subject_area is not None: if subject_area is not None:
book.subject_area.set(subject_area) book.subject_area.set(subject_area)
if authors is not None:
book.authors.set(authors)
return book return book
@ -1171,6 +1164,15 @@ class AdminBookVolumeSerializer(serializers.ModelSerializer):
] ]
class AdminBookResearcherSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
name = serializers.JSONField(required=False, default=list)
class Meta:
model = BookResearcher
fields = ["id", "name", "slug", "author"]
class AdminBookEditionSerializer(serializers.ModelSerializer): class AdminBookEditionSerializer(serializers.ModelSerializer):
publisher = serializers.JSONField(required=False, default=list) publisher = serializers.JSONField(required=False, default=list)
city_of_publication = serializers.JSONField(required=False, default=list) city_of_publication = serializers.JSONField(required=False, default=list)
@ -1179,6 +1181,7 @@ class AdminBookEditionSerializer(serializers.ModelSerializer):
tags = serializers.JSONField(required=False, default=list) tags = serializers.JSONField(required=False, default=list)
volumes_detail = AdminBookVolumeSerializer(source="volumes", many=True, read_only=True) volumes_detail = AdminBookVolumeSerializer(source="volumes", many=True, read_only=True)
number_of_volumes = serializers.SerializerMethodField() number_of_volumes = serializers.SerializerMethodField()
researchers = AdminBookResearcherSerializer(many=True, required=False)
class Meta: class Meta:
model = BookEdition model = BookEdition
@ -1196,9 +1199,40 @@ class AdminBookEditionSerializer(serializers.ModelSerializer):
"notes", "notes",
"tags", "tags",
"volumes_detail", "volumes_detail",
"researchers",
"created_at", "created_at",
"updated_at", "updated_at",
] ]
def get_number_of_volumes(self, obj): def get_number_of_volumes(self, obj):
return obj.volumes.count() return obj.volumes.count()
def create(self, validated_data):
researchers_data = validated_data.pop("researchers", [])
edition = super().create(validated_data)
for res_data in researchers_data:
BookResearcher.objects.create(book_edition=edition, **res_data)
return edition
def update(self, instance, validated_data):
researchers_data = validated_data.pop("researchers", None)
edition = super().update(instance, validated_data)
if researchers_data is not None:
# Delete researchers no longer in the request payload
keep_ids = [r.get("id") for r in researchers_data if r.get("id")]
instance.researchers.exclude(id__in=keep_ids).delete()
for res_data in researchers_data:
res_id = res_data.get("id")
if res_id:
# Update existing researcher
res_data_clean = {k: v for k, v in res_data.items() if k != "id"}
# Filter and update
BookResearcher.objects.filter(id=res_id, book_edition=instance).update(**res_data_clean)
# Trigger save to execute save() hook (for author update propagation if author is changed)
res_obj = BookResearcher.objects.filter(id=res_id, book_edition=instance).first()
if res_obj:
res_obj.save()
else:
# Create new researcher
BookResearcher.objects.create(book_edition=instance, **res_data)
return edition

21
apps/hadis/views/hadis.py

@ -124,7 +124,7 @@ class HadisSyncView(ListAPIView):
queryset=HadisReference.objects queryset=HadisReference.objects
.select_related('book_reference') .select_related('book_reference')
.prefetch_related( .prefetch_related(
'book_reference__authors',
'book_reference__author',
Prefetch( Prefetch(
'images', 'images',
queryset=ReferenceImage.objects.order_by('priority') queryset=ReferenceImage.objects.order_by('priority')
@ -135,7 +135,7 @@ class HadisSyncView(ListAPIView):
Prefetch( Prefetch(
'hadiscorrection_set', 'hadiscorrection_set',
queryset=HadisCorrection.objects.prefetch_related( queryset=HadisCorrection.objects.prefetch_related(
'references__book_reference__authors',
'references__book_reference__author',
'references__edition', 'references__edition',
'references__book_volume', 'references__book_volume',
'references__images' 'references__images'
@ -145,7 +145,7 @@ class HadisSyncView(ListAPIView):
Prefetch( Prefetch(
'category__interpretations', 'category__interpretations',
queryset=HadisInterpretation.objects.prefetch_related( queryset=HadisInterpretation.objects.prefetch_related(
'references__book_reference__authors',
'references__book_reference__author',
'references__edition', 'references__edition',
'references__book_volume', 'references__book_volume',
'references__images' 'references__images'
@ -530,7 +530,7 @@ class HadisDetailView(RetrieveAPIView):
).prefetch_related( ).prefetch_related(
'tags', 'tags',
'references__book_reference', 'references__book_reference',
'references__book_reference__authors',
'references__book_reference__author',
'references__images', 'references__images',
) )
@ -811,15 +811,14 @@ class HadisFiltersSyncAPIView(APIView):
# ۳. منابع / کتاب‌ها # ۳. منابع / کتاب‌ها
sources = [] sources = []
# استفاده از prefetch_related برای جلوگیری از مشکل N+1 Query هنگام گرفتن نویسنده‌ها
books = BookReference.objects.prefetch_related('authors').all().order_by('order', '-id')
# استفاده از select_related برای جلوگیری از مشکل N+1 Query هنگام گرفتن نویسنده‌ها
books = BookReference.objects.select_related('author').all().order_by('order', '-id')
for book in books: for book in books:
# استخراج نام نویسنده‌ها برای سرچ آفلاین فلاتر # استخراج نام نویسنده‌ها برای سرچ آفلاین فلاتر
authors_names = [ authors_names = [
get_localized_text(author.name, request=request, language_code=lang) or ""
for author in book.authors.all()
]
get_localized_text(book.author.name, request=request, language_code=lang) or ""
] if book.author else []
sources.append({ sources.append({
"id": book.id, "id": book.id,
@ -880,7 +879,7 @@ class HadisCorrectionDetailView(RetrieveAPIView):
return HadisCorrection.objects.all().select_related( return HadisCorrection.objects.all().select_related(
'hadis' 'hadis'
).prefetch_related( ).prefetch_related(
'references__book_reference__authors',
'references__book_reference__author',
'references__edition', 'references__edition',
'references__book_volume', 'references__book_volume',
'references__images' 'references__images'
@ -905,7 +904,7 @@ class HadisInterpretationDetailView(RetrieveAPIView):
return HadisInterpretation.objects.all().select_related( return HadisInterpretation.objects.all().select_related(
'category' 'category'
).prefetch_related( ).prefetch_related(
'references__book_reference__authors',
'references__book_reference__author',
'references__edition', 'references__edition',
'references__book_volume', 'references__book_volume',
'references__images' 'references__images'

31
apps/hadis/views/reference_v2.py

@ -16,8 +16,8 @@ class BookReferenceV2DetailView(generics.RetrieveAPIView):
return ( return (
BookReference.objects BookReference.objects
.filter(slug=self.kwargs.get('slug')) .filter(slug=self.kwargs.get('slug'))
.select_related('author')
.prefetch_related( .prefetch_related(
'authors',
'editions__volumes__images', 'editions__volumes__images',
'volumes__images', 'volumes__images',
'images', 'images',
@ -55,8 +55,8 @@ class BookReferenceV2SyncView(generics.ListAPIView):
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
qs = BookReference.objects.select_related().prefetch_related(
'authors', 'attributes', 'images', 'editions__volumes', 'volumes', 'hadis_references__hadis'
qs = BookReference.objects.select_related('author').prefetch_related(
'attributes', 'images', 'editions__volumes', 'volumes', 'hadis_references__hadis'
).distinct().order_by('id') ).distinct().order_by('id')
category = self.request.query_params.get('category') category = self.request.query_params.get('category')
@ -65,11 +65,14 @@ class BookReferenceV2SyncView(generics.ListAPIView):
author = self.request.query_params.get('author') author = self.request.query_params.get('author')
if author: if author:
qs = qs.filter(authors__id=author)
qs = qs.filter(author_id=author)
sect = self.request.query_params.get('sect')
if sect:
qs = qs.filter(type__id=sect)
madhab = self.request.query_params.get('madhab')
if madhab:
if madhab in ['shia', 'sunni']:
qs = qs.filter(type__sect=madhab)
else:
qs = qs.filter(type__id=madhab)
pdf_available = self.request.query_params.get('pdf_available') pdf_available = self.request.query_params.get('pdf_available')
if pdf_available == 'true': if pdf_available == 'true':
@ -80,12 +83,12 @@ class BookReferenceV2SyncView(generics.ListAPIView):
century = self.request.query_params.get('century') century = self.request.query_params.get('century')
if century: if century:
if century == 'before_islam': if century == 'before_islam':
qs = qs.filter(authors__death_year_hijri__lt=0)
qs = qs.filter(author__death_year_hijri__lt=0)
elif century.isdigit(): elif century.isdigit():
century_num = int(century) century_num = int(century)
start_year = (century_num - 1) * 100 + 1 start_year = (century_num - 1) * 100 + 1
end_year = century_num * 100 end_year = century_num * 100
qs = qs.filter(authors__death_year_hijri__gte=start_year, authors__death_year_hijri__lte=end_year)
qs = qs.filter(author__death_year_hijri__gte=start_year, author__death_year_hijri__lte=end_year)
return qs return qs
@ -142,7 +145,7 @@ class BookReferenceV2ListView(generics.ListAPIView):
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
qs = BookReference.objects.prefetch_related('authors', 'tags', 'volumes__images').distinct().order_by('-created_at')
qs = BookReference.objects.select_related('author').prefetch_related('tags', 'volumes__images').order_by('-created_at')
search = self.request.query_params.get('search') search = self.request.query_params.get('search')
if search: if search:
@ -150,7 +153,7 @@ class BookReferenceV2ListView(generics.ListAPIView):
qs = qs.filter( qs = qs.filter(
Q(title__icontains=search) | Q(title__icontains=search) |
Q(description__icontains=search) | Q(description__icontains=search) |
Q(authors__name__icontains=search)
Q(author__name__icontains=search)
) )
category = self.request.query_params.get('category') category = self.request.query_params.get('category')
@ -159,7 +162,7 @@ class BookReferenceV2ListView(generics.ListAPIView):
author = self.request.query_params.get('author') author = self.request.query_params.get('author')
if author: if author:
qs = qs.filter(authors__id=author)
qs = qs.filter(author_id=author)
madhab = self.request.query_params.get('madhab') madhab = self.request.query_params.get('madhab')
if madhab: if madhab:
@ -287,8 +290,8 @@ class AuthorReferencesListView(ListAPIView):
# پیدا کردن رفرنس‌هایی که این نویسنده جزو authors آن‌هاست # پیدا کردن رفرنس‌هایی که این نویسنده جزو authors آن‌هاست
return BookReference.objects.filter( return BookReference.objects.filter(
authors__slug=author_slug
).prefetch_related('authors', 'images').distinct().order_by('-created_at')
author__slug=author_slug
).select_related('author').prefetch_related('images').order_by('-created_at')
class BookAuthorSyncView(generics.ListAPIView): class BookAuthorSyncView(generics.ListAPIView):
""" """

4
apps/hadis/views/transmitter.py

@ -146,7 +146,7 @@ class TransmitterSyncView(ListAPIView):
'originaltexts', 'originaltexts',
# 👇 ۳. اضافه کردن 'transmitter' برای جلوگیری از کوئری چرخشی در DetailSerializer متون اصلی # 👇 ۳. اضافه کردن 'transmitter' برای جلوگیری از کوئری چرخشی در DetailSerializer متون اصلی
queryset=TransmitterOriginalText.objects.select_related('transmitter').prefetch_related( queryset=TransmitterOriginalText.objects.select_related('transmitter').prefetch_related(
'references__book_reference__authors',
'references__book_reference__author',
'references__edition', 'references__edition',
'references__book_volume', 'references__book_volume',
'references__images' 'references__images'
@ -483,7 +483,7 @@ class TransmitterOriginalTextDetailView(RetrieveAPIView):
return TransmitterOriginalText.objects.all().select_related( return TransmitterOriginalText.objects.all().select_related(
'transmitter' 'transmitter'
).prefetch_related( ).prefetch_related(
'references__book_reference__authors',
'references__book_reference__author',
'references__edition', 'references__edition',
'references__book_volume', 'references__book_volume',
'references__images' 'references__images'

5
apps/hadis/views_admin.py

@ -511,7 +511,7 @@ class AdminBookReferenceViewSet(ModelViewSet):
return AdminBookReferenceDetailSerializer return AdminBookReferenceDetailSerializer
def get_queryset(self): def get_queryset(self):
queryset = BookReference.objects.all().prefetch_related("subject_area", "authors", "volumes", "editions").select_related("type")
queryset = BookReference.objects.all().prefetch_related("subject_area", "volumes", "editions").select_related("type", "author")
search_query = self.request.query_params.get("search") search_query = self.request.query_params.get("search")
if search_query: if search_query:
@ -521,7 +521,6 @@ class AdminBookReferenceViewSet(ModelViewSet):
| Q(isbn__icontains=search_query) | Q(isbn__icontains=search_query)
| Q(publisher__icontains=search_query) | Q(publisher__icontains=search_query)
| Q(description__icontains=search_query) | Q(description__icontains=search_query)
| Q(researcher__icontains=search_query)
) )
type_id = self.request.query_params.get("type") type_id = self.request.query_params.get("type")
@ -538,7 +537,7 @@ class AdminBookReferenceViewSet(ModelViewSet):
author_id = self.request.query_params.get("author") author_id = self.request.query_params.get("author")
if author_id and author_id != "all": if author_id and author_id != "all":
queryset = queryset.filter(authors__id=author_id)
queryset = queryset.filter(author_id=author_id)
return queryset.distinct().order_by("-created_at") return queryset.distinct().order_by("-created_at")

Loading…
Cancel
Save