Browse Source

references filters added to the apis , list of needed filters also added. new tag model for references added too

master
Mohsen Taba 1 month ago
parent
commit
6b306b6cb1
  1. 57
      apps/hadis/admin/reference.py
  2. 42
      apps/hadis/migrations/0017_booktag_alter_bookreference_subject_area_and_more.py
  3. 32
      apps/hadis/models/reference.py
  4. 26
      apps/hadis/serializers/reference_v2.py
  5. 3
      apps/hadis/urls.py
  6. 79
      apps/hadis/views/reference.py
  7. 158
      apps/hadis/views/reference_v2.py

57
apps/hadis/admin/reference.py

@ -16,7 +16,8 @@ from ..models import (
BookAuthor,
BookAttribute,
BookSubjectArea,
BookType
BookType,
BookTag
)
# -----------------------------------------------------------------------------
@ -353,6 +354,43 @@ class BookSubjectAreaAdminForm(forms.ModelForm):
})
class BookTagAdminForm(forms.ModelForm):
"""Custom form for BookTag with JSON editor widgets"""
class Meta:
model = BookTag
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
title_schema = {
"type": "array",
"title": "Titles",
"items": {
"type": "object",
"title": "Title",
"properties": {
"language_code": {
"type": "string",
"title": "Language Code",
"enum": ["en", "fa", "ar", "ur", "ru"],
},
"text": {
"type": "string",
"title": "Title Text"
}
},
"required": ["language_code", "text"]
}
}
self.fields['title'].widget = JsonEditorWidget(attrs={
'schema': json.dumps(title_schema),
'title': 'Titles'
})
class BookTypeAdminForm(forms.ModelForm):
"""Custom form for BookType with JSON editor widgets"""
@ -449,11 +487,11 @@ class BookReferenceAdmin(ModelAdmin):
inlines = [BookReferenceImageInline, BookAttributeInline]
# Use filter_horizontal for ManyToMany fields to make selection easier
filter_horizontal = ('subject_area',)
filter_horizontal = ('subject_area', 'tags')
fieldsets = (
(_('Basic Info'), {
'fields': ('title', 'description', 'slug', 'language')
'fields': ('title', 'description', 'slug', 'language', 'tags', 'type')
}),
(_('Publication Info'), {
'fields': ('number_page',)
@ -572,6 +610,18 @@ class BookAttributeAdmin(ModelAdmin):
return '-'
class BookTagAdmin(ModelAdmin):
form = BookTagAdminForm
list_display = ('get_title_display', 'created_at')
@display(description=_('Title'), ordering='title')
def get_title_display(self, obj):
if obj.title and isinstance(obj.title, list) and len(obj.title) > 0:
first = obj.title[0]
if isinstance(first, dict):
return first.get('text', '-')
return '-'
# class BookSubjectAreaAdmin(ModelAdmin):
# """Admin for BookSubjectArea model"""
# form = BookSubjectAreaAdminForm
@ -622,5 +672,6 @@ dovoodi_admin_site.register(BookReference, BookReferenceAdmin)
dovoodi_admin_site.register(BookAuthor, BookAuthorAdmin)
dovoodi_admin_site.register(BookAttribute, BookAttributeAdmin)
dovoodi_admin_site.register(BookReferenceImage, BookReferenceImageAdmin)
dovoodi_admin_site.register(BookTag, BookTagAdmin)
# dovoodi_admin_site.register(BookSubjectArea, BookSubjectAreaAdmin)
# dovoodi_admin_site.register(BookType, BookTypeAdmin)

42
apps/hadis/migrations/0017_booktag_alter_bookreference_subject_area_and_more.py

@ -0,0 +1,42 @@
# Generated by Django 5.2.12 on 2026-06-20 13:00
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hadis', '0016_remove_bookreference_city_of_publication_and_more'),
]
operations = [
migrations.CreateModel(
name='BookTag',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.JSONField(default=list, 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')),
],
options={
'verbose_name': 'Book Tag',
'verbose_name_plural': 'Book Tags',
},
),
migrations.AlterField(
model_name='bookreference',
name='subject_area',
field=models.ManyToManyField(blank=True, to='hadis.booksubjectarea', verbose_name='Subject Area'),
),
migrations.AlterField(
model_name='bookreference',
name='type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hadis.booktype', verbose_name='Type'),
),
migrations.AddField(
model_name='bookreference',
name='tags',
field=models.ManyToManyField(blank=True, related_name='book_references', to='hadis.booktag', verbose_name='Tags'),
),
]

32
apps/hadis/models/reference.py

@ -30,6 +30,24 @@ class BookSubjectArea(models.Model):
return None
class BookTag(models.Model):
"""
Model for book reference tags
"""
title = models.JSONField(default=list, 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'))
class Meta:
verbose_name = _('Book Tag')
verbose_name_plural = _('Book Tags')
def __str__(self):
try:
return self.title[0]['text']
except:
return f"Tag {self.id}"
class BookType(models.Model):
title = models.JSONField(default = list , verbose_name=_('Title'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
@ -66,8 +84,18 @@ class BookReference(models.Model):
language = models.JSONField(default = list , verbose_name=_('Language'))
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)
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'))
subject_area = models.ManyToManyField(
'BookSubjectArea',
verbose_name=_('Subject Area'),
blank=True
)
tags = models.ManyToManyField(
'BookTag',
verbose_name=_('Tags'),
blank=True,
related_name='book_references'
)
type = models.ForeignKey('BookType', on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Type'))
order = models.IntegerField(default=0, verbose_name=_('Order'))
researcher = models.JSONField(default=list, blank=True, verbose_name=_('Researcher'))
rate = models.DecimalField(

26
apps/hadis/serializers/reference_v2.py

@ -1,15 +1,16 @@
from rest_framework import serializers
from rest_framework.pagination import PageNumberPagination
from ..models import BookReference, BookAuthor, BookEdition, BookVolume
from ..models import BookReference, BookAuthor, BookEdition, BookVolume, BookTag
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 BookTagV2Serializer(serializers.ModelSerializer):
title = LocalizedField()
class Meta:
model = BookTag
fields = ['id', 'title']
class BookAuthorV2Serializer(serializers.ModelSerializer):
name = LocalizedField()
@ -17,6 +18,19 @@ class BookAuthorV2Serializer(serializers.ModelSerializer):
model = BookAuthor
fields = ['id', 'name', 'slug']
class BookReferenceV2ListSerializer(serializers.ModelSerializer):
title = LocalizedField()
authors = BookAuthorV2Serializer(many=True, read_only=True)
tags = BookTagV2Serializer(many=True, read_only=True)
number_of_volumes = serializers.SerializerMethodField()
class Meta:
model = BookReference
fields = ['id', 'slug', 'title', 'authors', 'number_of_volumes', 'tags']
def get_number_of_volumes(self, obj):
return obj.volumes.count()
class BookEditionV2Serializer(serializers.ModelSerializer):
publisher = LocalizedField()
notes = LocalizedField()

3
apps/hadis/urls.py

@ -4,7 +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.transmitter import TransmitterView ,TransmitterDetailView, TransmitterSyncView,TransmitterOpinionView, TransmitterOriginalTextView, TransmitterFiltersView
from .views.reference import BookDetailView, BookReferencesView, BookReferenceSyncView, BookAttributeView
from .views.reference_v2 import BookReferenceV2DetailView, BookReferenceV2SyncView
from .views.reference_v2 import BookReferenceV2DetailView, BookReferenceV2SyncView, BookReferenceV2ListView
from .views.version import ContentReleaseSyncView
from .views.info import HadisInfoView
from .views_admin import (
@ -89,6 +89,7 @@ urlpatterns = [
# Reference paths
path('v2/references/<str:slug>/', BookReferenceV2DetailView.as_view(), name='reference-detail-v2'),
path('v2/references/', BookReferenceV2ListView.as_view(), name='reference-list-v2'),
path('references/<str:reference_slug>', BookDetailView.as_view(), name='reference-detail'),
path('references/', BookReferencesView.as_view(), name='references'),

79
apps/hadis/views/reference.py

@ -99,17 +99,74 @@ class BookReferenceSyncView(ListAPIView):
"""
Prefetch ALL related data to avoid N+1 queries
"""
return (
BookReference.objects
.select_related() # If any ForeignKey fields
.prefetch_related(
'authors',
'attributes', # ← ADDED
'images', # ← ADDED
'hadis_references__hadis'
)
.order_by('id')
)
qs = BookReference.objects.select_related().prefetch_related(
'authors', 'attributes', 'images', 'hadis_references__hadis'
).distinct().order_by('id')
category = self.request.query_params.get('category')
if category:
qs = qs.filter(subject_area__id=category)
author = self.request.query_params.get('author')
if author:
qs = qs.filter(authors__id=author)
sect = self.request.query_params.get('sect')
if sect:
qs = qs.filter(type__id=sect)
pdf_available = self.request.query_params.get('pdf_available')
if pdf_available == 'true':
qs = qs.filter(documents__isnull=False)
elif pdf_available == 'false':
qs = qs.filter(documents__isnull=True)
century = self.request.query_params.get('century')
if century:
if century == 'before_islam':
qs = qs.filter(authors__death_year_hijri__lt=0)
elif century.isdigit():
century_num = int(century)
start_year = (century_num - 1) * 100 + 1
end_year = century_num * 100
qs = qs.filter(authors__death_year_hijri__gte=start_year, authors__death_year_hijri__lte=end_year)
return qs
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
# If pagination is active, paginate it (though it's NoPagination)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
else:
serializer = self.get_serializer(queryset, many=True)
from ..models import BookSubjectArea, BookType
from ..serializers.category import get_localized_text
categories_qs = BookSubjectArea.objects.all()
categories = [{"id": c.id, "title": get_localized_text(c.title, request)} for c in categories_qs]
sects_qs = BookType.objects.all()
sects = [{"id": s.id, "title": get_localized_text(s.title, request)} for s in sects_qs]
filters_metadata = {
"categories": categories,
"madhab": sects
}
if page is not None:
response = self.get_paginated_response(serializer.data)
response.data['filters'] = filters_metadata
return response
return Response({
"count": queryset.count(),
"filters": filters_metadata,
"results": serializer.data
})
class BookAttributeView(ListCreateAPIView):

158
apps/hadis/views/reference_v2.py

@ -54,16 +54,148 @@ class BookReferenceV2SyncView(generics.ListAPIView):
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')
)
qs = BookReference.objects.select_related().prefetch_related(
'authors', 'attributes', 'images', 'editions__volumes', 'volumes', 'hadis_references__hadis'
).distinct().order_by('id')
category = self.request.query_params.get('category')
if category:
qs = qs.filter(subject_area__id=category)
author = self.request.query_params.get('author')
if author:
qs = qs.filter(authors__id=author)
sect = self.request.query_params.get('sect')
if sect:
qs = qs.filter(type__id=sect)
pdf_available = self.request.query_params.get('pdf_available')
if pdf_available == 'true':
qs = qs.filter(documents__isnull=False)
elif pdf_available == 'false':
qs = qs.filter(documents__isnull=True)
century = self.request.query_params.get('century')
if century:
if century == 'before_islam':
qs = qs.filter(authors__death_year_hijri__lt=0)
elif century.isdigit():
century_num = int(century)
start_year = (century_num - 1) * 100 + 1
end_year = century_num * 100
qs = qs.filter(authors__death_year_hijri__gte=start_year, authors__death_year_hijri__lte=end_year)
return qs
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
categories_qs = BookSubjectArea.objects.all()
categories = [{"id": c.id, "title": get_localized_text(c.title, request)} for c in categories_qs]
sects_qs = BookType.objects.all()
sects = [{"id": s.id, "title": get_localized_text(s.title, request)} for s in sects_qs]
filters_metadata = {
"categories": categories,
"madhab": sects
}
return Response({
"count": queryset.count(),
"filters": filters_metadata,
"results": serializer.data
})
from rest_framework.response import Response
from ..serializers.reference_v2 import BookReferenceV2ListSerializer
from ..models import BookAuthor, BookSubjectArea, BookType
from utils.pagination import StandardResultsSetPagination
from ..serializers.category import get_localized_text
from drf_yasg import openapi
class BookReferenceV2ListView(generics.ListAPIView):
"""
API view to list V2 book references with filters and metadata
"""
serializer_class = BookReferenceV2ListSerializer
pagination_class = StandardResultsSetPagination
@swagger_auto_schema(
operation_summary="List V2 Book References with Filters",
operation_description="Returns a paginated list of book references and available filters (categories, sects).\n"
"Use query parameters to filter the results.",
tags=['Dobodbi - Hadis (V2)'],
manual_parameters=[
openapi.Parameter('category', openapi.IN_QUERY, description="Filter by Category/Subject Area ID", type=openapi.TYPE_INTEGER),
openapi.Parameter('author', openapi.IN_QUERY, description="Filter by Author ID", type=openapi.TYPE_INTEGER),
openapi.Parameter('sect', openapi.IN_QUERY, description="Filter by Sect/Type ID", type=openapi.TYPE_INTEGER),
openapi.Parameter('pdf_available', openapi.IN_QUERY, description="Filter by PDF availability ('true' or 'false')", type=openapi.TYPE_STRING),
openapi.Parameter('century', openapi.IN_QUERY, description="Filter by Century (e.g. '1', '2', 'before_islam')", type=openapi.TYPE_STRING),
]
)
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
def get_queryset(self):
qs = BookReference.objects.prefetch_related('authors', 'tags', 'volumes').distinct().order_by('-created_at')
category = self.request.query_params.get('category')
if category:
qs = qs.filter(subject_area__id=category)
author = self.request.query_params.get('author')
if author:
qs = qs.filter(authors__id=author)
sect = self.request.query_params.get('sect')
if sect:
qs = qs.filter(type__id=sect)
pdf_available = self.request.query_params.get('pdf_available')
if pdf_available == 'true':
qs = qs.filter(documents__isnull=False)
elif pdf_available == 'false':
qs = qs.filter(documents__isnull=True)
century = self.request.query_params.get('century')
if century:
if century == 'before_islam':
qs = qs.filter(authors__death_year_hijri__lt=0)
elif century.isdigit():
century_num = int(century)
start_year = (century_num - 1) * 100 + 1
end_year = century_num * 100
qs = qs.filter(authors__death_year_hijri__gte=start_year, authors__death_year_hijri__lte=end_year)
return qs
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
serializer = self.get_serializer(page if page is not None else queryset, many=True)
categories_qs = BookSubjectArea.objects.all()
categories = [{"id": c.id, "title": get_localized_text(c.title, request)} for c in categories_qs]
sects_qs = BookType.objects.all()
sects = [{"id": s.id, "title": get_localized_text(s.title, request)} for s in sects_qs]
filters_metadata = {
"categories": categories,
"madhab": sects
}
if page is not None:
response = self.get_paginated_response(serializer.data)
# Add filters directly into the response dict
response.data['filters'] = filters_metadata
return response
return Response({
"filters": filters_metadata,
"results": serializer.data
})
Loading…
Cancel
Save