Browse Source

references update and changes: new models for reference edditions and volumes added.

update former serializers to be synced with new changes

create a new v2 api for new structure and lates changes of references page

new populated script added and run in commands
master
Mohsen Taba 1 month ago
parent
commit
076384f78c
  1. 12
      apps/hadis/admin/reference.py
  2. 74
      apps/hadis/management/commands/populate_book_metadata.py
  3. 113
      apps/hadis/migrations/0016_remove_bookreference_city_of_publication_and_more.py
  4. 107
      apps/hadis/models/reference.py
  5. 44
      apps/hadis/serializers/reference.py
  6. 171
      apps/hadis/serializers/reference_v2.py
  7. 70
      apps/hadis/serializers/serializers_admin.py
  8. 3
      apps/hadis/urls.py
  9. 69
      apps/hadis/views/reference_v2.py

12
apps/hadis/admin/reference.py

@ -433,17 +433,15 @@ class BookReferenceAdmin(ModelAdmin):
list_display = ( list_display = (
'get_title_display', 'get_title_display',
'slug', 'slug',
'get_publisher_display',
'year_of_publication',
'rate', 'rate',
'created_at' 'created_at'
) )
list_filter = ('year_of_publication', 'rate', 'created_at', 'type')
list_filter = ('rate', 'created_at', 'type')
# Searching by JSON fields via string is limited in Django, # Searching by JSON fields via string is limited in Django,
# so we focus on standard fields # so we focus on standard fields
search_fields = ('isbn', 'slug', 'volume')
search_fields = ('slug',)
readonly_fields = ('created_at', 'updated_at') readonly_fields = ('created_at', 'updated_at')
@ -458,7 +456,7 @@ class BookReferenceAdmin(ModelAdmin):
'fields': ('title', 'description', 'slug', 'language') 'fields': ('title', 'description', 'slug', 'language')
}), }),
(_('Publication Info'), { (_('Publication Info'), {
'fields': ('publisher', 'isbn', 'year_of_publication', 'number_page', 'volume')
'fields': ('number_page',)
}), }),
(_('Rating & Stats'), { (_('Rating & Stats'), {
'fields': ('rate', 'created_at', 'updated_at') 'fields': ('rate', 'created_at', 'updated_at')
@ -471,10 +469,6 @@ class BookReferenceAdmin(ModelAdmin):
def get_title_display(self, obj): def get_title_display(self, obj):
return self._extract_first_text(obj.title) return self._extract_first_text(obj.title)
@display(description=_('Publisher'))
def get_publisher_display(self, obj):
return self._extract_first_text(obj.publisher)
def _extract_first_text(self, json_data): def _extract_first_text(self, json_data):
"""Helper to safely extract the first 'text' from a JSON list""" """Helper to safely extract the first 'text' from a JSON list"""
if json_data and isinstance(json_data, list) and len(json_data) > 0: if json_data and isinstance(json_data, list) and len(json_data) > 0:

74
apps/hadis/management/commands/populate_book_metadata.py

@ -0,0 +1,74 @@
import json
from django.core.management.base import BaseCommand
from apps.hadis.models import BookReference, BookAuthor, BookEdition, BookVolume
class Command(BaseCommand):
help = 'Populates BookReference, BookEdition, and BookVolume with default test data if empty.'
def handle(self, *args, **kwargs):
self.stdout.write("Starting to populate book metadata...")
# Default values
default_title = "Musnad Abu Ya'la"
default_author_name = "Abu Ya'la al-Mawsili, Imam Hafiz Ahmad ibn Ali ibn al-Muthanna al-Tamimi"
default_summary = "Various publishers (originally compiled in manuscript form; published by multiple Islamic publishers over time, such as Dar al-Ihya al-Turath al-Arabi, Beirut)"
default_publisher = "Dar al-Hadith"
default_city = "Cairo"
default_country = "Egypt"
default_edition_stmt = "1st edition"
default_year = "1434 AH / 2013 CE"
default_source_url = "https://shamela.ws/book/181"
default_tag = "Hadith collection"
books = BookReference.objects.all()
count = 0
for book in books:
# 1. Update Title and Description if empty
if not book.title:
book.title = [{"language_code": "en", "text": default_title}]
if not book.description:
book.description = [{"language_code": "en", "text": default_summary}]
book.save()
# 2. Assign Author if none exists
if not book.authors.exists():
author, created = BookAuthor.objects.get_or_create(
slug="abu-yala-al-mawsili",
defaults={
"name": [{"language_code": "en", "text": default_author_name}]
}
)
book.authors.add(author)
# 3. Create BookEdition if none exists
if not book.editions.exists():
edition = BookEdition.objects.create(
book_reference=book,
publisher=[{"language_code": "en", "text": default_publisher}],
city_of_publication=[{"language_code": "en", "text": default_city}],
country_of_publication=[{"language_code": "en", "text": default_country}],
edition_number=default_edition_stmt,
year_of_publication=default_year,
source_url=default_source_url,
number_of_volumes=1,
tags=[{"language_code": "en", "text": default_tag}],
notes=[]
)
else:
edition = book.editions.first()
# 4. Create BookVolume if none exists
if not book.volumes.exists():
BookVolume.objects.create(
book_reference=book,
edition=edition,
title="Volume 1"
)
count += 1
self.stdout.write(self.style.SUCCESS(f"Successfully populated metadata for {count} books."))

113
apps/hadis/migrations/0016_remove_bookreference_city_of_publication_and_more.py

@ -0,0 +1,113 @@
# Generated by Django 5.2.12 on 2026-06-20 12:01
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hadis', '0015_categoryquranverse_quransecondarytranslation'),
]
operations = [
migrations.RemoveField(
model_name='bookreference',
name='city_of_publication',
),
migrations.RemoveField(
model_name='bookreference',
name='country_of_publication',
),
migrations.RemoveField(
model_name='bookreference',
name='edition_number',
),
migrations.RemoveField(
model_name='bookreference',
name='isbn',
),
migrations.RemoveField(
model_name='bookreference',
name='number_of_volumes',
),
migrations.RemoveField(
model_name='bookreference',
name='publisher',
),
migrations.RemoveField(
model_name='bookreference',
name='source_url',
),
migrations.RemoveField(
model_name='bookreference',
name='volume',
),
migrations.RemoveField(
model_name='bookreference',
name='year_of_publication',
),
migrations.RemoveField(
model_name='bookreferencedocument',
name='volume',
),
migrations.RemoveField(
model_name='bookreferenceimage',
name='volume',
),
migrations.AddField(
model_name='bookauthor',
name='slug',
field=models.SlugField(blank=True, max_length=255, null=True, unique=True, verbose_name='slug'),
),
migrations.CreateModel(
name='BookEdition',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('publisher', models.JSONField(blank=True, default=list, verbose_name='Publisher')),
('city_of_publication', models.JSONField(blank=True, default=list, verbose_name='City of Publication')),
('country_of_publication', models.JSONField(blank=True, default=list, verbose_name='Country of Publication')),
('edition_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='Edition Number')),
('year_of_publication', models.CharField(blank=True, max_length=50, null=True, verbose_name='year of publication')),
('number_of_volumes', models.PositiveIntegerField(blank=True, null=True, verbose_name='Total Number of Volumes')),
('source_url', models.URLField(blank=True, max_length=1000, null=True, verbose_name='Online Source URL')),
('isbn', models.CharField(blank=True, max_length=100, null=True, verbose_name='ISBN')),
('notes', models.JSONField(blank=True, default=list, verbose_name='Notes')),
('tags', models.JSONField(blank=True, default=list, verbose_name='Tags')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')),
('book_reference', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='editions', to='hadis.bookreference', verbose_name='book reference')),
],
options={
'verbose_name': 'Book Edition',
'verbose_name_plural': 'Book Editions',
'ordering': ['-created_at'],
},
),
migrations.CreateModel(
name='BookVolume',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(blank=True, help_text='e.g., Volume 1', max_length=255, null=True, verbose_name='Title')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')),
('book_reference', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='volumes', to='hadis.bookreference', verbose_name='book reference')),
('edition', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='volumes', to='hadis.bookedition', verbose_name='edition')),
],
options={
'verbose_name': 'Book Volume',
'verbose_name_plural': 'Book Volumes',
'ordering': ['title'],
},
),
migrations.AddField(
model_name='bookreferencedocument',
name='book_volume',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='hadis.bookvolume', verbose_name='book volume'),
),
migrations.AddField(
model_name='bookreferenceimage',
name='book_volume',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='images', to='hadis.bookvolume', verbose_name='book volume'),
),
]

