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

1""" 

2This module contains views for product listing, detail, attribute retrieval, and review management. 

3 

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 

13 

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 

18 

19 

20from src.common.permissions import IsOrderManager 

21from src.products.models.product import Bracelet, Color, Earring, Metal, Necklace, Pendant, Ring, Stone, Collection, Watch 

22 

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) 

41 

42 

43from src.products.views.base import ( 

44 BaseAttributeView, 

45 BaseProductItemView, 

46 BaseProductListView, 

47) 

48 

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 

56 

57 

58class EarringListView(BaseProductListView): 

59 model = Earring 

60 serializer_class = EarringListSerializer 

61 

62 

63class NecklaceListView(BaseProductListView): 

64 model = Necklace 

65 serializer_class = NecklaceListSerializer 

66 

67 

68class PendantListView(BaseProductListView): 

69 model = Pendant 

70 serializer_class = PendantListSerializer 

71 

72 

73class BraceletListView(BaseProductListView): 

74 model = Bracelet 

75 serializer_class = BraceletListSerializer 

76 

77 

78class WatchListView(BaseProductListView): 

79 model = Watch 

80 serializer_class = WatchListSerializer 

81 

82 

83class RingListView(BaseProductListView): 

84 model = Ring 

85 serializer_class = RingListSerializer 

86 

87 

88class EarringItemView(BaseProductItemView): 

89 model = Earring 

90 serializer_class = EarringItemSerializer 

91 

92 

93class NecklaceItemView(BaseProductItemView): 

94 model = Necklace 

95 serializer_class = NecklaceItemSerializer 

96 

97 

98class PendantItemView(BaseProductItemView): 

99 model = Pendant 

100 serializer_class = PendantItemSerializer 

101 

102 

103class BraceletItemView(BaseProductItemView): 

104 model = Bracelet 

105 serializer_class = BraceletItemSerializer 

106 

107 

108class WatchItemView(BaseProductItemView): 

109 model = Watch 

110 serializer_class = WatchItemSerializer 

111 

112 

113class RingItemView(BaseProductItemView): 

114 model = Ring 

115 serializer_class = RingItemSerializer 

116 

117 

118class CollectionRetrieveView(BaseAttributeView): 

119 model = Collection 

120 serializer_class = CollectionSerializer 

121 

122 

123class ColorRetrieveView(BaseAttributeView): 

124 model = Color 

125 serializer_class = ColorSerializer 

126 

127 

128class MetalRetrieveView(BaseAttributeView): 

129 model = Metal 

130 serializer_class = MetalSerializer 

131 

132 

133class StoneRetrieveView(BaseAttributeView): 

134 model = Stone 

135 serializer_class = StoneSerializer 

136 

137 

138class ProductAllReviewsView(APIView): 

139 permission_classes = [IsAuthenticated, IsOrderManager] 

140 

141 def get(self, request, category, pk): 

142 

143 model_map = { 

144 'earring': Earring, 

145 'necklace': Necklace, 

146 'pendant': Pendant, 

147 'bracelet': Bracelet, 

148 'watch': Watch, 

149 'ring': Ring, 

150 } 

151 

152 model = model_map.get(category.lower()) 

153 

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 ) 

166 

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) 

173 

174 return Response({'reviews': serializer.data}) 

175 

176 

177def catalog_page(request): 

178 """Display the catalog download page""" 

179 return render(request, 'catalog_download.html') 

180 

181 

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 

189 

190 # Generate PDF 

191 call_command('generate_product_catalog', output=tmp_path) 

192 

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) 

196 

197 # Get file size 

198 file_size = os.path.getsize(tmp_path) 

199 

200 # Read and return PDF 

201 with open(tmp_path, 'rb') as pdf_file: 

202 pdf_data = pdf_file.read() 

203 

204 # Clean up temp file 

205 os.unlink(tmp_path) 

206 

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 

212 

213 except Exception as e: 

214 return JsonResponse({'error': f'Error generating catalog: {str(e)}'}, status=500) 

215 

216 

217def download_catalog(request): 

218 """Legacy view - redirect to new page""" 

219 return catalog_page(request)