Skip to content

Commit 49bb8eb

Browse files
Merge pull request #31 from RandomProgramm3r/develop
feat(promo): Refactor CompanyPromoDetailView to RetrieveUpdateAPIView Refactor CompanyPromoDetailView from a generic APIView to DRF’s RetrieveUpdateAPIView in order to leverage built-in retrieve/update behavior and simplify the implementation. Key changes: - Use `RetrieveUpdateAPIView` as the base class to get object lookup, serialization, validation and response handling for free - Remove manual `get()` and `patch()` methods - Add `business.permissions.IsPromoOwner` to `permission_classes` for declarative authorization - Define `queryset = Promo.objects.with_related().only(...).for_company()` on the view to centralize and optimize data access - Implement `with_related()` and `for_company(user)` on the Promo queryset/manager for efficient prefetching and filtering - Drop unused imports and old helper code This change reduces boilerplate, improves consistency with DRF idioms, and makes permission checks and queryset logic easier to maintain.
2 parents fdf5002 + 90ccf3f commit 49bb8eb

File tree

4 files changed

+52
-128
lines changed

4 files changed

+52
-128
lines changed

promo_code/business/managers.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,33 @@ def create_company(self, email, name, password=None, **extra_fields):
2222

2323

2424
class PromoManager(django.db.models.Manager):
25+
def get_queryset(self):
26+
return super().get_queryset()
27+
28+
def with_related(self):
29+
return (
30+
self.select_related('company')
31+
.prefetch_related('unique_codes')
32+
.only(
33+
'id',
34+
'company',
35+
'description',
36+
'image_url',
37+
'target',
38+
'max_count',
39+
'active_from',
40+
'active_until',
41+
'mode',
42+
'promo_common',
43+
'created_at',
44+
'company__id',
45+
'company__name',
46+
)
47+
)
48+
49+
def for_company(self, user):
50+
return self.with_related().filter(company=user)
51+
2552
@django.db.transaction.atomic
2653
def create_promo(
2754
self,

promo_code/business/pagination.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import rest_framework.exceptions
21
import rest_framework.pagination
32
import rest_framework.response
43

promo_code/business/permissions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,14 @@
55

66
class IsCompanyUser(rest_framework.permissions.BasePermission):
77
def has_permission(self, request, view):
8+
if not request.user or not request.user.is_authenticated:
9+
return False
10+
811
return isinstance(request.user, business.models.Company)
12+
13+
14+
class IsPromoOwner(rest_framework.permissions.BasePermission):
15+
message = 'The promo code does not belong to this company.'
16+
17+
def has_object_permission(self, request, view, obj):
18+
return getattr(obj, 'company_id', None) == request.user.id

promo_code/business/views.py

Lines changed: 15 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import re
22

33
import django.db.models
4-
import django.shortcuts
54
import pycountry
65
import rest_framework.exceptions
76
import rest_framework.generics
@@ -150,29 +149,7 @@ class CompanyPromoListView(rest_framework.generics.ListAPIView):
150149
pagination_class = business.pagination.CustomLimitOffsetPagination
151150

152151
def get_queryset(self):
153-
queryset = (
154-
business.models.Promo.objects.filter(
155-
company=self.request.user,
156-
)
157-
.select_related('company')
158-
.prefetch_related('unique_codes')
159-
.only(
160-
'id',
161-
'company',
162-
'description',
163-
'image_url',
164-
'target',
165-
'max_count',
166-
'active_from',
167-
'active_until',
168-
'mode',
169-
'promo_common',
170-
'created_at',
171-
'company__id',
172-
'company__name',
173-
)
174-
)
175-
152+
queryset = business.models.Promo.objects.for_company(self.request.user)
176153
countries = [
177154
country.strip()
178155
for group in self.request.query_params.getlist('country', [])
@@ -298,113 +275,24 @@ def _validate_limit(self):
298275
)
299276

300277

301-
class CompanyPromoDetailView(rest_framework.views.APIView):
278+
class CompanyPromoDetailView(rest_framework.generics.RetrieveUpdateAPIView):
279+
"""
280+
Retrieve (GET) and partially update (PATCH) detailed information
281+
about a company’s promo.
282+
"""
283+
284+
http_method_names = ['get', 'patch', 'options', 'head']
285+
286+
serializer_class = business.serializers.PromoDetailSerializer
287+
302288
permission_classes = [
303289
rest_framework.permissions.IsAuthenticated,
304290
business.permissions.IsCompanyUser,
291+
business.permissions.IsPromoOwner,
305292
]
306293

307294
lookup_field = 'id'
308295

309-
def get_queryset(self):
310-
user = self.request.user
311-
return (
312-
business.models.Promo.objects.filter(company=user)
313-
.select_related('company')
314-
.prefetch_related('unique_codes')
315-
.select_related('company')
316-
.prefetch_related('unique_codes')
317-
.only(
318-
'id',
319-
'company',
320-
'description',
321-
'image_url',
322-
'target',
323-
'max_count',
324-
'active_from',
325-
'active_until',
326-
'mode',
327-
'promo_common',
328-
'created_at',
329-
'company__id',
330-
'company__name',
331-
)
332-
)
333-
334-
def get(self, request, id):
335-
try:
336-
promo = business.models.Promo.objects.get(
337-
id=id,
338-
)
339-
except business.models.Promo.DoesNotExist:
340-
raise rest_framework.exceptions.NotFound(
341-
'Promo not found,',
342-
)
343-
344-
if promo.company != request.user:
345-
return rest_framework.response.Response(
346-
{
347-
'status': 'error',
348-
'message': (
349-
'The promo code does not belong to this company.'
350-
),
351-
},
352-
status=rest_framework.status.HTTP_403_FORBIDDEN,
353-
)
354-
355-
serializer = business.serializers.PromoDetailSerializer(
356-
promo,
357-
)
358-
359-
return rest_framework.response.Response(
360-
serializer.data,
361-
status=rest_framework.status.HTTP_200_OK,
362-
)
363-
364-
def patch(self, request, id, *args, **kwargs):
365-
try:
366-
promo = business.models.Promo.objects.get(
367-
id=id,
368-
)
369-
except business.models.Promo.DoesNotExist:
370-
return rest_framework.response.Response(
371-
{
372-
'status': 'error',
373-
'message': 'Promo code not found.',
374-
},
375-
status=rest_framework.status.HTTP_404_NOT_FOUND,
376-
)
377-
378-
if promo.company != request.user:
379-
return rest_framework.response.Response(
380-
{
381-
'status': 'error',
382-
'message': ('Promo code does not belong to this company.'),
383-
},
384-
status=rest_framework.status.HTTP_403_FORBIDDEN,
385-
)
386-
387-
serializer = business.serializers.PromoDetailSerializer(
388-
promo,
389-
data=request.data,
390-
partial=True,
391-
context={
392-
'request': request,
393-
},
394-
)
395-
396-
if not serializer.is_valid():
397-
return rest_framework.response.Response(
398-
{
399-
'status': 'error',
400-
'message': 'Request data error.',
401-
},
402-
status=rest_framework.status.HTTP_400_BAD_REQUEST,
403-
)
404-
405-
serializer.save()
406-
407-
return rest_framework.response.Response(
408-
serializer.data,
409-
status=rest_framework.status.HTTP_200_OK,
410-
)
296+
# Use an enriched base queryset without pre-filtering by company,
297+
# so that ownership mismatches raise 403 Forbidden (not 404 Not Found).
298+
queryset = business.models.Promo.objects.with_related()

0 commit comments

Comments
 (0)