Browse Source

feat(references): add volume unique images and source/edition single tag system

master
Mohsen Taba 3 weeks ago
parent
commit
1f3a13861b
  1. 4
      apps/hadis/admin/reference.py
  2. 37
      apps/hadis/migrations/0037_remove_bookedition_tags_remove_bookreference_tags_and_more.py
  3. 23
      apps/hadis/models/reference.py
  4. 24
      apps/hadis/serializers/reference_v2.py
  5. 47
      apps/hadis/serializers/serializers_admin.py
  6. 13
      apps/hadis/urls.py
  7. 40
      apps/hadis/views/hadis.py
  8. 52
      apps/hadis/views/reference.py
  9. 2
      apps/hadis/views/reference_v2.py
  10. 19
      apps/hadis/views_admin.py

4
apps/hadis/admin/reference.py

@ -487,11 +487,11 @@ class BookReferenceAdmin(ModelAdmin):
inlines = [BookReferenceImageInline, BookAttributeInline] inlines = [BookReferenceImageInline, BookAttributeInline]
# Use filter_horizontal for ManyToMany fields to make selection easier # Use filter_horizontal for ManyToMany fields to make selection easier
filter_horizontal = ('subject_area', 'tags')
filter_horizontal = ('subject_area',)
fieldsets = ( fieldsets = (
(_('Basic Info'), { (_('Basic Info'), {
'fields': ('title', 'description', 'slug', 'language', 'tags', 'type', 'author')
'fields': ('title', 'description', 'slug', 'language', 'tag', 'type', 'author')
}), }),
(_('Publication Info'), { (_('Publication Info'), {
'fields': ('number_page',) 'fields': ('number_page',)

37
apps/hadis/migrations/0037_remove_bookedition_tags_remove_bookreference_tags_and_more.py

@ -0,0 +1,37 @@
# Generated by Django 4.2.30 on 2026-08-29 17:44
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('hadis', '0036_alter_narratorlayer_options_narratorlayer_hadis_and_more'),
]
operations = [
migrations.RemoveField(
model_name='bookedition',
name='tags',
),
migrations.RemoveField(
model_name='bookreference',
name='tags',
),
migrations.AddField(
model_name='bookedition',
name='tag',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='book_editions', to='hadis.booktag', verbose_name='Tag'),
),
migrations.AddField(
model_name='bookreference',
name='tag',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='book_references', to='hadis.booktag', verbose_name='Tag'),
),
migrations.AddField(
model_name='bookvolume',
name='image',
field=models.ImageField(blank=True, null=True, upload_to='hadis/volume_images/', verbose_name='Image'),
),
]

23
apps/hadis/models/reference.py

@ -101,11 +101,13 @@ class BookReference(LowercaseSlugMixin, models.Model):
verbose_name=_('Subject Area'), verbose_name=_('Subject Area'),
blank=True blank=True
) )
tags = models.ManyToManyField(
tag = models.ForeignKey(
'BookTag', 'BookTag',
verbose_name=_('Tags'),
on_delete=models.SET_NULL,
null=True,
blank=True, blank=True,
related_name='book_references'
related_name='book_references',
verbose_name=_('Tag')
) )
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( author = models.ForeignKey(
@ -215,7 +217,14 @@ class BookEdition(models.Model):
source_url = models.URLField(max_length=1000, null=True, blank=True, verbose_name=_('Online Source URL')) 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) isbn = models.CharField(max_length=100, verbose_name=_('ISBN'), blank=True, null=True)
notes = models.JSONField(default=list, blank=True, verbose_name=_('Notes')) notes = models.JSONField(default=list, blank=True, verbose_name=_('Notes'))
tags = models.JSONField(default=list, blank=True, verbose_name=_('Tags'))
tag = models.ForeignKey(
'BookTag',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='book_editions',
verbose_name=_('Tag')
)
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'))
@ -235,6 +244,12 @@ class BookVolume(models.Model):
book_reference = models.ForeignKey(BookReference, on_delete=models.CASCADE, related_name='volumes', verbose_name=_('book reference')) 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) 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) title = models.CharField(max_length=255, verbose_name=_('Title'), help_text=_('e.g., Volume 1'), blank=True, null=True)
image = models.ImageField(
upload_to='hadis/volume_images/',
null=True,
blank=True,
verbose_name=_('Image')
)
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'))

