213 lines
6.6 KiB
Python
213 lines
6.6 KiB
Python
from rest_framework import generics, status, filters
|
|
from rest_framework.decorators import api_view, permission_classes
|
|
from rest_framework.permissions import AllowAny, IsAuthenticated
|
|
from rest_framework.response import Response
|
|
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
|
|
from django_filters.rest_framework import DjangoFilterBackend
|
|
from django.shortcuts import get_object_or_404
|
|
from django.db.models import Q, Avg
|
|
from django.db import models
|
|
from django.conf import settings
|
|
|
|
from .models import Service, ServiceFeature, ServiceCategory
|
|
from .serializers import (
|
|
ServiceSerializer,
|
|
ServiceListSerializer,
|
|
ServiceCreateUpdateSerializer,
|
|
ServiceFeatureSerializer,
|
|
ServiceCategorySerializer
|
|
)
|
|
|
|
|
|
class ServiceListView(generics.ListAPIView):
|
|
"""
|
|
List all active services with optional filtering
|
|
"""
|
|
queryset = Service.objects.filter(is_active=True)
|
|
serializer_class = ServiceListSerializer
|
|
permission_classes = [AllowAny]
|
|
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
|
filterset_fields = ['featured', 'display_order']
|
|
search_fields = ['title', 'description']
|
|
ordering_fields = ['display_order', 'created_at', 'title', 'price']
|
|
ordering = ['display_order', 'created_at']
|
|
|
|
def get_queryset(self):
|
|
queryset = super().get_queryset()
|
|
|
|
# Filter by category if provided
|
|
category_slug = self.request.query_params.get('category', None)
|
|
if category_slug:
|
|
queryset = queryset.filter(category__slug=category_slug)
|
|
|
|
# Filter by price range if provided
|
|
min_price = self.request.query_params.get('min_price', None)
|
|
max_price = self.request.query_params.get('max_price', None)
|
|
|
|
if min_price:
|
|
queryset = queryset.filter(price__gte=min_price)
|
|
if max_price:
|
|
queryset = queryset.filter(price__lte=max_price)
|
|
|
|
return queryset
|
|
|
|
|
|
class ServiceDetailView(generics.RetrieveAPIView):
|
|
"""
|
|
Retrieve a single service by slug
|
|
"""
|
|
queryset = Service.objects.filter(is_active=True)
|
|
serializer_class = ServiceSerializer
|
|
permission_classes = [AllowAny]
|
|
lookup_field = 'slug'
|
|
|
|
|
|
class ServiceCreateView(generics.CreateAPIView):
|
|
"""
|
|
Create a new service (admin only)
|
|
"""
|
|
queryset = Service.objects.all()
|
|
serializer_class = ServiceCreateUpdateSerializer
|
|
permission_classes = [IsAuthenticated]
|
|
parser_classes = [MultiPartParser, FormParser, JSONParser]
|
|
|
|
|
|
class ServiceUpdateView(generics.UpdateAPIView):
|
|
"""
|
|
Update an existing service (admin only)
|
|
"""
|
|
queryset = Service.objects.all()
|
|
serializer_class = ServiceCreateUpdateSerializer
|
|
permission_classes = [IsAuthenticated]
|
|
lookup_field = 'slug'
|
|
parser_classes = [MultiPartParser, FormParser, JSONParser]
|
|
|
|
|
|
class ServiceDeleteView(generics.DestroyAPIView):
|
|
"""
|
|
Delete a service (admin only)
|
|
"""
|
|
queryset = Service.objects.all()
|
|
permission_classes = [IsAuthenticated]
|
|
lookup_field = 'slug'
|
|
|
|
def perform_destroy(self, instance):
|
|
# Delete the image file if it exists
|
|
if instance.image:
|
|
instance.image.delete(save=False)
|
|
instance.delete()
|
|
|
|
|
|
class FeaturedServicesView(generics.ListAPIView):
|
|
"""
|
|
List all featured services
|
|
"""
|
|
queryset = Service.objects.filter(is_active=True, featured=True)
|
|
serializer_class = ServiceListSerializer
|
|
permission_classes = [AllowAny]
|
|
ordering = ['display_order', 'created_at']
|
|
|
|
|
|
class ServiceCategoryListView(generics.ListAPIView):
|
|
"""
|
|
List all active service categories
|
|
"""
|
|
queryset = ServiceCategory.objects.filter(is_active=True)
|
|
serializer_class = ServiceCategorySerializer
|
|
permission_classes = [AllowAny]
|
|
ordering = ['display_order', 'name']
|
|
|
|
|
|
class ServiceCategoryDetailView(generics.RetrieveAPIView):
|
|
"""
|
|
Retrieve a single service category by slug
|
|
"""
|
|
queryset = ServiceCategory.objects.filter(is_active=True)
|
|
serializer_class = ServiceCategorySerializer
|
|
permission_classes = [AllowAny]
|
|
lookup_field = 'slug'
|
|
|
|
|
|
@api_view(['GET'])
|
|
@permission_classes([AllowAny])
|
|
def service_search(request):
|
|
"""
|
|
Search services by query string
|
|
"""
|
|
query = request.GET.get('q', '')
|
|
if not query:
|
|
return Response({'error': 'Query parameter "q" is required'},
|
|
status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
services = Service.objects.filter(
|
|
Q(is_active=True) & (
|
|
Q(title__icontains=query) |
|
|
Q(description__icontains=query) |
|
|
Q(slug__icontains=query)
|
|
)
|
|
).order_by('display_order', 'created_at')
|
|
|
|
serializer = ServiceListSerializer(services, many=True)
|
|
return Response({
|
|
'query': query,
|
|
'count': services.count(),
|
|
'results': serializer.data
|
|
})
|
|
|
|
|
|
@api_view(['GET'])
|
|
@permission_classes([AllowAny])
|
|
def service_stats(request):
|
|
"""
|
|
Get service statistics
|
|
"""
|
|
total_services = Service.objects.filter(is_active=True).count()
|
|
featured_services = Service.objects.filter(is_active=True, featured=True).count()
|
|
categories = ServiceCategory.objects.filter(is_active=True).count()
|
|
|
|
# Average price
|
|
services_with_price = Service.objects.filter(is_active=True, price__gt=0)
|
|
avg_price = services_with_price.aggregate(avg_price=Avg('price'))['avg_price'] or 0
|
|
|
|
return Response({
|
|
'total_services': total_services,
|
|
'featured_services': featured_services,
|
|
'categories': categories,
|
|
'average_price': float(avg_price)
|
|
})
|
|
|
|
|
|
@api_view(['POST'])
|
|
@permission_classes([IsAuthenticated])
|
|
def upload_service_image(request, slug):
|
|
"""
|
|
Upload an image for a specific service (admin only)
|
|
"""
|
|
try:
|
|
service = get_object_or_404(Service, slug=slug)
|
|
|
|
if 'image' not in request.FILES:
|
|
return Response(
|
|
{'error': 'No image file provided'},
|
|
status=status.HTTP_400_BAD_REQUEST
|
|
)
|
|
|
|
# Delete old image if it exists
|
|
if service.image:
|
|
service.image.delete(save=False)
|
|
|
|
# Save new image
|
|
service.image = request.FILES['image']
|
|
service.save()
|
|
|
|
serializer = ServiceSerializer(service)
|
|
return Response({
|
|
'message': 'Image uploaded successfully',
|
|
'service': serializer.data
|
|
})
|
|
|
|
except Exception as e:
|
|
return Response(
|
|
{'error': f'Failed to upload image: {str(e)}'},
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
) |