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