24
apps/hadis/serializers/reference_v2.py

@ -36,13 +36,13 @@ class BookEditorV2Serializer(serializers.ModelSerializer):
class BookReferenceV2ListSerializer(serializers.ModelSerializer): class BookReferenceV2ListSerializer(serializers.ModelSerializer):
title = LocalizedField() title = LocalizedField()
authors = serializers.SerializerMethodField() authors = serializers.SerializerMethodField()
tags = BookTagV2Serializer(many=True, read_only=True)
tag = BookTagV2Serializer(read_only=True)
number_of_volumes = serializers.SerializerMethodField() number_of_volumes = serializers.SerializerMethodField()
image = serializers.SerializerMethodField() image = serializers.SerializerMethodField()
class Meta: class Meta:
model = BookReference model = BookReference
fields = ['id', 'slug', 'title', 'authors', 'number_of_volumes', 'tags', 'image']
fields = ['id', 'slug', 'title', 'authors', 'number_of_volumes', 'tag', 'image']
def get_authors(self, obj): def get_authors(self, obj):
if obj.author: if obj.author:
@ -53,8 +53,18 @@ class BookReferenceV2ListSerializer(serializers.ModelSerializer):
return obj.volumes.count() return obj.volumes.count()
def get_image(self, obj): def get_image(self, obj):
if hasattr(obj, 'thumbnail') and obj.thumbnail:
request = self.context.get('request')
if request:
return absolute_https_url(obj.thumbnail.url, request)
return absolute_https_url(obj.thumbnail.url)
first_volume = obj.volumes.first() first_volume = obj.volumes.first()
if first_volume: if first_volume:
if first_volume.image:
request = self.context.get('request')
if request:
return absolute_https_url(first_volume.image.url, request)
return absolute_https_url(first_volume.image.url)
first_image = first_volume.images.order_by('order').first() first_image = first_volume.images.order_by('order').first()
if first_image and first_image.image: if first_image and first_image.image:
request = self.context.get('request') request = self.context.get('request')
@ -66,7 +76,7 @@ class BookReferenceV2ListSerializer(serializers.ModelSerializer):
class BookEditionV2Serializer(serializers.ModelSerializer): class BookEditionV2Serializer(serializers.ModelSerializer):
publisher = LocalizedField() publisher = LocalizedField()
notes = LocalizedField() notes = LocalizedField()
tags = LocalizedField()
tag = BookTagV2Serializer(read_only=True)
publication_place = serializers.SerializerMethodField() publication_place = serializers.SerializerMethodField()
researchers = BookResearcherV2Serializer(many=True, read_only=True) researchers = BookResearcherV2Serializer(many=True, read_only=True)
editors = BookEditorV2Serializer(many=True, read_only=True) editors = BookEditorV2Serializer(many=True, read_only=True)
@ -75,7 +85,7 @@ class BookEditionV2Serializer(serializers.ModelSerializer):
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', 'tag',
'researchers', 'editors' 'researchers', 'editors'
] ]
@ -96,9 +106,13 @@ class BookVolumeV2Serializer(serializers.ModelSerializer):
fields = ['id', 'title', 'image'] fields = ['id', 'title', 'image']
def get_image(self, obj): def get_image(self, obj):
request = self.context.get('request')
if obj.image:
if request:
return absolute_https_url(obj.image.url, request)
return absolute_https_url(obj.image.url)
first_image = obj.images.order_by('order').first() first_image = obj.images.order_by('order').first()
if first_image and first_image.image: if first_image and first_image.image:
request = self.context.get('request')
if request: if request:
return absolute_https_url(first_image.image.url, request) return absolute_https_url(first_image.image.url, request)
return absolute_https_url(first_image.image.url) return absolute_https_url(first_image.image.url)

