You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
408 lines
16 KiB
408 lines
16 KiB
import logging
|
|
from django.db.models import Q, F
|
|
from django.shortcuts import get_object_or_404
|
|
from django.utils.translation import gettext_lazy as _
|
|
from rest_framework import status
|
|
from rest_framework.generics import GenericAPIView
|
|
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
|
|
from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser
|
|
from rest_framework.response import Response
|
|
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiResponse
|
|
|
|
from apps.cms.models.post import (
|
|
PostCategory,
|
|
PostTag,
|
|
Post,
|
|
PostComment,
|
|
PostLike,
|
|
MediaAsset,
|
|
)
|
|
from apps.cms.permissions import IsAuthorOrEditorOrReadOnly, CanPublishPost
|
|
from apps.cms.serializers import (
|
|
PostCategorySerializer,
|
|
PostTagSerializer,
|
|
PostCommentSerializer,
|
|
PostCommentCreateSerializer,
|
|
PostListSerializer,
|
|
PostDetailSerializer,
|
|
PostCreateUpdateSerializer,
|
|
PostLikeResponseSerializer,
|
|
MediaAssetSerializer,
|
|
)
|
|
from utils.pagination import StandardResultsSetPagination
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def get_post_by_id_or_slug(lookup_val, user=None):
|
|
qs = Post.objects.all()
|
|
# Filter visible status if anonymous
|
|
if not user or not user.is_authenticated or not (user.is_staff or getattr(user, 'is_super_admin', False) or getattr(user, 'is_regional_admin', False)):
|
|
qs = qs.filter(status=Post.Status.PUBLISHED)
|
|
|
|
if str(lookup_val).isdigit():
|
|
return get_object_or_404(qs, id=int(lookup_val))
|
|
return get_object_or_404(qs, slug=lookup_val)
|
|
|
|
|
|
class PostListCreateView(GenericAPIView):
|
|
serializer_class = PostListSerializer
|
|
pagination_class = StandardResultsSetPagination
|
|
|
|
def get_permissions(self):
|
|
if self.request.method == 'POST':
|
|
return [IsAuthenticated(), CanPublishPost()]
|
|
return [AllowAny()]
|
|
|
|
def get_queryset(self):
|
|
if getattr(self, 'swagger_fake_view', False):
|
|
return Post.objects.none()
|
|
|
|
user = self.request.user
|
|
qs = Post.objects.all().select_related('author', 'category', 'institution')
|
|
|
|
# Visibility filter: non-staff only see published posts unless filtering by my_posts
|
|
is_admin = user.is_authenticated and (user.is_staff or getattr(user, 'is_super_admin', False) or getattr(user, 'is_regional_admin', False))
|
|
|
|
status_param = self.request.query_params.get('status')
|
|
if status_param and is_admin:
|
|
qs = qs.filter(status=status_param)
|
|
elif not is_admin:
|
|
# If requesting own posts, can see drafts
|
|
my_posts = self.request.query_params.get('my_posts')
|
|
if my_posts and user.is_authenticated:
|
|
qs = qs.filter(author=user)
|
|
if status_param:
|
|
qs = qs.filter(status=status_param)
|
|
else:
|
|
qs = qs.filter(status=Post.Status.PUBLISHED)
|
|
|
|
# Filters
|
|
post_type = self.request.query_params.get('post_type')
|
|
if post_type:
|
|
qs = qs.filter(post_type=post_type)
|
|
|
|
language = self.request.query_params.get('language')
|
|
if language:
|
|
qs = qs.filter(language=language)
|
|
|
|
category = self.request.query_params.get('category')
|
|
if category:
|
|
if str(category).isdigit():
|
|
qs = qs.filter(category_id=int(category))
|
|
else:
|
|
qs = qs.filter(category__slug=category)
|
|
|
|
tag = self.request.query_params.get('tag')
|
|
if tag:
|
|
qs = qs.filter(tags__icontains=tag)
|
|
|
|
institution_id = self.request.query_params.get('institution_id')
|
|
if institution_id:
|
|
qs = qs.filter(institution_id=institution_id)
|
|
|
|
author_id = self.request.query_params.get('author_id')
|
|
if author_id:
|
|
qs = qs.filter(author_id=author_id)
|
|
|
|
is_featured = self.request.query_params.get('is_featured')
|
|
if is_featured is not None:
|
|
qs = qs.filter(is_featured=is_featured.lower() in ['true', '1'])
|
|
|
|
search = self.request.query_params.get('search')
|
|
if search:
|
|
qs = qs.filter(
|
|
Q(title__icontains=search) |
|
|
Q(excerpt__icontains=search) |
|
|
Q(content__icontains=search)
|
|
)
|
|
|
|
return qs.order_by('-is_featured', '-publish_date')
|
|
|
|
@extend_schema(
|
|
summary="List multilingual posts & articles",
|
|
description="Returns a paginated list of published news, articles, and interviews with multilingual filtering.",
|
|
parameters=[
|
|
OpenApiParameter('post_type', str, description='Filter by type (news, article, report, interview)'),
|
|
OpenApiParameter('language', str, description='Filter by language code (fa, ar, en, ur, fr)'),
|
|
OpenApiParameter('category', str, description='Filter by category ID or slug'),
|
|
OpenApiParameter('tag', str, description='Filter by tag string'),
|
|
OpenApiParameter('institution_id', int, description='Filter by associated institution ID'),
|
|
OpenApiParameter('author_id', int, description='Filter by author user ID'),
|
|
OpenApiParameter('is_featured', bool, description='Filter by featured status'),
|
|
OpenApiParameter('search', str, description='Search text in title, excerpt, and content'),
|
|
OpenApiParameter('status', str, description='Filter by status (staff/admin only)'),
|
|
OpenApiParameter('my_posts', bool, description='Filter by current authenticated user posts'),
|
|
],
|
|
responses={200: PostListSerializer(many=True)},
|
|
tags=["Content Management System (CMS)"],
|
|
)
|
|
def get(self, request, *args, **kwargs):
|
|
queryset = self.filter_queryset(self.get_queryset())
|
|
page = self.paginate_queryset(queryset)
|
|
if page is not None:
|
|
serializer = self.get_serializer(page, many=True, context={'request': request})
|
|
return self.get_paginated_response(serializer.data)
|
|
|
|
serializer = self.get_serializer(queryset, many=True, context={'request': request})
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Publish or draft new post",
|
|
description="Creates a new article, news announcement, or report.",
|
|
request=PostCreateUpdateSerializer,
|
|
responses={
|
|
201: PostDetailSerializer,
|
|
400: OpenApiResponse(description="Validation error"),
|
|
401: OpenApiResponse(description="Authentication required"),
|
|
},
|
|
tags=["Content Management System (CMS)"],
|
|
)
|
|
def post(self, request, *args, **kwargs):
|
|
serializer = PostCreateUpdateSerializer(data=request.data, context={'request': request})
|
|
serializer.is_valid(raise_exception=True)
|
|
post_obj = serializer.save()
|
|
|
|
response_serializer = PostDetailSerializer(post_obj, context={'request': request})
|
|
return Response(response_serializer.data, status=status.HTTP_201_CREATED)
|
|
|
|
|
|
class PostDetailView(GenericAPIView):
|
|
serializer_class = PostDetailSerializer
|
|
queryset = Post.objects.all()
|
|
|
|
def get_permissions(self):
|
|
if self.request.method in ['PATCH', 'PUT', 'DELETE']:
|
|
return [IsAuthenticated(), IsAuthorOrEditorOrReadOnly()]
|
|
return [AllowAny()]
|
|
|
|
def get_object(self):
|
|
lookup = self.kwargs.get('pk_or_slug')
|
|
post_obj = get_post_by_id_or_slug(lookup, self.request.user)
|
|
self.check_object_permissions(self.request, post_obj)
|
|
return post_obj
|
|
|
|
@extend_schema(
|
|
summary="Get full post details",
|
|
description="Retrieves full article content and increments the view counter.",
|
|
responses={
|
|
200: PostDetailSerializer,
|
|
404: OpenApiResponse(description="Post not found"),
|
|
},
|
|
tags=["Content Management System (CMS)"],
|
|
)
|
|
def get(self, request, pk_or_slug, *args, **kwargs):
|
|
post_obj = self.get_object()
|
|
|
|
# Increment views counter
|
|
Post.objects.filter(id=post_obj.id).update(views_count=F('views_count') + 1)
|
|
post_obj.refresh_from_db()
|
|
|
|
serializer = PostDetailSerializer(post_obj, context={'request': request})
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Update post",
|
|
description="Updates an existing post. Permitted for original author, institution editors, or platform admins.",
|
|
request=PostCreateUpdateSerializer,
|
|
responses={
|
|
200: PostDetailSerializer,
|
|
403: OpenApiResponse(description="Permission denied"),
|
|
},
|
|
tags=["Content Management System (CMS)"],
|
|
)
|
|
def patch(self, request, pk_or_slug, *args, **kwargs):
|
|
post_obj = self.get_object()
|
|
serializer = PostCreateUpdateSerializer(
|
|
post_obj,
|
|
data=request.data,
|
|
partial=True,
|
|
context={'request': request}
|
|
)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
|
|
response_serializer = PostDetailSerializer(post_obj, context={'request': request})
|
|
return Response(response_serializer.data, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Delete post",
|
|
description="Removes a post from the platform.",
|
|
responses={
|
|
204: OpenApiResponse(description="Post deleted successfully"),
|
|
403: OpenApiResponse(description="Permission denied"),
|
|
},
|
|
tags=["Content Management System (CMS)"],
|
|
)
|
|
def delete(self, request, pk_or_slug, *args, **kwargs):
|
|
post_obj = self.get_object()
|
|
post_obj.delete()
|
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
|
|
|
|
|
class PostLikeToggleView(GenericAPIView):
|
|
permission_classes = [IsAuthenticated]
|
|
serializer_class = PostLikeResponseSerializer
|
|
queryset = PostLike.objects.all()
|
|
|
|
@extend_schema(
|
|
summary="Toggle like on post",
|
|
description="Likes or unlikes an article for the authenticated user and updates the like counter.",
|
|
responses={200: PostLikeResponseSerializer},
|
|
tags=["Content Management System (CMS)"],
|
|
)
|
|
def post(self, request, pk_or_slug, *args, **kwargs):
|
|
post_obj = get_post_by_id_or_slug(pk_or_slug, request.user)
|
|
|
|
like_obj, created = PostLike.objects.get_or_create(
|
|
post=post_obj,
|
|
user=request.user
|
|
)
|
|
|
|
if created:
|
|
Post.objects.filter(id=post_obj.id).update(likes_count=F('likes_count') + 1)
|
|
is_liked = True
|
|
msg = _("Post liked.")
|
|
else:
|
|
like_obj.delete()
|
|
Post.objects.filter(id=post_obj.id, likes_count__gt=0).update(likes_count=F('likes_count') - 1)
|
|
is_liked = False
|
|
msg = _("Post unliked.")
|
|
|
|
post_obj.refresh_from_db()
|
|
return Response({
|
|
'is_liked': is_liked,
|
|
'likes_count': post_obj.likes_count,
|
|
'message': msg
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
|
|
class PostCommentsView(GenericAPIView):
|
|
serializer_class = PostCommentSerializer
|
|
queryset = PostComment.objects.all()
|
|
|
|
def get_permissions(self):
|
|
return [AllowAny()]
|
|
|
|
@extend_schema(
|
|
summary="List post comments",
|
|
description="Retrieves approved reader comments for a post.",
|
|
responses={200: PostCommentSerializer(many=True)},
|
|
tags=["Content Management System (CMS)"],
|
|
)
|
|
def get(self, request, pk_or_slug, *args, **kwargs):
|
|
post_obj = get_post_by_id_or_slug(pk_or_slug, request.user)
|
|
comments = post_obj.comments.filter(status=PostComment.Status.APPROVED).order_by('-created_at')
|
|
serializer = PostCommentSerializer(comments, many=True)
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Submit post comment",
|
|
description="Submits a comment on an article.",
|
|
request=PostCommentCreateSerializer,
|
|
responses={
|
|
201: PostCommentSerializer,
|
|
400: OpenApiResponse(description="Validation error"),
|
|
},
|
|
tags=["Content Management System (CMS)"],
|
|
)
|
|
def post(self, request, pk_or_slug, *args, **kwargs):
|
|
post_obj = get_post_by_id_or_slug(pk_or_slug, request.user)
|
|
serializer = PostCommentCreateSerializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
user = request.user if request.user.is_authenticated else None
|
|
author_name = serializer.validated_data.get('author_name') or (user.fullname if user else "Visitor")
|
|
author_email = serializer.validated_data.get('author_email') or (user.email if user else "")
|
|
|
|
comment = PostComment.objects.create(
|
|
post=post_obj,
|
|
user=user,
|
|
author_name=author_name,
|
|
author_email=author_email,
|
|
content=serializer.validated_data['content'],
|
|
status=PostComment.Status.APPROVED
|
|
)
|
|
|
|
return Response(PostCommentSerializer(comment).data, status=status.HTTP_201_CREATED)
|
|
|
|
|
|
class PostCategoryListView(GenericAPIView):
|
|
permission_classes = [AllowAny]
|
|
serializer_class = PostCategorySerializer
|
|
queryset = PostCategory.objects.all()
|
|
|
|
@extend_schema(
|
|
summary="List post categories",
|
|
description="Returns content categories with optional language filtering.",
|
|
parameters=[
|
|
OpenApiParameter('language', str, description='Filter categories by language code (fa, ar, en, ur, fr)')
|
|
],
|
|
responses={200: PostCategorySerializer(many=True)},
|
|
tags=["Content Management System (CMS)"],
|
|
)
|
|
def get(self, request, *args, **kwargs):
|
|
qs = PostCategory.objects.all()
|
|
lang = request.query_params.get('language')
|
|
if lang:
|
|
qs = qs.filter(language=lang)
|
|
serializer = PostCategorySerializer(qs, many=True)
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
|
|
class PostTagListView(GenericAPIView):
|
|
permission_classes = [AllowAny]
|
|
serializer_class = PostTagSerializer
|
|
queryset = PostTag.objects.all()
|
|
|
|
@extend_schema(
|
|
summary="List CMS tags",
|
|
description="Returns distinct content tags used across posts.",
|
|
responses={200: PostTagSerializer(many=True)},
|
|
tags=["Content Management System (CMS)"],
|
|
)
|
|
def get(self, request, *args, **kwargs):
|
|
tags = PostTag.objects.all()
|
|
serializer = PostTagSerializer(tags, many=True)
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
|
|
class MediaAssetListCreateView(GenericAPIView):
|
|
serializer_class = MediaAssetSerializer
|
|
parser_classes = [MultiPartParser, FormParser, JSONParser]
|
|
queryset = MediaAsset.objects.all()
|
|
|
|
def get_permissions(self):
|
|
if self.request.method == 'POST':
|
|
return [IsAuthenticated()]
|
|
return [AllowAny()]
|
|
|
|
@extend_schema(
|
|
summary="List media library assets",
|
|
description="Retrieves uploaded photos, videos, and documents.",
|
|
parameters=[
|
|
OpenApiParameter('media_type', str, description='Filter by media type (image, video, audio, document)')
|
|
],
|
|
responses={200: MediaAssetSerializer(many=True)},
|
|
tags=["Content Management System (CMS)"],
|
|
)
|
|
def get(self, request, *args, **kwargs):
|
|
qs = MediaAsset.objects.all().order_by('-created_at')
|
|
m_type = request.query_params.get('media_type')
|
|
if m_type:
|
|
qs = qs.filter(media_type=m_type)
|
|
serializer = MediaAssetSerializer(qs, many=True)
|
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
|
|
@extend_schema(
|
|
summary="Upload media asset",
|
|
description="Uploads a new photo, document, or audio file to the media library.",
|
|
request=MediaAssetSerializer,
|
|
responses={201: MediaAssetSerializer},
|
|
tags=["Content Management System (CMS)"],
|
|
)
|
|
def post(self, request, *args, **kwargs):
|
|
serializer = MediaAssetSerializer(data=request.data, context={'request': request})
|
|
serializer.is_valid(raise_exception=True)
|
|
asset = serializer.save()
|
|
return Response(MediaAssetSerializer(asset).data, status=status.HTTP_201_CREATED)
|