Coverage for src/products/views/product.py: 71%
102 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-09-21 16:24 +0300
« prev ^ index » next coverage.py v7.9.2, created at 2025-09-21 16:24 +0300
1"""
2This module contains views for product listing, detail, attribute retrieval, and review management.
4It provides:
5- List and detail views for each product type (Earring, Necklace, Pendant, Bracelet, Watch)
6- Attribute views for product properties like color, metal, stone, and collection
7- Both synchronous and asynchronous endpoints for attribute retrieval
8- Custom permission for reviewer access to all product reviews
9- Review management endpoints for products
10"""
11import os
12import tempfile
14from django.shortcuts import render
15from django.core.management import call_command
16from django.http import HttpResponse, JsonResponse
17from django.views.decorators.http import require_http_methods
20from src.common.permissions import IsOrderManager
21from src.products.models.product import Bracelet, Color, Earring, Metal, Necklace, Pendant, Ring, Stone, Collection, Watch
23from src.products.serializers.product import (
24 BraceletItemSerializer,
25 BraceletListSerializer,
26 CollectionSerializer,
27 ColorSerializer,
28 EarringItemSerializer,
29 EarringListSerializer,
30 MetalSerializer,
31 NecklaceItemSerializer,
32 NecklaceListSerializer,
33 PendantItemSerializer,
34 PendantListSerializer,
35 RingItemSerializer,
36 RingListSerializer,
37 StoneSerializer,
38 WatchItemSerializer,
39 WatchListSerializer,
40)
43from src.products.views.base import (
44 BaseAttributeView,
45 BaseProductItemView,
46 BaseProductListView,
47)
49from rest_framework.views import APIView
50from rest_framework.permissions import IsAuthenticated
51from rest_framework.response import Response
52from src.products.models.review import Review
53from src.products.serializers.review import ReviewSerializer
54from django.contrib.contenttypes.models import ContentType
55from rest_framework import status
58class EarringListView(BaseProductListView):
59 model = Earring
60 serializer_class = EarringListSerializer
63class NecklaceListView(BaseProductListView):
64 model = Necklace
65 serializer_class = NecklaceListSerializer
68class PendantListView(BaseProductListView):
69 model = Pendant
70 serializer_class = PendantListSerializer
73class BraceletListView(BaseProductListView):
74 model = Bracelet
75 serializer_class = BraceletListSerializer
78class WatchListView(BaseProductListView):
79 model = Watch
80 serializer_class = WatchListSerializer
83class RingListView(BaseProductListView):
84 model = Ring
85 serializer_class = RingListSerializer
88class EarringItemView(BaseProductItemView):
89 model = Earring
90 serializer_class = EarringItemSerializer
93class NecklaceItemView(BaseProductItemView):
94 model = Necklace
95 serializer_class = NecklaceItemSerializer
98class PendantItemView(BaseProductItemView):
99 model = Pendant
100 serializer_class = PendantItemSerializer
103class BraceletItemView(BaseProductItemView):
104 model = Bracelet
105 serializer_class = BraceletItemSerializer
108class WatchItemView(BaseProductItemView):
109 model = Watch
110 serializer_class = WatchItemSerializer
113class RingItemView(BaseProductItemView):
114 model = Ring
115 serializer_class = RingItemSerializer
118class CollectionRetrieveView(BaseAttributeView):
119 model = Collection
120 serializer_class = CollectionSerializer
123class ColorRetrieveView(BaseAttributeView):
124 model = Color
125 serializer_class = ColorSerializer
128class MetalRetrieveView(BaseAttributeView):
129 model = Metal
130 serializer_class = MetalSerializer
133class StoneRetrieveView(BaseAttributeView):
134 model = Stone
135 serializer_class = StoneSerializer
138class ProductAllReviewsView(APIView):
139 permission_classes = [IsAuthenticated, IsOrderManager]
141 def get(self, request, category, pk):
143 model_map = {
144 'earring': Earring,
145 'necklace': Necklace,
146 'pendant': Pendant,
147 'bracelet': Bracelet,
148 'watch': Watch,
149 'ring': Ring,
150 }
152 model = model_map.get(category.lower())
154 if not model:
155 return Response(
156 {'detail': 'Invalid category.'},
157 status=status.HTTP_400_BAD_REQUEST,
158 )
159 try:
160 product = model.objects.get(pk=pk)
161 except model.DoesNotExist:
162 return Response(
163 {'detail': 'Product not found.'},
164 status=status.HTTP_404_NOT_FOUND,
165 )
167 # Get all reviews for this product
168 content_type = ContentType.objects.get_for_model(model)
169 reviews = Review.objects.filter(
170 content_type=content_type, object_id=product.id
171 ).order_by('-created_at')
172 serializer = ReviewSerializer(reviews, many=True)
174 return Response({'reviews': serializer.data})
177def catalog_page(request):
178 """Display the catalog download page"""
179 return render(request, 'catalog_download.html')
182@require_http_methods(["POST"])
183def generate_catalog(request):
184 """Generate and return the PDF catalog"""
185 try:
186 # Create temporary file
187 with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp_file:
188 tmp_path = tmp_file.name
190 # Generate PDF
191 call_command('generate_product_catalog', output=tmp_path)
193 # Check if file was created successfully
194 if not os.path.exists(tmp_path):
195 return JsonResponse({'error': 'Failed to generate PDF'}, status=500)
197 # Get file size
198 file_size = os.path.getsize(tmp_path)
200 # Read and return PDF
201 with open(tmp_path, 'rb') as pdf_file:
202 pdf_data = pdf_file.read()
204 # Clean up temp file
205 os.unlink(tmp_path)
207 # Return PDF with proper headers
208 response = HttpResponse(pdf_data, content_type='application/pdf')
209 response['Content-Disposition'] = 'attachment; filename="product_catalog.pdf"'
210 response['Content-Length'] = str(file_size)
211 return response
213 except Exception as e:
214 return JsonResponse({'error': f'Error generating catalog: {str(e)}'}, status=500)
217def download_catalog(request):
218 """Legacy view - redirect to new page"""
219 return catalog_page(request)