Browse Source
references update and changes: new models for reference edditions and volumes added.
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 commandsmaster
9 changed files with 632 additions and 31 deletions
-
12apps/hadis/admin/reference.py
-
74apps/hadis/management/commands/populate_book_metadata.py
-
113apps/hadis/migrations/0016_remove_bookreference_city_of_publication_and_more.py
-
107apps/hadis/models/reference.py
-
44apps/hadis/serializers/reference.py
-
171apps/hadis/serializers/reference_v2.py
-
70apps/hadis/serializers/serializers_admin.py
-
3apps/hadis/urls.py
-
69apps/hadis/views/reference_v2.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.")) |
||||
@ -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'), |
||||
|
), |
||||
|
] |
||||
@ -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 |
||||
|
} |
||||
@ -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') |
||||
|
) |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue