mortezaei 6 months ago
parent
commit
e093b6f6ce
  1. 63
      apps/api/views/documentation.py
  2. 3
      apps/article/serializers.py
  3. 44
      apps/article/views.py
  4. 2
      apps/hadis/serializers/hadis.py
  5. 47
      apps/library/doc.py
  6. 18
      apps/library/migrations/0007_auto_20251203_1529.py
  7. 30
      apps/library/migrations/0008_auto_20251203_1533.py
  8. 8
      apps/library/models.py
  9. 3
      apps/library/serializers.py
  10. 26
      apps/library/views.py
  11. 7
      apps/podcast/serializers.py
  12. 35
      apps/podcast/views.py

63
apps/api/views/documentation.py

@ -296,10 +296,13 @@ class CustomAPIDocumentationView(View):
'name': 'Book List', 'name': 'Book List',
'method': 'GET', 'method': 'GET',
'url': '/api/library/books/', 'url': '/api/library/books/',
'description': 'Get paginated list of books',
'description': 'Get paginated list of books with filtering and sorting',
'parameters': [ 'parameters': [
{'name': 'category', 'type': 'integer', 'description': 'Filter by category ID', 'required': False},
{'name': 'search', 'type': 'string', 'description': 'Search in book titles and authors', 'required': False},
{'name': 'category', 'type': 'string', 'description': 'Filter by category slug(s). Can be a single slug or comma-separated list (e.g., "slug1,slug2")', 'required': False},
{'name': 'collection_id', 'type': 'integer', 'description': 'Filter by collection ID', 'required': False},
{'name': 'is_bookmark', 'type': 'boolean', 'description': 'Filter bookmarked books (true/false)', 'required': False},
{'name': 'search', 'type': 'string', 'description': 'Search in book titles, summary, publisher, or ISBN', 'required': False},
{'name': 'sort', 'type': 'string', 'description': 'Sort by field. Options: created_at, -created_at, view_count, -view_count, download_count, -download_count, title, -title, pin, -pin, -pin,-created_at', 'required': False},
], ],
'response_examples': { 'response_examples': {
'success': json.dumps({ 'success': json.dumps({
@ -308,13 +311,28 @@ class CustomAPIDocumentationView(View):
{ {
"id": 1, "id": 1,
"title": "Al-Kafi", "title": "Al-Kafi",
"slug": "al-kafi",
"author": "Muhammad ibn Ya'qub al-Kulayni", "author": "Muhammad ibn Ya'qub al-Kulayni",
"description": "One of the most important Shia hadith collections",
"cover_image": "https://example.com/media/books/alkafi.jpg",
"file_size": "15.2 MB",
"pages": 1200,
"language": "Arabic",
"download_count": 2456
"publisher": "Islamic Publications",
"year_of_publication": "2020",
"isbn": "978-1234567890",
"language": 1,
"main_themes": ["Hadith", "Islamic Jurisprudence", "Shia Islam"],
"notable_works": ["Volume 1: Faith and Disbelief", "Volume 2: Reason and Ignorance"],
"summary": "One of the most important Shia hadith collections",
"thumbnail": "https://example.com/media/books/alkafi.jpg",
"file_type": "pdf",
"book_file": "https://example.com/media/books/alkafi.pdf",
"view_count": 5432,
"download_count": 2456,
"pin": True,
"bookmark": False,
"user_rate": {
"is_rated": True,
"rate": 5
},
"average_rate": 4.7,
"created_at": "2024-01-15T10:30:00Z"
} }
] ]
}, indent=2) }, indent=2)
@ -576,12 +594,13 @@ class CustomAPIDocumentationView(View):
'name': 'Podcast Playlist List', 'name': 'Podcast Playlist List',
'method': 'GET', 'method': 'GET',
'url': '/api/podcast/playlists/', 'url': '/api/podcast/playlists/',
'description': 'Get paginated list of podcast playlists',
'description': 'Get paginated list of podcast playlists with filtering and sorting',
'parameters': [ 'parameters': [
{'name': 'category', 'type': 'string', 'description': 'Filter by category slug', 'required': False},
{'name': 'category', 'type': 'string', 'description': 'Filter by category slug(s). Can be a single slug or comma-separated list (e.g., "slug1,slug2")', 'required': False},
{'name': 'collection', 'type': 'string', 'description': 'Filter by collection slug', 'required': False}, {'name': 'collection', 'type': 'string', 'description': 'Filter by collection slug', 'required': False},
{'name': 'is_bookmark', 'type': 'boolean', 'description': 'Filter bookmarked playlists', 'required': False}, {'name': 'is_bookmark', 'type': 'boolean', 'description': 'Filter bookmarked playlists', 'required': False},
{'name': 'search', 'type': 'string', 'description': 'Search in playlist titles', 'required': False}, {'name': 'search', 'type': 'string', 'description': 'Search in playlist titles', 'required': False},
{'name': 'sort', 'type': 'string', 'description': 'Sort by field. Options: created_at, -created_at, view_count, -view_count, title, -title, order, -order', 'required': False},
], ],
'response_examples': { 'response_examples': {
'success': json.dumps({ 'success': json.dumps({
@ -595,6 +614,7 @@ class CustomAPIDocumentationView(View):
"slogan": "Learn the basics of Islamic thought through audio", "slogan": "Learn the basics of Islamic thought through audio",
"view_count": 1234, "view_count": 1234,
"total_time_formatted": "02:45:30", "total_time_formatted": "02:45:30",
"episodes_count": 12,
"order": 1, "order": 1,
"created_at": "2024-01-15T10:30:00Z" "created_at": "2024-01-15T10:30:00Z"
} }
@ -915,12 +935,13 @@ class CustomAPIDocumentationView(View):
'name': 'Article List', 'name': 'Article List',
'method': 'GET', 'method': 'GET',
'url': '/api/article/list/', 'url': '/api/article/list/',
'description': 'Get paginated list of articles with filtering',
'description': 'Get paginated list of articles with filtering and sorting',
'parameters': [ 'parameters': [
{'name': 'category', 'type': 'string', 'description': 'Filter by category slug', 'required': False},
{'name': 'category', 'type': 'string', 'description': 'Filter by category slug(s). Can be a single slug or comma-separated list (e.g., "slug1,slug2")', 'required': False},
{'name': 'collection', 'type': 'string', 'description': 'Filter by collection slug', 'required': False}, {'name': 'collection', 'type': 'string', 'description': 'Filter by collection slug', 'required': False},
{'name': 'is_bookmark', 'type': 'boolean', 'description': 'Filter bookmarked articles', 'required': False}, {'name': 'is_bookmark', 'type': 'boolean', 'description': 'Filter bookmarked articles', 'required': False},
{'name': 'search', 'type': 'string', 'description': 'Search in article titles', 'required': False}, {'name': 'search', 'type': 'string', 'description': 'Search in article titles', 'required': False},
{'name': 'sort', 'type': 'string', 'description': 'Sort by field. Options: created_at, -created_at, view_count, -view_count, title, -title', 'required': False},
], ],
'response_examples': { 'response_examples': {
'success': json.dumps({ 'success': json.dumps({
@ -933,7 +954,21 @@ class CustomAPIDocumentationView(View):
"thumbnail": "https://example.com/media/articles/thumb1.jpg", "thumbnail": "https://example.com/media/articles/thumb1.jpg",
"description": "Краткая биография девятого имама", "description": "Краткая биография девятого имама",
"view_count": 234, "view_count": 234,
"created_at": "2025-01-15T10:30:00Z"
"created_at": "2025-01-15T10:30:00Z",
"categories": [
{
"id": 1,
"title": "Имамы",
"slug": "imams",
"acticle_count": 12
},
{
"id": 2,
"title": "Биография",
"slug": "biography",
"acticle_count": 8
}
]
} }
] ]
}, indent=2) }, indent=2)

3
apps/article/serializers.py

@ -43,10 +43,11 @@ class MiddleArticleCollectionSerializer(serializers.ModelSerializer):
class ArticleListSerializer(serializers.ModelSerializer): class ArticleListSerializer(serializers.ModelSerializer):
thumbnail = serializers.SerializerMethodField() thumbnail = serializers.SerializerMethodField()
categories = ArticleCategoryListSerializer(many=True, read_only=True)
class Meta: class Meta:
model = Article model = Article
fields = ['id', 'title', 'slug', 'thumbnail', 'description', 'view_count', 'created_at']
fields = ['id', 'title', 'slug', 'thumbnail', 'description', 'view_count', 'created_at', 'categories']
def get_thumbnail(self, obj): def get_thumbnail(self, obj):
return get_thumbs(obj.thumbnail, self.context.get('request')) return get_thumbs(obj.thumbnail, self.context.get('request'))

44
apps/article/views.py

@ -81,40 +81,47 @@ class ArticleListAPIView(generics.ListAPIView):
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
@swagger_auto_schema( @swagger_auto_schema(
operation_description="Get a list of article with optional filtering",
operation_description="Get a list of articles with optional filtering and sorting",
manual_parameters=[ manual_parameters=[
openapi.Parameter( openapi.Parameter(
name='category', name='category',
in_=openapi.IN_QUERY, in_=openapi.IN_QUERY,
description='Filter article by category slug',
description='Filter articles by category slug(s). Can be a single slug or comma-separated list of slugs',
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=False required=False
), ),
openapi.Parameter( openapi.Parameter(
name='collection', name='collection',
in_=openapi.IN_QUERY, in_=openapi.IN_QUERY,
description='Filter article by collection slug',
description='Filter articles by collection slug',
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=False required=False
), ),
openapi.Parameter( openapi.Parameter(
name='is_bookmark', name='is_bookmark',
in_=openapi.IN_QUERY, in_=openapi.IN_QUERY,
description='Filter article that are bookmarked by the user (true/false)',
description='Filter articles that are bookmarked by the user (true/false)',
type=openapi.TYPE_BOOLEAN, type=openapi.TYPE_BOOLEAN,
required=False required=False
), ),
openapi.Parameter( openapi.Parameter(
name='search', name='search',
in_=openapi.IN_QUERY, in_=openapi.IN_QUERY,
description='Search article by title',
description='Search articles by title',
type=openapi.TYPE_STRING,
required=False
),
openapi.Parameter(
name='sort',
in_=openapi.IN_QUERY,
description='Sort articles by field. Options: created_at, -created_at, view_count, -view_count, title, -title',
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=False required=False
) )
], ],
responses={ responses={
200: openapi.Response( 200: openapi.Response(
description="List of article",
description="List of articles",
schema=ArticleListSerializer(many=True) schema=ArticleListSerializer(many=True)
) )
} }
@ -123,7 +130,7 @@ class ArticleListAPIView(generics.ListAPIView):
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
queryset = Article.objects.filter(status=True).order_by('-created_at')
queryset = Article.objects.filter(status=True)
# Search by title if search parameter is provided # Search by title if search parameter is provided
search_query = self.request.query_params.get('search', None) search_query = self.request.query_params.get('search', None)
@ -131,14 +138,16 @@ class ArticleListAPIView(generics.ListAPIView):
queryset = queryset.filter(title__icontains=search_query) queryset = queryset.filter(title__icontains=search_query)
# Filter by category if provided # Filter by category if provided
category_slug = self.request.query_params.get('category', None)
if category_slug:
queryset = queryset.filter(categories__slug=category_slug)
category = self.request.query_params.get('category', None)
if category:
# Support both single slug and comma-separated list of slugs
category_slugs = [slug.strip() for slug in category.split(',')]
queryset = queryset.filter(categories__slug__in=category_slugs).distinct()
# Filter by collection if provided # Filter by collection if provided
collection_slug = self.request.query_params.get('collection', None) collection_slug = self.request.query_params.get('collection', None)
if collection_slug: if collection_slug:
# Get all podcasts that are in the collection with the given slug
# Get all articles that are in the collection with the given slug
queryset = queryset.filter( queryset = queryset.filter(
collections__slug=collection_slug collections__slug=collection_slug
) )
@ -150,16 +159,25 @@ class ArticleListAPIView(generics.ListAPIView):
# Import Bookmark model here to avoid circular imports # Import Bookmark model here to avoid circular imports
from apps.bookmark.models import Bookmark from apps.bookmark.models import Bookmark
# Get all bookmarked podcast IDs for the current user
# Get all bookmarked article IDs for the current user
bookmarked_ids = Bookmark.objects.filter( bookmarked_ids = Bookmark.objects.filter(
user=self.request.user, user=self.request.user,
service=Bookmark.ServiceChoices.ARTICLE, service=Bookmark.ServiceChoices.ARTICLE,
status=True status=True
).values_list('content_id', flat=True) ).values_list('content_id', flat=True)
# Filter podcasts by these IDs
# Filter articles by these IDs
queryset = queryset.filter(id__in=bookmarked_ids) queryset = queryset.filter(id__in=bookmarked_ids)
# Sort by parameter
sort = self.request.query_params.get('sort', '-created_at')
# Allowed sort fields
allowed_sorts = ['created_at', '-created_at', 'view_count', '-view_count', 'title', '-title']
if sort in allowed_sorts:
queryset = queryset.order_by(sort)
else:
queryset = queryset.order_by('-created_at')
return queryset return queryset

2
apps/hadis/serializers/hadis.py

@ -15,7 +15,7 @@ class HadisListSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Hadis model = Hadis
fields = ['id', 'number', 'title', 'category', 'translation']
fields = ['id', 'number', 'title', 'text' ,'category', 'translation']
def get_category(self, obj): def get_category(self, obj):
"""Get category id and title""" """Get category id and title"""

47
apps/library/doc.py

@ -41,10 +41,26 @@ is_bookmark_param = openapi.Parameter(
required=False required=False
) )
category_param = openapi.Parameter(
'category',
openapi.IN_QUERY,
description="Filter books by category slug(s). Can be a single slug or comma-separated list of slugs",
type=openapi.TYPE_STRING,
required=False
)
sort_param = openapi.Parameter(
'sort',
openapi.IN_QUERY,
description="Sort books by field. Options: created_at, -created_at, view_count, -view_count, download_count, -download_count, title, -title, pin, -pin, -pin,-created_at",
type=openapi.TYPE_STRING,
required=False
)
search_param = openapi.Parameter( search_param = openapi.Parameter(
'search', 'search',
openapi.IN_QUERY, openapi.IN_QUERY,
description="Search books by title, summary, or author",
description="Search books by title, summary, publisher, or isbn",
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=False required=False
) )
@ -82,6 +98,21 @@ book_schema = openapi.Schema(
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
description="Author of the book" description="Author of the book"
), ),
'language': openapi.Schema(
type=openapi.TYPE_INTEGER,
description="Language ID of the book",
nullable=True
),
'main_themes': openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING),
description="List of main themes"
),
'notable_works': openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING),
description="List of notable works"
),
'status': openapi.Schema( 'status': openapi.Schema(
type=openapi.TYPE_BOOLEAN, type=openapi.TYPE_BOOLEAN,
description="Whether the book is active/visible" description="Whether the book is active/visible"
@ -178,17 +209,23 @@ book_list_swagger = swagger_auto_schema(
You can filter books by: You can filter books by:
- Collection ID using the query parameter 'collection_id' - Collection ID using the query parameter 'collection_id'
- Middle section collection using the query parameter 'middle'
- Bottom section collection using the query parameter 'bottom'
- Category slug(s) using the query parameter 'category' (single slug or comma-separated list)
- Bookmarked books using the query parameter 'is_bookmark=true' - Bookmarked books using the query parameter 'is_bookmark=true'
You can also search for books by title, summary, or author using the query parameter 'search'.
You can also search for books by title, summary, publisher, or isbn using the query parameter 'search'.
You can sort books by:
- created_at, -created_at
- view_count, -view_count
- download_count, -download_count
- title, -title
- pin, -pin, -pin,-created_at
Note: To get downloaded books, use the separate endpoint /books/downloaded/ Note: To get downloaded books, use the separate endpoint /books/downloaded/
""", """,
operation_summary="List Books", operation_summary="List Books",
tags=["Library"], tags=["Library"],
manual_parameters=[collection_id_param, middle_param, bottom_param, is_bookmark_param, search_param],
manual_parameters=[collection_id_param, category_param, is_bookmark_param, search_param, sort_param],
responses={ responses={
200: books_response, 200: books_response,
401: "Authentication credentials were not provided or are invalid.", 401: "Authentication credentials were not provided or are invalid.",

18
apps/library/migrations/0007_auto_20251203_1529.py

@ -0,0 +1,18 @@
# Generated by Django 5.1.8 on 2025-12-03 15:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0006_remove_book_author_book_isbn_book_numnber_of_volume_and_more'),
]
operations = [
migrations.AddField(
model_name='book',
name='author',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

30
apps/library/migrations/0008_auto_20251203_1533.py

@ -0,0 +1,30 @@
# Generated by Django 5.1.8 on 2025-12-03 15:33
from django.db import migrations, models
import dj_language.field
class Migration(migrations.Migration):
dependencies = [
('library', '0007_auto_20251203_1529'),
('dj_language', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='book',
name='language',
field=dj_language.field.LanguageField(blank=True, null=True, on_delete=models.SET_NULL, to='dj_language.language', verbose_name='Language'),
),
migrations.AddField(
model_name='book',
name='main_themes',
field=models.JSONField(blank=True, default=list, help_text='List of main themes', verbose_name='Main Themes'),
),
migrations.AddField(
model_name='book',
name='notable_works',
field=models.JSONField(blank=True, default=list, help_text='List of notable works', verbose_name='Notable Works'),
),
]

8
apps/library/models.py

@ -3,6 +3,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from filer.fields.image import FilerImageField from filer.fields.image import FilerImageField
from dj_language.field import LanguageField
from utils import generate_slug_for_model from utils import generate_slug_for_model
from apps.account.models import User from apps.account.models import User
@ -108,10 +109,15 @@ class Book(models.Model):
publisher = models.CharField(max_length=655, null=True, blank=True) publisher = models.CharField(max_length=655, null=True, blank=True)
year_of_publication = models.CharField(max_length=255, null=True, blank=True) year_of_publication = models.CharField(max_length=255, null=True, blank=True)
# author = models.CharField(max_length=255, null=True, blank=True)
author = models.CharField(max_length=255, null=True, blank=True)
isbn = models.CharField(max_length=255, null=True, blank=True) isbn = models.CharField(max_length=255, null=True, blank=True)
numnber_of_volume = models.CharField(max_length=255, null=True, blank=True) numnber_of_volume = models.CharField(max_length=255, null=True, blank=True)
# Language, themes and notable works
language = LanguageField(verbose_name=_('Language'), null=True, blank=True)
main_themes = models.JSONField(verbose_name=_('Main Themes'), default=list, blank=True, help_text=_('List of main themes'))
notable_works = models.JSONField(verbose_name=_('Notable Works'), default=list, blank=True, help_text=_('List of notable works'))
pages_count = models.CharField(verbose_name=_('Number of Pages'), max_length=255, help_text=_('eg. 34'), null=True) pages_count = models.CharField(verbose_name=_('Number of Pages'), max_length=255, help_text=_('eg. 34'), null=True)
status = models.BooleanField(default=True, verbose_name=_('status')) status = models.BooleanField(default=True, verbose_name=_('status'))
pin = models.BooleanField(default=True, verbose_name=_('Pin to top')) pin = models.BooleanField(default=True, verbose_name=_('Pin to top'))

3
apps/library/serializers.py

@ -60,7 +60,8 @@ class BookSerializer(serializers.ModelSerializer):
model = Book model = Book
fields = ( fields = (
'id', 'title', 'slug', 'summary', 'summary_title', 'thumbnail', 'slogan', 'id', 'title', 'slug', 'summary', 'summary_title', 'thumbnail', 'slogan',
'status', 'pin', 'view_count', 'download_count', 'publisher', 'year_of_publication', 'isbn', 'numnber_of_volume',
'status', 'pin', 'view_count', 'download_count', 'publisher', 'year_of_publication', 'author', 'isbn', 'numnber_of_volume',
'language', 'main_themes', 'notable_works',
'file_type', 'book_file', 'created_at', 'bookmark', 'user_rate', 'file_type', 'book_file', 'created_at', 'bookmark', 'user_rate',
'average_rate' 'average_rate'
) )

26
apps/library/views.py

@ -103,6 +103,13 @@ class BookListView(ListAPIView):
if collection_id: if collection_id:
queryset = queryset.filter(collections__id=collection_id) queryset = queryset.filter(collections__id=collection_id)
# Filter by category if provided
category = self.request.query_params.get('category')
if category:
# Support both single slug and comma-separated list of slugs
category_slugs = [slug.strip() for slug in category.split(',')]
queryset = queryset.filter(categories__slug__in=category_slugs).distinct()
# Filter by middle collection if requested # Filter by middle collection if requested
# if self.request.query_params.get('middle'): # if self.request.query_params.get('middle'):
# middle_collections = BookCollection.objects.filter( # middle_collections = BookCollection.objects.filter(
@ -137,7 +144,24 @@ class BookListView(ListAPIView):
# Filter books by these IDs # Filter books by these IDs
queryset = queryset.filter(id__in=bookmarked_ids) queryset = queryset.filter(id__in=bookmarked_ids)
return queryset.order_by('-pin', '-created_at')
# Sort by parameter
sort = self.request.query_params.get('sort', '-pin,-created_at')
# Allowed sort fields
allowed_sorts = [
'created_at', '-created_at', 'view_count', '-view_count',
'download_count', '-download_count', 'title', '-title',
'pin', '-pin', '-pin,-created_at'
]
if sort in allowed_sorts:
# Handle multiple sort fields (e.g., '-pin,-created_at')
if ',' in sort:
queryset = queryset.order_by(*sort.split(','))
else:
queryset = queryset.order_by(sort)
else:
queryset = queryset.order_by('-pin', '-created_at')
return queryset
class BookDetailView(RetrieveAPIView): class BookDetailView(RetrieveAPIView):

7
apps/podcast/serializers.py

@ -188,10 +188,11 @@ class PinnedPodcastCollectionSerializer(serializers.ModelSerializer):
class PodcastPlaylistListSerializer(serializers.ModelSerializer): class PodcastPlaylistListSerializer(serializers.ModelSerializer):
thumbnail = serializers.SerializerMethodField() thumbnail = serializers.SerializerMethodField()
total_time_formatted = serializers.SerializerMethodField() total_time_formatted = serializers.SerializerMethodField()
episodes_count = serializers.SerializerMethodField()
class Meta: class Meta:
model = PodcastPlaylist model = PodcastPlaylist
fields = ['id', 'title', 'slug', 'thumbnail', 'slogan', 'view_count', 'total_time_formatted', 'order', 'created_at']
fields = ['id', 'title', 'slug', 'thumbnail', 'slogan', 'view_count', 'total_time_formatted', 'episodes_count', 'order', 'created_at']
def get_thumbnail(self, obj): def get_thumbnail(self, obj):
return get_thumbs(obj.thumbnail, self.context.get('request')) return get_thumbs(obj.thumbnail, self.context.get('request'))
@ -206,6 +207,10 @@ class PodcastPlaylistListSerializer(serializers.ModelSerializer):
return f"{hours:02d}:{minutes:02d}:{seconds:02d}" return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
return "00:00:00" return "00:00:00"
def get_episodes_count(self, obj):
"""Return the number of episodes (podcasts) in this playlist"""
return obj.playlist_items.count()
class PodcastPlaylistDetailSerializer(serializers.ModelSerializer): class PodcastPlaylistDetailSerializer(serializers.ModelSerializer):
categories = PodcastCategoryListSerializer(many=True, read_only=True) categories = PodcastCategoryListSerializer(many=True, read_only=True)

35
apps/podcast/views.py

@ -90,12 +90,12 @@ class PodcastListAPIView(generics.ListAPIView):
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
@swagger_auto_schema( @swagger_auto_schema(
operation_description="Get a list of podcast playlists with optional filtering",
operation_description="Get a list of podcast playlists with optional filtering and sorting",
manual_parameters=[ manual_parameters=[
openapi.Parameter( openapi.Parameter(
name='category', name='category',
in_=openapi.IN_QUERY, in_=openapi.IN_QUERY,
description='Filter playlists by category slug',
description='Filter playlists by category slug(s). Can be a single slug or comma-separated list of slugs',
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=False required=False
), ),
@ -119,11 +119,18 @@ class PodcastListAPIView(generics.ListAPIView):
description='Search playlists by title', description='Search playlists by title',
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=False required=False
),
openapi.Parameter(
name='sort',
in_=openapi.IN_QUERY,
description='Sort playlists by field. Options: created_at, -created_at, view_count, -view_count, title, -title, order, -order',
type=openapi.TYPE_STRING,
required=False
) )
], ],
responses={ responses={
200: openapi.Response( 200: openapi.Response(
description="List of podcast playlists",
description="List of podcast playlists with episodes count",
schema=PodcastPlaylistListSerializer(many=True) schema=PodcastPlaylistListSerializer(many=True)
) )
} }
@ -132,7 +139,7 @@ class PodcastListAPIView(generics.ListAPIView):
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
queryset = PodcastPlaylist.objects.filter(status=True).order_by('-created_at')
queryset = PodcastPlaylist.objects.filter(status=True)
# Search by title if search parameter is provided # Search by title if search parameter is provided
search_query = self.request.query_params.get('search', None) search_query = self.request.query_params.get('search', None)
@ -140,9 +147,11 @@ class PodcastListAPIView(generics.ListAPIView):
queryset = queryset.filter(title__icontains=search_query) queryset = queryset.filter(title__icontains=search_query)
# Filter by category if provided # Filter by category if provided
category_slug = self.request.query_params.get('category', None)
if category_slug:
queryset = queryset.filter(categories__slug=category_slug)
category = self.request.query_params.get('category', None)
if category:
# Support both single slug and comma-separated list of slugs
category_slugs = [slug.strip() for slug in category.split(',')]
queryset = queryset.filter(categories__slug__in=category_slugs).distinct()
# Filter by collection if provided # Filter by collection if provided
collection_slug = self.request.query_params.get('collection', None) collection_slug = self.request.query_params.get('collection', None)
@ -164,6 +173,18 @@ class PodcastListAPIView(generics.ListAPIView):
queryset = queryset.filter(id__in=bookmarked_ids) queryset = queryset.filter(id__in=bookmarked_ids)
# Sort by parameter
sort = self.request.query_params.get('sort', '-created_at')
# Allowed sort fields
allowed_sorts = [
'created_at', '-created_at', 'view_count', '-view_count',
'title', '-title', 'order', '-order'
]
if sort in allowed_sorts:
queryset = queryset.order_by(sort)
else:
queryset = queryset.order_by('-created_at')
return queryset return queryset

Loading…
Cancel
Save