Fix permissions and prepare admin login for deploy
- Admin-only: list contacts, create/edit campaigns and compositions - Public: submit contact, mine contacts by email/phone, read campaigns/compositions - Add admin_response field on ContactUs - Update Postman base_url to https://zoneco.org/api with token save Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
125
api/views.py
125
api/views.py
@@ -1,11 +1,10 @@
|
||||
from rest_framework import viewsets, status
|
||||
from rest_framework.decorators import action, api_view, permission_classes
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser
|
||||
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
|
||||
from rest_framework.authtoken.models import Token
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
from django.utils.dateparse import parse_datetime
|
||||
from .models import ContactUs, Composition, Campaign, CompositionImage
|
||||
@@ -13,7 +12,6 @@ from .serializers import (
|
||||
ContactUsSerializer,
|
||||
CompositionSerializer,
|
||||
CampaignSerializer,
|
||||
CompositionImageSerializer,
|
||||
AdminLoginSerializer,
|
||||
)
|
||||
|
||||
@@ -43,7 +41,7 @@ def admin_login(request):
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
@permission_classes([IsAdminUser])
|
||||
def admin_logout(request):
|
||||
"""Delete the current admin auth token (logout)."""
|
||||
Token.objects.filter(user=request.user).delete()
|
||||
@@ -51,7 +49,7 @@ def admin_logout(request):
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
@permission_classes([IsAdminUser])
|
||||
def admin_me(request):
|
||||
"""Return the currently authenticated admin user."""
|
||||
user = request.user
|
||||
@@ -66,24 +64,47 @@ def admin_me(request):
|
||||
|
||||
class ContactUsViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
ViewSet for ContactUs model.
|
||||
Provides CRUD operations for contact form submissions.
|
||||
Contact Us permissions:
|
||||
- Public: create a message, and view own messages via /mine/
|
||||
- Admin: list all, retrieve, update (including admin_response), delete
|
||||
"""
|
||||
queryset = ContactUs.objects.all()
|
||||
serializer_class = ContactUsSerializer
|
||||
permission_classes = [AllowAny] # Allow anyone to submit contact forms
|
||||
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
def get_permissions(self):
|
||||
"""
|
||||
Override to allow GET (list, retrieve) and POST (create) for anyone.
|
||||
"""
|
||||
if self.action in ['list', 'retrieve', 'create', 'by_category']:
|
||||
if self.action in ['create', 'mine']:
|
||||
return [AllowAny()]
|
||||
return [IsAuthenticated()]
|
||||
|
||||
return [IsAdminUser()]
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
serializer = super().get_serializer(*args, **kwargs)
|
||||
# Public create cannot set admin_response
|
||||
if self.action == 'create' and not (
|
||||
self.request.user and self.request.user.is_staff
|
||||
):
|
||||
serializer.fields['admin_response'].read_only = True
|
||||
return serializer
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def mine(self, request):
|
||||
"""
|
||||
Public: list contacts for a given email_or_phone (own submissions),
|
||||
including admin_response.
|
||||
"""
|
||||
email_or_phone = (request.query_params.get('email_or_phone') or '').strip()
|
||||
if not email_or_phone:
|
||||
return Response(
|
||||
{'error': 'email_or_phone query parameter is required.'},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
contacts = self.queryset.filter(email_or_phone__iexact=email_or_phone)
|
||||
serializer = self.get_serializer(contacts, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def by_category(self, request):
|
||||
"""Get contacts filtered by category."""
|
||||
"""Admin: get contacts filtered by category."""
|
||||
category = request.query_params.get('category', None)
|
||||
if category:
|
||||
contacts = self.queryset.filter(category=category)
|
||||
@@ -97,35 +118,28 @@ class ContactUsViewSet(viewsets.ModelViewSet):
|
||||
|
||||
class CompositionViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
ViewSet for Composition model.
|
||||
|
||||
Supports uploading one or more images at create/update time via the
|
||||
multipart ``uploaded_images`` field, with an optional ``main_image_index``
|
||||
to flag the main image. Extra actions allow adding images, choosing the
|
||||
main image, and deleting an image after creation.
|
||||
Compositions (مقالات):
|
||||
- Public: list, retrieve, by-created-at
|
||||
- Admin: create, update, delete, manage images
|
||||
"""
|
||||
queryset = Composition.objects.prefetch_related('images').all()
|
||||
serializer_class = CompositionSerializer
|
||||
permission_classes = [AllowAny] # Public read access
|
||||
permission_classes = [IsAdminUser]
|
||||
parser_classes = [MultiPartParser, FormParser, JSONParser]
|
||||
|
||||
|
||||
def get_permissions(self):
|
||||
"""
|
||||
Override to allow GET (list, retrieve) and POST (create) for anyone.
|
||||
"""
|
||||
if self.action in ['list', 'retrieve', 'create', 'by_created_at']:
|
||||
if self.action in ['list', 'retrieve', 'by_created_at']:
|
||||
return [AllowAny()]
|
||||
return [IsAuthenticated()]
|
||||
|
||||
return [IsAdminUser()]
|
||||
|
||||
def get_serializer_context(self):
|
||||
"""Add request to serializer context for image URL generation."""
|
||||
context = super().get_serializer_context()
|
||||
context['request'] = self.request
|
||||
return context
|
||||
|
||||
|
||||
@action(detail=False, methods=['get'], url_path='by-created-at')
|
||||
def by_created_at(self, request):
|
||||
"""Get compositions filtered by created_at date range."""
|
||||
"""Public: compositions filtered by created_at date range."""
|
||||
from_param = request.query_params.get('from')
|
||||
to_param = request.query_params.get('to')
|
||||
|
||||
@@ -161,17 +175,16 @@ class CompositionViewSet(viewsets.ModelViewSet):
|
||||
|
||||
serializer = self.get_serializer(compositions, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
def _composition_response(self, composition):
|
||||
"""Return a fresh composition payload with up-to-date images."""
|
||||
composition = Composition.objects.prefetch_related('images').get(
|
||||
pk=composition.pk
|
||||
)
|
||||
return self.get_serializer(composition).data
|
||||
|
||||
|
||||
@action(detail=True, methods=['post'], url_path='add-images')
|
||||
def add_images(self, request, pk=None):
|
||||
"""Add one or more images to an existing composition."""
|
||||
"""Admin: add one or more images to an existing composition."""
|
||||
composition = self.get_object()
|
||||
serializer = self.get_serializer(
|
||||
composition, data=request.data, partial=True
|
||||
@@ -179,10 +192,10 @@ class CompositionViewSet(viewsets.ModelViewSet):
|
||||
serializer.is_valid(raise_exception=True)
|
||||
composition = serializer.save()
|
||||
return Response(self._composition_response(composition))
|
||||
|
||||
|
||||
@action(detail=True, methods=['post'], url_path='set-main-image')
|
||||
def set_main_image(self, request, pk=None):
|
||||
"""Flag one of the composition's images as the main image."""
|
||||
"""Admin: flag one image as the main image."""
|
||||
composition = self.get_object()
|
||||
image_id = request.data.get('image_id')
|
||||
if image_id is None:
|
||||
@@ -194,16 +207,16 @@ class CompositionViewSet(viewsets.ModelViewSet):
|
||||
CompositionImage, pk=image_id, composition=composition
|
||||
)
|
||||
image.is_main = True
|
||||
image.save() # model.save() unsets is_main on the other images
|
||||
image.save()
|
||||
return Response(self._composition_response(composition))
|
||||
|
||||
|
||||
@action(
|
||||
detail=True,
|
||||
methods=['delete'],
|
||||
url_path='images/(?P<image_id>[^/.]+)'
|
||||
)
|
||||
def delete_image(self, request, pk=None, image_id=None):
|
||||
"""Delete a single image from the composition."""
|
||||
"""Admin: delete a single image from the composition."""
|
||||
composition = self.get_object()
|
||||
image = get_object_or_404(
|
||||
CompositionImage, pk=image_id, composition=composition
|
||||
@@ -214,30 +227,26 @@ class CompositionViewSet(viewsets.ModelViewSet):
|
||||
|
||||
class CampaignViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
ViewSet for Campaign model.
|
||||
Provides CRUD operations for campaigns.
|
||||
Campaigns:
|
||||
- Public: list, retrieve, active, upcoming, ended
|
||||
- Admin: create, update, delete
|
||||
"""
|
||||
queryset = Campaign.objects.all()
|
||||
serializer_class = CampaignSerializer
|
||||
permission_classes = [AllowAny] # Public read access
|
||||
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
def get_permissions(self):
|
||||
"""
|
||||
Override to allow GET (list, retrieve) and POST (create) for anyone.
|
||||
"""
|
||||
if self.action in ['list', 'retrieve', 'create', 'active', 'upcoming', 'ended']:
|
||||
if self.action in ['list', 'retrieve', 'active', 'upcoming', 'ended']:
|
||||
return [AllowAny()]
|
||||
return [IsAuthenticated()]
|
||||
|
||||
return [IsAdminUser()]
|
||||
|
||||
def get_serializer_context(self):
|
||||
"""Add request to serializer context for image URL generation."""
|
||||
context = super().get_serializer_context()
|
||||
context['request'] = self.request
|
||||
return context
|
||||
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def active(self, request):
|
||||
"""Get all currently active campaigns."""
|
||||
now = timezone.now()
|
||||
active_campaigns = self.queryset.filter(
|
||||
start_time__lte=now,
|
||||
@@ -245,18 +254,16 @@ class CampaignViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
serializer = self.get_serializer(active_campaigns, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def upcoming(self, request):
|
||||
"""Get all upcoming campaigns."""
|
||||
now = timezone.now()
|
||||
upcoming_campaigns = self.queryset.filter(start_time__gt=now)
|
||||
serializer = self.get_serializer(upcoming_campaigns, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def ended(self, request):
|
||||
"""Get all ended campaigns."""
|
||||
now = timezone.now()
|
||||
ended_campaigns = self.queryset.filter(end_time__lt=now)
|
||||
serializer = self.get_serializer(ended_campaigns, many=True)
|
||||
|
||||
Reference in New Issue
Block a user