107
apps/hadis/models/reference.py

@ -61,24 +61,15 @@ class BookReference(models.Model):
This is different from library books - these are reference books for hadis This is different from library books - these are reference books for hadis
""" """
legacy_id = models.CharField(max_length=255, unique=True, null=True, blank=True, db_index=True) legacy_id = models.CharField(max_length=255, unique=True, null=True, blank=True, db_index=True)
source_url = models.URLField(max_length=1000, null=True, blank=True, verbose_name=_('Source URL'))
title = models.JSONField(default = list , verbose_name=_('Title')) title = models.JSONField(default = list , verbose_name=_('Title'))
description = models.JSONField(default = list , verbose_name=_('Description')) description = models.JSONField(default = list , verbose_name=_('Description'))
language = models.JSONField(default = list , verbose_name=_('Language')) language = models.JSONField(default = list , verbose_name=_('Language'))
isbn = models.CharField(max_length=100, verbose_name=_('ISBN'), blank=True, null=True)
volume = models.CharField(max_length=100, verbose_name=_('volume'), blank=True, null=True)
number_of_volumes = models.PositiveIntegerField(verbose_name=_('Total Number of Volumes'), null=True, blank=True)
year_of_publication = models.CharField(max_length=50, verbose_name=_('year of publication'), blank=True, null=True)
number_page = models.PositiveIntegerField(verbose_name=_('number of pages'), blank=True, null=True) number_page = models.PositiveIntegerField(verbose_name=_('number of pages'), blank=True, null=True)
slug = models.SlugField(max_length=255, verbose_name=_('slug'), blank=True,unique=True) slug = models.SlugField(max_length=255, verbose_name=_('slug'), blank=True,unique=True)
publisher = models.JSONField(default = list , verbose_name=_('Publisher'))
subject_area = models.ManyToManyField(BookSubjectArea, related_name="book_subjects", verbose_name=_('subject area'), blank=True) subject_area = models.ManyToManyField(BookSubjectArea, related_name="book_subjects", verbose_name=_('subject area'), blank=True)
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'))
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')) researcher = models.JSONField(default=list, blank=True, verbose_name=_('Researcher'))
city_of_publication = models.JSONField(default=list, blank=True, verbose_name=_('City of Publication'))
country_of_publication = models.JSONField(default=list, blank=True, verbose_name=_('Country of Publication'))
edition_number = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('Edition Number'))
rate = models.DecimalField( rate = models.DecimalField(
max_digits=3, max_digits=3,
decimal_places=2, decimal_places=2,
@ -142,10 +133,6 @@ class BookReference(models.Model):
def get_description(self, lang): def get_description(self, lang):
return self._get_json_field("description" , lang) return self._get_json_field("description" , lang)
def get_publisher(self, lang):
return self._get_json_field("publisher" , lang)
def get_language(self, lang): def get_language(self, lang):
return self._get_json_field("language" , lang) return self._get_json_field("language" , lang)
@ -208,6 +195,52 @@ class BookReference(models.Model):
self.slug = base_slug self.slug = base_slug
super().save(*args, **kwargs) super().save(*args, **kwargs)
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'))
publisher = models.JSONField(default=list, blank=True, verbose_name=_('Publisher'))
city_of_publication = models.JSONField(default=list, blank=True, verbose_name=_('City of Publication'))
country_of_publication = models.JSONField(default=list, blank=True, verbose_name=_('Country of Publication'))
edition_number = models.CharField(max_length=100, blank=True, null=True, verbose_name=_('Edition Number'))
year_of_publication = models.CharField(max_length=50, blank=True, null=True, verbose_name=_('year of publication'))
number_of_volumes = models.PositiveIntegerField(null=True, blank=True, verbose_name=_('Total Number of Volumes'))
source_url = models.URLField(max_length=1000, null=True, blank=True, verbose_name=_('Online Source URL'))
isbn = models.CharField(max_length=100, verbose_name=_('ISBN'), blank=True, null=True)
notes = models.JSONField(default=list, blank=True, verbose_name=_('Notes'))
tags = models.JSONField(default=list, blank=True, verbose_name=_('Tags'))
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 Edition')
verbose_name_plural = _('Book Editions')
ordering = ['-created_at']
def __str__(self):
return f"{self.book_reference} - {self.edition_number or 'Edition ' + str(self.id)}"
class BookVolume(models.Model):
"""
Model representing a single volume, which can belong to an edition or directly to the book.
"""
book_reference = models.ForeignKey(BookReference, on_delete=models.CASCADE, related_name='volumes', verbose_name=_('book reference'))
edition = models.ForeignKey(BookEdition, on_delete=models.CASCADE, related_name='volumes', verbose_name=_('edition'), null=True, blank=True)
title = models.CharField(max_length=255, verbose_name=_('Title'), help_text=_('e.g., Volume 1'), blank=True, null=True)
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 Volume')
verbose_name_plural = _('Book Volumes')
ordering = ['title']
def __str__(self):
return f"{self.book_reference} - {self.title or 'Vol ' + str(self.id)}"
class BookReferenceImage(models.Model): class BookReferenceImage(models.Model):
""" """
Model for book reference images - multiple images per book reference Model for book reference images - multiple images per book reference
@ -218,8 +251,15 @@ class BookReferenceImage(models.Model):
related_name='images', related_name='images',
verbose_name=_('book reference') verbose_name=_('book reference')
) )
book_volume = models.ForeignKey(
BookVolume,
on_delete=models.CASCADE,
related_name='images',
verbose_name=_('book volume'),
null=True,
blank=True
)
image = models.ImageField(upload_to='hadis/book_reference_images/', verbose_name=_('image')) image = models.ImageField(upload_to='hadis/book_reference_images/', verbose_name=_('image'))
volume = models.CharField(max_length=50, null=True, blank=True, verbose_name=_('Volume'))
order = models.PositiveIntegerField(default=0, verbose_name=_('order')) order = models.PositiveIntegerField(default=0, verbose_name=_('order'))
description = models.JSONField(default = list , verbose_name=_('Description')) description = models.JSONField(default = list , verbose_name=_('Description'))
@ -260,8 +300,15 @@ class BookReferenceDocument(models.Model):
related_name='documents', related_name='documents',
verbose_name=_('book reference') verbose_name=_('book reference')
) )
book_volume = models.ForeignKey(
BookVolume,
on_delete=models.CASCADE,
related_name='documents',
verbose_name=_('book volume'),
null=True,
blank=True
)
file = models.FileField(upload_to='hadis/book_reference_documents/', verbose_name=_('Document File')) file = models.FileField(upload_to='hadis/book_reference_documents/', verbose_name=_('Document File'))
volume = models.CharField(max_length=50, null=True, blank=True, verbose_name=_('Volume'))
title = models.CharField(max_length=255, blank=True, null=True, help_text=_('e.g., Volume 1, 01.pdf')) title = models.CharField(max_length=255, blank=True, null=True, help_text=_('e.g., Volume 1, 01.pdf'))
order = models.PositiveIntegerField(default=0, verbose_name=_('order')) order = models.PositiveIntegerField(default=0, verbose_name=_('order'))
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
@ -280,6 +327,7 @@ class BookAuthor(models.Model):
Model for book reference authors Model for book reference authors
""" """
name = models.JSONField(default = list , verbose_name=_('Name')) name = models.JSONField(default = list , verbose_name=_('Name'))
slug = models.SlugField(max_length=255, verbose_name=_('slug'), null=True, blank=True, unique=True)
birth_year_hijri = models.IntegerField(verbose_name=_("Birth Year (Hijri)"), null=True, blank=True) 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) death_year_hijri = models.IntegerField(verbose_name=_("Death Year (Hijri)"), null=True, blank=True)
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)
@ -319,6 +367,35 @@ class BookAuthor(models.Model):
return tr.get('text', '') return tr.get('text', '')
return None return None
def save(self, *args, **kwargs):
if not self.slug or (isinstance(self.slug, str) and self.slug.strip() == ''):
try:
if self.name and isinstance(self.name, list) and len(self.name) > 0:
first_item = self.name[0]
if isinstance(first_item, dict):
name_text = first_item.get('text', '').strip()
if name_text:
base_slug = slugify(name_text, allow_unicode=True)
slug = base_slug
counter = 1
while BookAuthor.objects.filter(slug=slug).exclude(pk=self.pk).exists():
slug = f"{base_slug}-{counter}"
counter += 1
self.slug = slug
else:
import time
self.slug = f"author-{int(time.time() * 1000) % 1000000}"
else:
import time
self.slug = f"author-{int(time.time() * 1000) % 1000000}"
else:
import time
self.slug = f"author-{int(time.time() * 1000) % 1000000}"
except Exception:
import time
self.slug = f"author-{int(time.time() * 1000) % 1000000}"
super().save(*args, **kwargs)
class BookAttribute(models.Model): class BookAttribute(models.Model):
""" """