47
apps/hadis/serializers/serializers_admin.py

@ -7,7 +7,7 @@ from utils.image_compression import maybe_compress_uploaded_file
from utils import absolute_https_url from utils import absolute_https_url
from ..models import ( from ..models import (
Hadis, HadisCategory, HadisStatus, HadisTag, HadisCorrection, HadisInterpretation, HadisCollection, HadisSect, HadisInCollection, HadisTransmitter, HadisReference, Hadis, HadisCategory, HadisStatus, HadisTag, HadisCorrection, HadisInterpretation, HadisCollection, HadisSect, HadisInCollection, HadisTransmitter, HadisReference,
BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAttribute, BookSubjectArea, BookType, BookEdition, BookVolume, BookResearcher, BookEditor,
BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume, BookResearcher, BookEditor,
Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, TransmitterOpinion, TransmitterOriginalText, Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, TransmitterOpinion, TransmitterOriginalText,
CorrectionReference, CorrectionReferenceImage, InterpretationReference, InterpretationReferenceImage, CorrectionReference, CorrectionReferenceImage, InterpretationReference, InterpretationReferenceImage,
OriginalTextReference, OriginalTextReferenceImage OriginalTextReference, OriginalTextReferenceImage
@ -1179,9 +1179,19 @@ class AdminBookReferenceDocumentSerializer(serializers.ModelSerializer):
return obj.book_volume.title if getattr(obj, 'book_volume', None) else "" return obj.book_volume.title if getattr(obj, 'book_volume', None) else ""
class AdminBookTagSerializer(serializers.ModelSerializer):
title = serializers.JSONField(default=list)
class Meta:
model = BookTag
fields = ["id", "title", "created_at", "updated_at"]
class AdminBookReferenceListSerializer(serializers.ModelSerializer): class AdminBookReferenceListSerializer(serializers.ModelSerializer):
author_id = serializers.IntegerField(source="author.id", read_only=True, allow_null=True) author_id = serializers.IntegerField(source="author.id", read_only=True, allow_null=True)
author_name = serializers.JSONField(source="author.name", read_only=True, allow_null=True) author_name = serializers.JSONField(source="author.name", read_only=True, allow_null=True)
tag_id = serializers.IntegerField(source="tag.id", read_only=True, allow_null=True)
tag_detail = AdminBookTagSerializer(source="tag", read_only=True)
volume = serializers.SerializerMethodField() volume = serializers.SerializerMethodField()
publisher = serializers.SerializerMethodField() publisher = serializers.SerializerMethodField()
year_of_publication = serializers.SerializerMethodField() year_of_publication = serializers.SerializerMethodField()
@ -1195,6 +1205,8 @@ class AdminBookReferenceListSerializer(serializers.ModelSerializer):
"has_editions", "has_editions",
"author_id", "author_id",
"author_name", "author_name",
"tag_id",
"tag_detail",
"volume", "volume",
"publisher", "publisher",
"year_of_publication", "year_of_publication",
@ -1219,6 +1231,7 @@ 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)
author_detail = AdminBookAuthorSerializer(source="author", read_only=True) author_detail = AdminBookAuthorSerializer(source="author", read_only=True)
tag_detail = AdminBookTagSerializer(source="tag", read_only=True)
subject_area = serializers.PrimaryKeyRelatedField( subject_area = serializers.PrimaryKeyRelatedField(
queryset=BookSubjectArea.objects.all(), queryset=BookSubjectArea.objects.all(),
@ -1235,6 +1248,11 @@ class AdminBookReferenceDetailSerializer(serializers.ModelSerializer):
required=False, required=False,
allow_null=True, allow_null=True,
) )
tag = serializers.PrimaryKeyRelatedField(
queryset=BookTag.objects.all(),
required=False,
allow_null=True,
)
source_url = serializers.SerializerMethodField() source_url = serializers.SerializerMethodField()
isbn = serializers.SerializerMethodField() isbn = serializers.SerializerMethodField()
@ -1303,6 +1321,8 @@ class AdminBookReferenceDetailSerializer(serializers.ModelSerializer):
"subject_area_detail", "subject_area_detail",
"type", "type",
"type_detail", "type_detail",
"tag",
"tag_detail",
"order", "order",
"city_of_publication", "city_of_publication",
"country_of_publication", "country_of_publication",
@ -1548,6 +1568,8 @@ class AdminTransmitterOriginalTextSerializer(serializers.ModelSerializer):
class AdminBookVolumeSerializer(serializers.ModelSerializer): class AdminBookVolumeSerializer(serializers.ModelSerializer):
remove_image = serializers.BooleanField(write_only=True, required=False, default=False)
class Meta: class Meta:
model = BookVolume model = BookVolume
fields = [ fields = [
@ -1555,10 +1577,23 @@ class AdminBookVolumeSerializer(serializers.ModelSerializer):
"book_reference", "book_reference",
"edition", "edition",
"title", "title",
"image",
"remove_image",
"created_at", "created_at",
"updated_at", "updated_at",
] ]
def create(self, validated_data):
validated_data.pop("remove_image", False)
return super().create(validated_data)
def update(self, instance, validated_data):
remove_image = validated_data.pop("remove_image", False)
if remove_image and instance.image:
instance.image.delete(save=False)
instance.image = None
return super().update(instance, validated_data)
class AdminBookResearcherSerializer(serializers.ModelSerializer): class AdminBookResearcherSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False) id = serializers.IntegerField(required=False)
@ -1583,7 +1618,12 @@ class AdminBookEditionSerializer(serializers.ModelSerializer):
city_of_publication = serializers.JSONField(required=False, default=list) city_of_publication = serializers.JSONField(required=False, default=list)
country_of_publication = serializers.JSONField(required=False, default=list) country_of_publication = serializers.JSONField(required=False, default=list)
notes = serializers.JSONField(required=False, default=list) notes = serializers.JSONField(required=False, default=list)
tags = serializers.JSONField(required=False, default=list)
tag = serializers.PrimaryKeyRelatedField(
queryset=BookTag.objects.all(),
required=False,
allow_null=True,
)
tag_detail = AdminBookTagSerializer(source="tag", read_only=True)
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) researchers = AdminBookResearcherSerializer(many=True, required=False)
@ -1605,7 +1645,8 @@ class AdminBookEditionSerializer(serializers.ModelSerializer):
"source_url", "source_url",
"isbn", "isbn",
"notes", "notes",
"tags",
"tag",
"tag_detail",
"volumes_detail", "volumes_detail",
"researchers", "researchers",
"editors", "editors",

