diff --git a/apps/hadis/docs.py b/apps/hadis/docs.py index b1d1878..8b8c89c 100644 --- a/apps/hadis/docs.py +++ b/apps/hadis/docs.py @@ -2,7 +2,6 @@ from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from rest_framework import status - # Swagger documentation for HadisSectListView hadis_sect_list_swagger = swagger_auto_schema( operation_description="Get list of all active Hadis sects grouped by sect type (Shia/Sunni)", @@ -1362,6 +1361,143 @@ book_authors_list_swagger = swagger_auto_schema( } ) +# ============================================================================ +# GET - List all book attributes +# ============================================================================ +book_attributes_list_swagger = swagger_auto_schema( + operation_description="Retrieve all custom attributes for books. Optionally filter by book reference ID or attribute title.", + operation_summary="List Book Attributes", + tags=['Hadis'], + manual_parameters=[ + openapi.Parameter( + 'book_reference', + openapi.IN_QUERY, + description='Filter attributes by book reference ID (optional)', + type=openapi.TYPE_INTEGER, + required=False, + ), + openapi.Parameter( + 'title', + openapi.IN_QUERY, + description='Filter attributes by title - partial match (optional)', + type=openapi.TYPE_STRING, + required=False, + ), + openapi.Parameter( + 'limit', + openapi.IN_QUERY, + description='Number of results per page', + type=openapi.TYPE_INTEGER, + required=False, + ), + openapi.Parameter( + 'offset', + openapi.IN_QUERY, + description='Starting index for pagination', + type=openapi.TYPE_INTEGER, + required=False, + ), + ], + responses={ + status.HTTP_200_OK: openapi.Response( + description="List of book attributes retrieved successfully", + examples={ + "application/json": { + "count": 2, + "next": None, + "previous": None, + "results": [ + { + "id": 1, + "title": "Number of Hadith", + "value": "7,563", + "book_reference": 1 + }, + { + "id": 2, + "title": "Authenticity Grade", + "value": "Sahih (Authentic)", + "book_reference": 1 + } + ] + } + } + ), + status.HTTP_400_BAD_REQUEST: openapi.Response( + description="Invalid query parameters", + examples={ + "application/json": { + "error": "Invalid book_reference ID" + } + } + ), + status.HTTP_500_INTERNAL_SERVER_ERROR: openapi.Response( + description="Internal server error" + ) + } +) + +# ============================================================================ +# POST - Create a new book attribute +# ============================================================================ +book_attributes_create_swagger = swagger_auto_schema( + operation_description="Create a new custom attribute for a book. Attributes can store additional metadata about hadith books such as number of hadith, authenticity grade, or other relevant information.", + operation_summary="Create Book Attribute", + tags=['Hadis'], + request_body=openapi.Schema( + type=openapi.TYPE_OBJECT, + required=['title', 'value', 'book_reference'], + properties={ + 'title': openapi.Schema( + type=openapi.TYPE_STRING, + description='Attribute title/name (e.g., "Number of Hadith")', + example='Collection Type' + ), + 'value': openapi.Schema( + type=openapi.TYPE_STRING, + description='Attribute value (e.g., "7,563")', + example='Hadith Compilation' + ), + 'book_reference': openapi.Schema( + type=openapi.TYPE_INTEGER, + description='ID of the book this attribute belongs to', + example=2 + ), + } + ), + responses={ + status.HTTP_201_CREATED: openapi.Response( + description="Book attribute created successfully", + examples={ + "application/json": { + "id": 3, + "title": "Collection Type", + "value": "Hadith Compilation", + "book_reference": 2 + } + } + ), + status.HTTP_400_BAD_REQUEST: openapi.Response( + description="Invalid input data", + examples={ + "application/json": { + "title": ["This field is required."], + "value": ["This field is required."], + "book_reference": ["Invalid book reference ID."] + } + } + ), + status.HTTP_401_UNAUTHORIZED: openapi.Response( + description="Authentication required - provide a valid token" + ), + status.HTTP_403_FORBIDDEN: openapi.Response( + description="Permission denied - you do not have permission to create attributes" + ), + status.HTTP_500_INTERNAL_SERVER_ERROR: openapi.Response( + description="Internal server error" + ) + } +) # Swagger documentation for BookDetailView book_detail_swagger = swagger_auto_schema( @@ -1383,12 +1519,12 @@ book_detail_swagger = swagger_auto_schema( tags=['Hadis'], manual_parameters=[ openapi.Parameter( - 'bookreference_id', + 'reference_slug', openapi.IN_PATH, - description="Unique identifier of the book reference", - type=openapi.TYPE_INTEGER, + description="Unique slug of the book reference", + type=openapi.TYPE_STRING, required=True, - example=2 + example='sunan-ibn-majah' ) ], responses={ diff --git a/apps/hadis/models/category.py b/apps/hadis/models/category.py index 714b436..2a90f8f 100644 --- a/apps/hadis/models/category.py +++ b/apps/hadis/models/category.py @@ -46,7 +46,17 @@ class HadisCategory(MPTTModel): language = None language_id = None + def clean(self): + super().clean() + if self.parent and self.sect_id != self.parent.sect_id: + raise ValidationError( + _('Child category must have the same sect_type as its parent. ' + f'Parent sect: {self.parent.sect.sect_type}, ' + f'Your sect: {self.sect.sect_type}') + ) + def save(self, *args, **kwargs): + self.full_clean() if not self.slug: base_slug = slugify(self.title, allow_unicode=True) slug = base_slug diff --git a/apps/hadis/serializers/reference.py b/apps/hadis/serializers/reference.py index b96e76a..785b0d2 100644 --- a/apps/hadis/serializers/reference.py +++ b/apps/hadis/serializers/reference.py @@ -1,6 +1,6 @@ from rest_framework import serializers -from ..serializers import HadisListSerializer +from ..serializers import HadisListSerializer,HadisBasicSerializer from ..models import BookReference , BookAuthor , BookReferenceImage, HadisReference , BookAttribute class BookAuthorSerializer(serializers.ModelSerializer): @@ -22,7 +22,7 @@ class BookReferenceSerializer(serializers.ModelSerializer): author = serializers.SerializerMethodField() class Meta: model = BookReference - fields = ['id','title','rate','author','description','image','volume'] + fields = ['id','title','slug','rate','author','description','image','volume'] def get_author (self,obj): author = obj.authors.all() return [ @@ -56,16 +56,24 @@ class BookDetailSerializer(serializers.ModelSerializer): source='images' ) - hadis = HadisListSerializer( - many=True, - read_only=True, - source='hadis_references' - ) + # hadis = HadisListSerializer( + # many=True, + # read_only=True, + # source='hadis_references__hadis' + # ) + + hadis = serializers.SerializerMethodField() class Meta: model = BookReference fields = ['id','title','rate','isbn','language','number_page','publisher','description','volume','slug','attribute','author','image','hadis'] + def get_hadis(self,obj): + references = obj.hadis_references.all() + hadis_list = [ref.hadis for ref in references if ref.hadis] + return HadisBasicSerializer(hadis_list,many=True).data + + class BookReferenceSyncSerializer(serializers.ModelSerializer): """Serializer for syncing all book reference data for offline mode""" diff --git a/apps/hadis/urls.py b/apps/hadis/urls.py index bd7342f..8a5735b 100644 --- a/apps/hadis/urls.py +++ b/apps/hadis/urls.py @@ -29,6 +29,6 @@ urlpatterns = [ path('narrators//opinions',TransmitterOpinionView.as_view(), name='narrator-opinions'), path('narrators//original_texts',TransmitterOriginalTextView.as_view(), name='narrator-original-texts'), path('references/',BookReferencesView.as_view(), name='references'), - path('references/',BookDetailView.as_view(), name='reference-detail'), + path('references/',BookDetailView.as_view(), name='reference-detail'), path('references/attributes/',BookAttributeView.as_view(), name='book-attributes'), ] \ No newline at end of file diff --git a/apps/hadis/views/category.py b/apps/hadis/views/category.py index 0a30cc4..b32e270 100644 --- a/apps/hadis/views/category.py +++ b/apps/hadis/views/category.py @@ -212,6 +212,8 @@ class HadisCategorySelectBySectView(ListAPIView): def get_queryset(self): sect_type = self.kwargs.get('sect_type') slug = self.kwargs.get('slug') + print(slug) + print(sect_type) # Find the parent category by slug and sect_type try: @@ -221,6 +223,7 @@ class HadisCategorySelectBySectView(ListAPIView): sect__is_active=True ) except HadisCategory.DoesNotExist: + print('not ok') return HadisCategory.objects.none() # Return children of this category, filtered as before diff --git a/apps/hadis/views/reference.py b/apps/hadis/views/reference.py index d545232..2b7693d 100644 --- a/apps/hadis/views/reference.py +++ b/apps/hadis/views/reference.py @@ -2,7 +2,7 @@ from rest_framework.generics import ListAPIView, RetrieveAPIView,ListCreateAPIVi from rest_framework.response import Response from ..models import BookReference , BookAuthor , BookReferenceImage, BookAttribute from ..serializers.reference import BookAuthorSerializer, BookDetailSerializer , BookReferenceSerializer, BookReferenceSyncSerializer, BookAttributeSerializer -from ..docs import 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 @@ -26,18 +26,19 @@ class BookAuthorView(ListAPIView): class BookDetailView(RetrieveAPIView): serializer_class = BookDetailSerializer - lookup_field = 'id' - lookup_url_kwarg = 'bookreference_id' + lookup_field = 'slug' + lookup_url_kwarg = 'reference_slug' @book_detail_swagger def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): - return BookReference.objects.filter(id = self.kwargs['bookreference_id']).prefetch_related( - 'authors__name', - 'images__image', - ) + return BookReference.objects.filter(slug = self.kwargs.get('reference_slug')) + # .prefetch_related( + # 'authors__name', + # 'images__image', + # ) class BookReferenceSyncView(ListAPIView): @@ -84,3 +85,12 @@ class BookAttributeView(ListCreateAPIView): queryset = BookAttribute.objects.all() serializer_class = BookAttributeSerializer + @book_attributes_list_swagger + def get(self, request, *args, **kwargs): + return self.list(request, *args, **kwargs) + + @book_attributes_create_swagger + def post(self, request, *args, **kwargs): + return self.create(request, *args, **kwargs) + +