44
apps/hadis/serializers/reference.py

@ -29,9 +29,15 @@ class BookReferenceSerializer(serializers.ModelSerializer):
description = LocalizedField() description = LocalizedField()
author = serializers.SerializerMethodField() author = serializers.SerializerMethodField()
share_link = serializers.CharField(read_only=True) share_link = serializers.CharField(read_only=True)
volume = serializers.SerializerMethodField()
class Meta: class Meta:
model = BookReference model = BookReference
fields = ['id','title','slug','rate','author','description','image','volume','share_link'] fields = ['id','title','slug','rate','author','description','image','volume','share_link']
def get_volume(self, obj):
vol = obj.volumes.first()
return vol.title if vol else ""
def get_author(self, obj): def get_author(self, obj):
request = self.context.get("request") request = self.context.get("request")
# Prefer request.LANGUAGE_CODE if you use LocaleMiddleware # Prefer request.LANGUAGE_CODE if you use LocaleMiddleware
@ -106,15 +112,32 @@ class BookDetailSerializer(serializers.ModelSerializer):
hadis = serializers.SerializerMethodField() hadis = serializers.SerializerMethodField()
title = LocalizedField() title = LocalizedField()
language = LocalizedField() language = LocalizedField()
publisher = LocalizedField()
description = LocalizedField() description = LocalizedField()
share_link = serializers.CharField(read_only=True) share_link = serializers.CharField(read_only=True)
publisher = serializers.SerializerMethodField()
isbn = serializers.SerializerMethodField()
volume = serializers.SerializerMethodField()
class Meta: class Meta:
model = BookReference model = BookReference
fields = ['id','title','rate','isbn','share_link','language','number_page','publisher','description','volume','slug','attribute','author','image','hadis'] fields = ['id','title','rate','isbn','share_link','language','number_page','publisher','description','volume','slug','attribute','author','image','hadis']
def get_publisher(self, obj):
edition = obj.editions.first()
if edition and getattr(edition, 'publisher', None):
request = self.context.get("request")
return get_localized_text(edition.publisher, request)
return ""
def get_isbn(self, obj):
edition = obj.editions.first()
return edition.isbn if edition else ""
def get_volume(self, obj):
vol = obj.volumes.first()
return vol.title if vol else ""
def get_hadis(self,obj): def get_hadis(self,obj):
from apps.hadis.models import Hadis from apps.hadis.models import Hadis
# 1. Fetch related hadis QuerySet for better pagination efficiency # 1. Fetch related hadis QuerySet for better pagination efficiency
@ -181,16 +204,23 @@ class BookReferenceSyncSerializer(serializers.ModelSerializer):
def get_detail(self, obj): def get_detail(self, obj):
"""Get basic book information""" """Get basic book information"""
request = self.context.get('request') request = self.context.get('request')
edition = obj.editions.first()
vol = obj.volumes.first()
publisher_data = edition.publisher if edition else []
isbn_data = edition.isbn if edition else ""
year_data = edition.year_of_publication if edition else ""
volume_data = vol.title if vol else ""
return { return {
'description': get_localized_text(obj.description, request), 'description': get_localized_text(obj.description, request),
'publisher': get_localized_text(obj.publisher, request),
'volume': obj.volume,
'publisher': get_localized_text(publisher_data, request),
'volume': volume_data,
'language': get_localized_text(obj.language, request), 'language': get_localized_text(obj.language, request),
'isbn': obj.isbn,
'isbn': isbn_data,
'number_page': obj.number_page, 'number_page': obj.number_page,
'year_of_publication': obj.year_of_publication,
'volume_info': obj.volume,
'year_of_publication': year_data,
'volume_info': volume_data,
'rating': obj.rate 'rating': obj.rate
} }