13
apps/hadis/urls.py

@ -3,19 +3,19 @@ from rest_framework.routers import SimpleRouter
from .views.category import HadisCategorySectListView, HadisCategoryTreeView, CategoriesView, CategoriesBySectView, HadisCategorySelectBySectView, HadisCategorySelectBySectSourceView , HadisCategoryTreeNormalView ,test_deploy,debug_headers,HadisCategoryXMindView from .views.category import HadisCategorySectListView, HadisCategoryTreeView, CategoriesView, CategoriesBySectView, HadisCategorySelectBySectView, HadisCategorySelectBySectSourceView , HadisCategoryTreeNormalView ,test_deploy,debug_headers,HadisCategoryXMindView
from .views.hadis import ( from .views.hadis import (
HadisCollectionListView, HadisListView, HadisBasicView, HadisDetailView, HadisSyncView, HadisTransmittersView, HadisCorrectionsView,HadisMainListView, HadisFiltersView, HadisLayersView,PinnedHadisCollectionListView, HadisFiltersSyncAPIView, HadisCollectionListView, HadisListView, HadisBasicView, HadisDetailView, HadisSyncView, HadisTransmittersView, HadisCorrectionsView,HadisMainListView, HadisFiltersView, HadisLayersView,PinnedHadisCollectionListView, HadisFiltersSyncAPIView,
HadisCorrectionDetailView, HadisInterpretationDetailView
HadisCorrectionDetailView, HadisInterpretationDetailView, HadisInterpretsListView
) )
from .views.transmitter import ( from .views.transmitter import (
TransmitterView ,TransmitterDetailView, TransmitterSyncView,TransmitterOpinionView, TransmitterOriginalTextView, TransmitterFiltersView, TransmitterView ,TransmitterDetailView, TransmitterSyncView,TransmitterOpinionView, TransmitterOriginalTextView, TransmitterFiltersView,
NarratorArgumentsListView , NarratorTeachersView, NarratorStudentsView, TransmitterOriginalTextDetailView NarratorArgumentsListView , NarratorTeachersView, NarratorStudentsView, TransmitterOriginalTextDetailView
) )
from .views.reference import BookDetailView, BookReferencesView, BookReferenceSyncView, BookAttributeView
from .views.reference import BookDetailView, BookReferencesView, BookReferenceSyncView, BookAttributeView, ReferenceExcerptsListView
from .views.reference_v2 import BookReferenceV2DetailView, BookReferenceV2SyncView, BookReferenceV2ListView from .views.reference_v2 import BookReferenceV2DetailView, BookReferenceV2SyncView, BookReferenceV2ListView
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 (
AdminHadisCategoryViewSet, AdminHadisViewSet, AdminHadisCorrectionViewSet, AdminHadisInterpretationViewSet, AdminHadisStatusViewSet, AdminHadisTagViewSet, AdminHadisCollectionViewSet, AdminHadisCategoryViewSet, AdminHadisViewSet, AdminHadisCorrectionViewSet, AdminHadisInterpretationViewSet, AdminHadisStatusViewSet, AdminHadisTagViewSet, AdminHadisCollectionViewSet,
AdminBookSubjectAreaViewSet, AdminBookTypeViewSet, AdminBookAuthorViewSet, AdminBookAttributeViewSet, AdminBookReferenceImageViewSet, AdminBookReferenceDocumentViewSet, AdminBookReferenceViewSet,
AdminBookSubjectAreaViewSet, AdminBookTypeViewSet, AdminBookTagViewSet, AdminBookAuthorViewSet, AdminBookAttributeViewSet, AdminBookReferenceImageViewSet, AdminBookReferenceDocumentViewSet, AdminBookReferenceViewSet,
AdminTransmitterViewSet, AdminTransmitterReliabilityViewSet, DovodiBookSubjectAreaViewSet, DovodiBookTypeViewSet, AdminTransmitterViewSet, AdminTransmitterReliabilityViewSet, DovodiBookSubjectAreaViewSet, DovodiBookTypeViewSet,
DovodiNarratorLayerViewSet, DovodiTransmitterReliabilityViewSet, DovodiOpinionStatusViewSet, DovodiNarratorLayerViewSet, DovodiTransmitterReliabilityViewSet, DovodiOpinionStatusViewSet,
DovodiHadisSectViewSet, DovodiHadisCategoryViewSet, AdminCategoryQuranVerseViewSet, AdminHadisTransmitterViewSet, DovodiHadisSectViewSet, DovodiHadisCategoryViewSet, AdminCategoryQuranVerseViewSet, AdminHadisTransmitterViewSet,
@ -43,6 +43,7 @@ admin_router.register(r'collections', AdminHadisCollectionViewSet, basename='adm
admin_router.register(r'references', AdminBookReferenceViewSet, basename='admin-book-references') admin_router.register(r'references', AdminBookReferenceViewSet, basename='admin-book-references')
admin_router.register(r'subject-areas', AdminBookSubjectAreaViewSet, basename='admin-book-subject-areas') admin_router.register(r'subject-areas', AdminBookSubjectAreaViewSet, basename='admin-book-subject-areas')
admin_router.register(r'book-types', AdminBookTypeViewSet, basename='admin-book-types') admin_router.register(r'book-types', AdminBookTypeViewSet, basename='admin-book-types')
admin_router.register(r'book-tags', AdminBookTagViewSet, basename='admin-book-tags')
admin_router.register(r'dovodi-subject-areas', DovodiBookSubjectAreaViewSet, basename='dovodi-book-subject-areas') admin_router.register(r'dovodi-subject-areas', DovodiBookSubjectAreaViewSet, basename='dovodi-book-subject-areas')
admin_router.register(r'dovodi-book-types', DovodiBookTypeViewSet, basename='dovodi-book-types') admin_router.register(r'dovodi-book-types', DovodiBookTypeViewSet, basename='dovodi-book-types')
admin_router.register(r'book-authors', AdminBookAuthorViewSet, basename='admin-book-authors') admin_router.register(r'book-authors', AdminBookAuthorViewSet, basename='admin-book-authors')
@ -112,9 +113,12 @@ urlpatterns = [
path('narrators/', TransmitterView.as_view(), name='narrators'), path('narrators/', TransmitterView.as_view(), name='narrators'),
# Reference paths # Reference paths
path('v2/references/<str:reference_slug>/excerpts/', ReferenceExcerptsListView.as_view(), name='reference-excerpts-v2'),
path('v2/references/<str:slug>/', BookReferenceV2DetailView.as_view(), name='reference-detail-v2'), path('v2/references/<str:slug>/', BookReferenceV2DetailView.as_view(), name='reference-detail-v2'),
path('v2/references/', BookReferenceV2ListView.as_view(), name='reference-list-v2'), path('v2/references/', BookReferenceV2ListView.as_view(), name='reference-list-v2'),
path('references/<str:reference_slug>', BookDetailView.as_view(), name='reference-detail'),
path('references/<str:reference_slug>/excerpts/', ReferenceExcerptsListView.as_view(), name='reference-excerpts'),
path('references/<str:reference_slug>/', BookDetailView.as_view(), name='reference-detail'),
path('references/<str:reference_slug>', BookDetailView.as_view(), name='reference-detail-no-slash'),
path('references/', BookReferencesView.as_view(), name='references'), path('references/', BookReferencesView.as_view(), name='references'),
# Hadis detail paths (with slug, more specific) # Hadis detail paths (with slug, more specific)
@ -122,6 +126,7 @@ urlpatterns = [
path('<str:hadis_slug>/transmitters/', HadisTransmittersView.as_view(), name='hadis-transmitters'), path('<str:hadis_slug>/transmitters/', HadisTransmittersView.as_view(), name='hadis-transmitters'),
path('<str:hadis_slug>/transmitters/layers/', HadisLayersView.as_view(), name='hadis-layers'), path('<str:hadis_slug>/transmitters/layers/', HadisLayersView.as_view(), name='hadis-layers'),
path('<str:hadis_slug>/corrections/', HadisCorrectionsView.as_view(), name='hadis-corrections'), path('<str:hadis_slug>/corrections/', HadisCorrectionsView.as_view(), name='hadis-corrections'),
path('<str:hadis_slug>/interprets/', HadisInterpretsListView.as_view(), name='hadis-interprets'),
path('<str:hadis_slug>/', HadisBasicView.as_view(), name='hadis-basic'), # ← Least specific LAST path('<str:hadis_slug>/', HadisBasicView.as_view(), name='hadis-basic'), # ← Least specific LAST
# path('test/test-deploy',test_deploy , name='test'), # path('test/test-deploy',test_deploy , name='test'),

40
apps/hadis/views/hadis.py

@ -935,3 +935,43 @@ class HadisInterpretationDetailView(RetrieveAPIView):
self.check_object_permissions(self.request, obj) self.check_object_permissions(self.request, obj)
return obj return obj
class HadisInterpretsListView(ListAPIView):
"""
API View to list all interpretations for the category of a specific Hadis.
"""
serializer_class = HadisInterpretationDetailSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [AllowAny]
pagination_class = StandardResultsSetPagination
lookup_field = 'slug'
lookup_url_kwarg = 'hadis_slug'
@swagger_auto_schema(
operation_summary="Get Hadis Interpretations",
operation_description="Returns a paginated list of all interpretations registered for the category of this hadis, with full details and references.",
tags=['Dobodbi - Hadis (V2)'],
)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def get_queryset(self):
hadis_slug = self.kwargs.get('hadis_slug')
try:
hadis = Hadis.objects.select_related('category').get(slug=hadis_slug, status=True)
if not hadis.category_id:
return HadisInterpretation.objects.none()
return HadisInterpretation.objects.filter(
category_id=hadis.category_id
).select_related(
'category'
).prefetch_related(
'references__book_reference__author',
'references__edition',
'references__book_volume',
'references__images'
).order_by('priority', 'id')
except Hadis.DoesNotExist:
return HadisInterpretation.objects.none()

52
apps/hadis/views/reference.py

@ -1,10 +1,12 @@
from rest_framework.authentication import TokenAuthentication from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.generics import ListAPIView, RetrieveAPIView, ListCreateAPIView from rest_framework.generics import ListAPIView, RetrieveAPIView, ListCreateAPIView
from rest_framework.response import Response from rest_framework.response import Response
from django.db.models import Q from django.db.models import Q
from ..models import BookReference , BookAuthor , BookReferenceImage, BookAttribute
from drf_yasg.utils import swagger_auto_schema
from ..models import BookReference, BookAuthor, BookReferenceImage, BookAttribute, Hadis
from ..serializers.reference import BookAuthorSerializer, BookDetailSerializer, BookReferenceSerializer, BookReferenceSyncSerializer, BookAttributeSerializer from ..serializers.reference import BookAuthorSerializer, BookDetailSerializer, BookReferenceSerializer, BookReferenceSyncSerializer, BookAttributeSerializer
from ..serializers.hadis import HadisListSerializer
from ..serializers.category import get_localized_text from ..serializers.category import get_localized_text
from ..docs import book_attributes_create_swagger, book_attributes_list_swagger, book_references_list_swagger, book_authors_list_swagger, book_detail_swagger, reference_sync_swagger from ..docs import book_attributes_create_swagger, book_attributes_list_swagger, book_references_list_swagger, book_authors_list_swagger, book_detail_swagger, reference_sync_swagger
from utils.pagination import NoPagination from utils.pagination import NoPagination
@ -218,3 +220,49 @@ class BookAttributeView(ListCreateAPIView):
return self.create(request, *args, **kwargs) return self.create(request, *args, **kwargs)
class ReferenceExcerptsListView(ListAPIView):
"""
API view to retrieve all hadiths associated with a specific book reference (excerpts)
"""
serializer_class = HadisListSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [AllowAny]
pagination_class = StandardResultsSetPagination
lookup_field = 'slug'
lookup_url_kwarg = 'reference_slug'
@swagger_auto_schema(
operation_summary="Get Reference Excerpts (Hadiths)",
operation_description="Returns a paginated list of all hadiths connected to this book reference.",
tags=['Dobodbi - Hadis (V2)'],
)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def get_queryset(self):
reference_slug = self.kwargs.get('reference_slug')
return Hadis.objects.filter(
references__book_reference__slug=reference_slug,
status=True
).select_related(
'category__sect',
'hadis_status'
).distinct().order_by('id')
def get_serializer_context(self):
context = super().get_serializer_context()
user = self.request.user
if user.is_authenticated:
from apps.bookmark.models.bookmark import Bookmark
user_bookmarks = Bookmark.objects.filter(
user=user,
service=Bookmark.ServiceChoices.HADITH,
status=True
).values_list('content_id', flat=True)
context['user_bookmarked_hadis_ids'] = set(user_bookmarks)
else:
context['user_bookmarked_hadis_ids'] = set()
return context

2
apps/hadis/views/reference_v2.py

@ -156,7 +156,7 @@ class BookReferenceV2ListView(generics.ListAPIView):
return values return values
def get_queryset(self): def get_queryset(self):
qs = BookReference.objects.select_related('author').prefetch_related('tags', 'volumes__images').order_by('-created_at')
qs = BookReference.objects.select_related('author', 'tag').prefetch_related('volumes__images').order_by('-created_at')
search = self.request.query_params.get('search') search = self.request.query_params.get('search')
if search: if search:

19
apps/hadis/views_admin.py

@ -11,7 +11,7 @@ from utils.pagination import StandardResultsSetPagination, OptionalPagination
from .models import ( from .models import (
Hadis, HadisCategory, HadisCollection, HadisInCollection, HadisCorrection, HadisInterpretation, HadisStatus, HadisTag, HadisSect, HadisTransmitter, Hadis, HadisCategory, HadisCollection, HadisInCollection, HadisCorrection, HadisInterpretation, HadisStatus, HadisTag, HadisSect, HadisTransmitter,
BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAttribute, BookSubjectArea, BookType, BookEdition, BookVolume,
BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume,
Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, TransmitterOpinion, TransmitterOriginalText Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, TransmitterOpinion, TransmitterOriginalText
) )
from .serializers.serializers_admin import ( from .serializers.serializers_admin import (
@ -29,6 +29,7 @@ from .serializers.serializers_admin import (
AdminHadisTagSerializer, AdminHadisTagSerializer,
AdminBookSubjectAreaSerializer, AdminBookSubjectAreaSerializer,
AdminBookTypeSerializer, AdminBookTypeSerializer,
AdminBookTagSerializer,
AdminBookAuthorSerializer, AdminBookAuthorSerializer,
AdminBookAttributeSerializer, AdminBookAttributeSerializer,
AdminBookReferenceImageSerializer, AdminBookReferenceImageSerializer,
@ -770,11 +771,27 @@ class AdminBookEditionViewSet(ModelViewSet):
return queryset.order_by("-created_at") return queryset.order_by("-created_at")
class AdminBookTagViewSet(ModelViewSet):
permission_classes = [IsAuthenticated, IsSuperAdmin]
authentication_classes = [TokenAuthentication]
serializer_class = AdminBookTagSerializer
pagination_class = None
parser_classes = (JSONParser, MultiPartParser, FormParser)
def get_queryset(self):
queryset = BookTag.objects.all()
search_query = self.request.query_params.get("search")
if search_query:
queryset = queryset.filter(title__icontains=search_query)
return queryset.order_by("-id")
class AdminBookVolumeViewSet(ModelViewSet): class AdminBookVolumeViewSet(ModelViewSet):
permission_classes = [IsAuthenticated, IsSuperAdmin] permission_classes = [IsAuthenticated, IsSuperAdmin]
authentication_classes = [TokenAuthentication] authentication_classes = [TokenAuthentication]
serializer_class = AdminBookVolumeSerializer serializer_class = AdminBookVolumeSerializer
pagination_class = None pagination_class = None
parser_classes = (MultiPartParser, FormParser, JSONParser)
def get_queryset(self): def get_queryset(self):
queryset = BookVolume.objects.all() queryset = BookVolume.objects.all()

Loading…
Cancel
Save