171
apps/hadis/serializers/reference_v2.py

@ -0,0 +1,171 @@
from rest_framework import serializers
from rest_framework.pagination import PageNumberPagination
from ..models import BookReference, BookAuthor, BookEdition, BookVolume
from .category import get_localized_text
from apps.hadis.serializers import HadisListSerializer, HadisShortSerializer
from ..serializers.category import LocalizedField
from ..serializers.reference import BookAttributeSerializer, BookReferenceImageSerializer
from rest_framework.pagination import PageNumberPagination
from ..models import BookReference, BookAuthor, BookEdition, BookVolume
from .category import get_localized_text
from apps.hadis.serializers import HadisListSerializer
from ..serializers.category import LocalizedField
class BookAuthorV2Serializer(serializers.ModelSerializer):
name = LocalizedField()
class Meta:
model = BookAuthor
fields = ['id', 'name', 'slug']
class BookEditionV2Serializer(serializers.ModelSerializer):
publisher = LocalizedField()
notes = LocalizedField()
tags = LocalizedField()
publication_place = serializers.SerializerMethodField()
class Meta:
model = BookEdition
fields = [
'id', 'publisher', 'publication_place', 'edition_number',
'year_of_publication', 'number_of_volumes', 'notes', 'source_url', 'tags'
]
def get_publication_place(self, obj):
request = self.context.get("request")
city = get_localized_text(obj.city_of_publication, request) if obj.city_of_publication else ""
country = get_localized_text(obj.country_of_publication, request) if obj.country_of_publication else ""
return {
"city": city,
"country": country
}
class BookVolumeV2Serializer(serializers.ModelSerializer):
class Meta:
model = BookVolume
fields = ['id', 'title']
class BookReferenceV2DetailSerializer(serializers.ModelSerializer):
title = LocalizedField()
authors = BookAuthorV2Serializer(many=True, read_only=True)
information = serializers.SerializerMethodField()
excerpts = serializers.SerializerMethodField()
volumes = serializers.SerializerMethodField()
class Meta:
model = BookReference
fields = ['id', 'title', 'authors', 'information', 'excerpts', 'volumes']
def get_information(self, obj):
request = self.context.get("request")
summary = get_localized_text(obj.description, request) if obj.description else ""
editions = BookEditionV2Serializer(obj.editions.all(), many=True, context=self.context).data
return {
"summary": summary,
"editions": editions
}
def get_excerpts(self, obj):
from apps.hadis.models import Hadis
hadis_ids = obj.hadis_references.values_list('hadis', flat=True)
hadis_qs = Hadis.objects.filter(id__in=hadis_ids, status=True).order_by('id')
paginator = PageNumberPagination()
paginator.page_size = 20
request = self.context.get("request")
paginated_hadis = paginator.paginate_queryset(hadis_qs, request)
if paginated_hadis is not None:
serializer = HadisListSerializer(paginated_hadis, many=True, context=self.context)
return {
'count': paginator.page.paginator.count,
'next': paginator.get_next_link(),
'previous': paginator.get_previous_link(),
'results': serializer.data
}
return {
'count': hadis_qs.count(),
'next': None,
'previous': None,
'results': HadisListSerializer(hadis_qs, many=True, context=self.context).data
}
def get_volumes(self, obj):
editions = obj.editions.all()
if editions.exists():
editions_data = []
for ed in editions:
editions_data.append({
"edition_id": ed.id,
"edition_statement": ed.edition_number or f"Edition {ed.id}",
"volumes": BookVolumeV2Serializer(ed.volumes.all(), many=True).data
})
return {
"has_editions": True,
"editions_volumes": editions_data,
"all_volumes": []
}
else:
return {
"has_editions": False,
"editions_volumes": [],
"all_volumes": BookVolumeV2Serializer(obj.volumes.all(), many=True).data
}
class BookReferenceV2SyncSerializer(serializers.ModelSerializer):
title = LocalizedField()
authors = BookAuthorV2Serializer(many=True, read_only=True)
attributes = BookAttributeSerializer(many=True, read_only=True)
images = BookReferenceImageSerializer(many=True, read_only=True)
information = serializers.SerializerMethodField()
hadises = serializers.SerializerMethodField()
volumes = serializers.SerializerMethodField()
class Meta:
model = BookReference
fields = [
'id', 'title', 'slug', 'share_link', 'rate', 'authors',
'attributes', 'images', 'information', 'hadises', 'volumes'
]
def get_information(self, obj):
request = self.context.get("request")
summary = get_localized_text(obj.description, request) if obj.description else ""
editions = BookEditionV2Serializer(obj.editions.all(), many=True, context=self.context).data
return {
"summary": summary,
"editions": editions,
"language": get_localized_text(obj.language, request) if obj.language else "",
"number_page": obj.number_page
}
def get_hadises(self, obj):
references = obj.hadis_references.all()
hadis_list = [ref.hadis for ref in references if ref.hadis]
return HadisShortSerializer(hadis_list, many=True, context=self.context).data
def get_volumes(self, obj):
editions = obj.editions.all()
if editions.exists():
editions_data = []
for ed in editions:
editions_data.append({
"edition_id": ed.id,
"edition_statement": ed.edition_number or f"Edition {ed.id}",
"volumes": BookVolumeV2Serializer(ed.volumes.all(), many=True).data
})
return {
"has_editions": True,
"editions_volumes": editions_data,
"all_volumes": []
}
else:
return {
"has_editions": False,
"editions_volumes": [],
"all_volumes": BookVolumeV2Serializer(obj.volumes.all(), many=True).data
}

70
apps/hadis/serializers/serializers_admin.py

@ -447,21 +447,33 @@ class AdminBookAttributeSerializer(serializers.ModelSerializer):
class AdminBookReferenceImageSerializer(serializers.ModelSerializer): class AdminBookReferenceImageSerializer(serializers.ModelSerializer):
image = AbsoluteImageField(required=True) image = AbsoluteImageField(required=True)
volume = serializers.SerializerMethodField()
class Meta: class Meta:
model = BookReferenceImage model = BookReferenceImage
fields = ["id", "book_reference", "image", "volume", "order", "description"] fields = ["id", "book_reference", "image", "volume", "order", "description"]
def get_volume(self, obj):
return obj.book_volume.title if getattr(obj, 'book_volume', None) else ""
class AdminBookReferenceDocumentSerializer(serializers.ModelSerializer): class AdminBookReferenceDocumentSerializer(serializers.ModelSerializer):
file = AbsoluteFileField(required=True) file = AbsoluteFileField(required=True)
volume = serializers.SerializerMethodField()
class Meta: class Meta:
model = BookReferenceDocument model = BookReferenceDocument
fields = ["id", "book_reference", "file", "volume", "title", "order"] fields = ["id", "book_reference", "file", "volume", "title", "order"]
def get_volume(self, obj):
return obj.book_volume.title if getattr(obj, 'book_volume', None) else ""
class AdminBookReferenceListSerializer(serializers.ModelSerializer): class AdminBookReferenceListSerializer(serializers.ModelSerializer):
volume = serializers.SerializerMethodField()
publisher = serializers.SerializerMethodField()
year_of_publication = serializers.SerializerMethodField()
class Meta: class Meta:
model = BookReference model = BookReference
fields = [ fields = [
@ -475,6 +487,18 @@ class AdminBookReferenceListSerializer(serializers.ModelSerializer):
"created_at", "created_at",
] ]
def get_volume(self, obj):
vol = obj.volumes.first()
return vol.title if vol else ""
def get_publisher(self, obj):
edition = obj.editions.first()
return edition.publisher if edition else []
def get_year_of_publication(self, obj):
edition = obj.editions.first()
return edition.year_of_publication if edition else ""
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)
@ -497,6 +521,52 @@ class AdminBookReferenceDetailSerializer(serializers.ModelSerializer):
required=False, required=False,
) )
source_url = serializers.SerializerMethodField()
isbn = serializers.SerializerMethodField()
volume = serializers.SerializerMethodField()
number_of_volumes = serializers.SerializerMethodField()
year_of_publication = serializers.SerializerMethodField()
publisher = serializers.SerializerMethodField()
city_of_publication = serializers.SerializerMethodField()
country_of_publication = serializers.SerializerMethodField()
edition_number = serializers.SerializerMethodField()
def get_source_url(self, obj):
ed = obj.editions.first()
return ed.source_url if ed else ""
def get_isbn(self, obj):
ed = obj.editions.first()
return ed.isbn if ed else ""
def get_volume(self, obj):
vol = obj.volumes.first()
return vol.title if vol else ""
def get_number_of_volumes(self, obj):
ed = obj.editions.first()
return ed.number_of_volumes if ed else None
def get_year_of_publication(self, obj):
ed = obj.editions.first()
return ed.year_of_publication if ed else ""
def get_publisher(self, obj):
ed = obj.editions.first()
return ed.publisher if ed else []
def get_city_of_publication(self, obj):
ed = obj.editions.first()
return ed.city_of_publication if ed else []
def get_country_of_publication(self, obj):
ed = obj.editions.first()
return ed.country_of_publication if ed else []
def get_edition_number(self, obj):
ed = obj.editions.first()
return ed.edition_number if ed else ""
class Meta: class Meta:
model = BookReference model = BookReference
fields = [ fields = [

3
apps/hadis/urls.py

@ -4,6 +4,7 @@ from .views.category import HadisCategorySectListView, HadisCategoryTreeView, Ca
from .views.hadis import HadisCollectionListView, HadisListView, HadisBasicView, HadisDetailView, HadisSyncView, HadisTransmittersView, HadisCorrectionsView,HadisMainListView, HadisFiltersView, HadisLayersView,PinnedHadisCollectionListView, HadisFiltersSyncAPIView from .views.hadis import HadisCollectionListView, HadisListView, HadisBasicView, HadisDetailView, HadisSyncView, HadisTransmittersView, HadisCorrectionsView,HadisMainListView, HadisFiltersView, HadisLayersView,PinnedHadisCollectionListView, HadisFiltersSyncAPIView
from .views.transmitter import TransmitterView ,TransmitterDetailView, TransmitterSyncView,TransmitterOpinionView, TransmitterOriginalTextView, TransmitterFiltersView from .views.transmitter import TransmitterView ,TransmitterDetailView, TransmitterSyncView,TransmitterOpinionView, TransmitterOriginalTextView, TransmitterFiltersView
from .views.reference import BookDetailView, BookReferencesView, BookReferenceSyncView, BookAttributeView from .views.reference import BookDetailView, BookReferencesView, BookReferenceSyncView, BookAttributeView
from .views.reference_v2 import BookReferenceV2DetailView, BookReferenceV2SyncView
from .views.version import ContentReleaseSyncView from .views.version import ContentReleaseSyncView
from .views.info import HadisInfoView from .views.info import HadisInfoView
from .views_admin import ( from .views_admin import (
@ -61,6 +62,7 @@ urlpatterns = [
path('sync/hadis/', HadisSyncView.as_view(), name='hadis-sync'), path('sync/hadis/', HadisSyncView.as_view(), name='hadis-sync'),
path('sync/narrators/', TransmitterSyncView.as_view(), name='transmitter-sync'), path('sync/narrators/', TransmitterSyncView.as_view(), name='transmitter-sync'),
path('sync/references/', BookReferenceSyncView.as_view(), name='reference-sync'), path('sync/references/', BookReferenceSyncView.as_view(), name='reference-sync'),
path('v2/sync/references/', BookReferenceV2SyncView.as_view(), name='reference-sync-v2'),
path('sync/version/', ContentReleaseSyncView.as_view(), name='content-release-sync'), path('sync/version/', ContentReleaseSyncView.as_view(), name='content-release-sync'),
path('sync/filters/', HadisFiltersSyncAPIView.as_view(), name='hadis-filters-sync'), path('sync/filters/', HadisFiltersSyncAPIView.as_view(), name='hadis-filters-sync'),
path('info/', HadisInfoView.as_view(), name='hadis-info'), path('info/', HadisInfoView.as_view(), name='hadis-info'),
@ -86,6 +88,7 @@ urlpatterns = [
path('narrators/', TransmitterView.as_view(), name='narrators'), path('narrators/', TransmitterView.as_view(), name='narrators'),
# Reference paths # Reference paths
path('v2/references/<str:slug>/', BookReferenceV2DetailView.as_view(), name='reference-detail-v2'),
path('references/<str:reference_slug>', BookDetailView.as_view(), name='reference-detail'), path('references/<str:reference_slug>', BookDetailView.as_view(), name='reference-detail'),
path('references/', BookReferencesView.as_view(), name='references'), path('references/', BookReferencesView.as_view(), name='references'),

69
apps/hadis/views/reference_v2.py

@ -0,0 +1,69 @@
from rest_framework import generics
from ..models import BookReference
from ..serializers.reference_v2 import BookReferenceV2DetailSerializer
from drf_yasg.utils import swagger_auto_schema
class BookReferenceV2DetailView(generics.RetrieveAPIView):
"""
V2 View to retrieve Book Reference details, formatted with tabs
(Information, Excerpts, Volumes) and supporting multiple editions per book.
"""
queryset = BookReference.objects.all()
serializer_class = BookReferenceV2DetailSerializer
lookup_field = 'slug'
def get_queryset(self):
return (
BookReference.objects
.filter(slug=self.kwargs.get('slug'))
.prefetch_related(
'authors',
'editions__volumes',
'volumes',
'hadis_references__hadis'
)
)
@swagger_auto_schema(
operation_summary="Get V2 Book Reference Detail",
operation_description="Retrieve structured metadata for a book reference, including its editions, related hadiths (excerpts), and volumes.",
tags=['Dobodbi - Hadis (V2)'],
responses={200: BookReferenceV2DetailSerializer}
)
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
from utils.pagination import NoPagination
from ..serializers.reference_v2 import BookReferenceV2SyncSerializer
class BookReferenceV2SyncView(generics.ListAPIView):
"""
API view to sync all V2 book reference data for offline mode
Returns all book references with editions, volumes, and related hadises
"""
serializer_class = BookReferenceV2SyncSerializer
pagination_class = NoPagination
@swagger_auto_schema(
operation_summary="Sync V2 Book References",
operation_description="Returns all book references for offline sync.",
tags=['Dobodbi - Hadis (V2)'],
responses={200: BookReferenceV2SyncSerializer(many=True)}
)
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
def get_queryset(self):
return (
BookReference.objects
.select_related()
.prefetch_related(
'authors',
'attributes',
'images',
'editions__volumes',
'volumes',
'hadis_references__hadis'
)
.order_by('id')
)
Loading…
Cancel
Save