11import django .db .models
22import django .db .transaction
33import django .shortcuts
4- import django .utils .timezone
5- import rest_framework .exceptions
64import rest_framework .generics
75import rest_framework .permissions
86import rest_framework .response
119import rest_framework_simplejwt .tokens
1210import rest_framework_simplejwt .views
1311
14- import business .constants
1512import business .models
1613import core .pagination
17- import user .antifraud_service
1814import user .models
1915import user .permissions
2016import user .serializers
@@ -76,25 +72,19 @@ class UserPromoDetailView(rest_framework.generics.RetrieveAPIView):
7672 Retrieve (GET) information about the promo without receiving a promo code.
7773 """
7874
79- queryset = (
80- business .models .Promo .objects .select_related ('company' )
81- .prefetch_related (
82- 'unique_codes' ,
83- )
84- .only (
85- 'id' ,
86- 'company__id' ,
87- 'company__name' ,
88- 'description' ,
89- 'image_url' ,
90- 'active' ,
91- 'active_from' ,
92- 'active_until' ,
93- 'mode' ,
94- 'used_count' ,
95- 'like_count' ,
96- 'comment_count' ,
97- )
75+ queryset = business .models .Promo .objects .select_related ('company' ).only (
76+ 'id' ,
77+ 'company__id' ,
78+ 'company__name' ,
79+ 'description' ,
80+ 'image_url' ,
81+ 'active' ,
82+ 'active_from' ,
83+ 'active_until' ,
84+ 'mode' ,
85+ 'used_count' ,
86+ 'like_count' ,
87+ 'comment_count' ,
9888 )
9989
10090 serializer_class = user .serializers .UserPromoDetailSerializer
@@ -113,116 +103,26 @@ class UserFeedView(rest_framework.generics.ListAPIView):
113103
114104 def get_queryset (self ):
115105 user = self .request .user
116- user_age = user .other .get ('age' )
117- user_country_raw = user .other .get ('country' )
118- user_country = user_country_raw .lower () if user_country_raw else None
119-
120- queryset = business .models .Promo .objects .select_related ('company' )
121-
122- today_utc = django .utils .timezone .now ().date ()
123-
124- q_active_time = (
125- django .db .models .Q (active_from__lte = today_utc )
126- | django .db .models .Q (active_from__isnull = True )
127- ) & (
128- django .db .models .Q (active_until__gte = today_utc )
129- | django .db .models .Q (active_until__isnull = True )
130- )
131-
132- q_common_active = django .db .models .Q (
133- mode = business .constants .PROMO_MODE_COMMON ,
134- used_count__lt = django .db .models .F ('max_count' ),
135- )
136-
137- has_available_unique_codes = business .models .PromoCode .objects .filter (
138- promo = django .db .models .OuterRef ('pk' ),
139- is_used = False ,
140- )
141-
142- queryset = queryset .annotate (
143- _has_available_unique_codes = django .db .models .Exists (
144- has_available_unique_codes ,
145- ),
146- )
147- q_unique_active = django .db .models .Q (
148- mode = business .constants .PROMO_MODE_UNIQUE ,
149- _has_available_unique_codes = True ,
150- )
151-
152- q_is_active_by_rules = q_active_time & (
153- q_common_active | q_unique_active
154- )
155-
156- q_target_empty = django .db .models .Q (target = {})
157-
158- q_country_target_matches = django .db .models .Q ()
159- if user_country :
160- q_country_target_matches = django .db .models .Q (
161- target__country__iexact = user_country ,
162- )
163-
164- q_country_target_not_set_or_empty = ~ django .db .models .Q (
165- target__has_key = 'country' ,
166- ) | django .db .models .Q (target__country__isnull = True )
167- q_user_meets_country_target = (
168- q_country_target_matches | q_country_target_not_set_or_empty
169- )
170106
171- q_age_target_not_set = ~ django .db .models .Q (
172- target__has_key = 'age_from' ,
173- ) & ~ django .db .models .Q (target__has_key = 'age_until' )
174- q_user_meets_age_target = q_age_target_not_set
175-
176- if user_age is not None :
177- q_age_from_ok = (
178- ~ django .db .models .Q (target__has_key = 'age_from' )
179- | django .db .models .Q (target__age_from__isnull = True )
180- | django .db .models .Q (target__age_from__lte = user_age )
181- )
182- q_age_until_ok = (
183- ~ django .db .models .Q (target__has_key = 'age_until' )
184- | django .db .models .Q (target__age_until__isnull = True )
185- | django .db .models .Q (target__age_until__gte = user_age )
186- )
187- q_user_age_in_defined_range = q_age_from_ok & q_age_until_ok
188- q_user_meets_age_target = (
189- q_age_target_not_set | q_user_age_in_defined_range
190- )
191-
192- q_user_is_targeted = q_target_empty | (
193- q_user_meets_country_target & q_user_meets_age_target
107+ user_age = user .other .get ('age' )
108+ user_country = user .other .get ('country' ).lower ()
109+ active_filter = self .request .query_params .get ('active' )
110+
111+ return business .models .Promo .objects .get_feed_for_user (
112+ user ,
113+ active_filter = active_filter ,
114+ user_country = user_country ,
115+ user_age = user_age ,
194116 )
195117
196- queryset = queryset .filter (q_user_is_targeted )
197-
198- active_param_str = self .request .query_params .get ('active' )
199- if active_param_str is not None :
200- active_param_bool = active_param_str .lower () == 'true'
201- if active_param_bool :
202- queryset = queryset .filter (q_is_active_by_rules )
203- else :
204- queryset = queryset .exclude (q_is_active_by_rules )
205-
206- return queryset .order_by ('-created_at' )
207-
208118 def filter_queryset (self , queryset ):
209119 queryset = super ().filter_queryset (queryset )
210-
211120 category_param = self .request .query_params .get ('category' )
121+
212122 if category_param :
213- category_param = category_param .lower ()
214- if category_param :
215- filtered_pks = []
216- for promo in queryset :
217- target_categories = promo .target .get ('categories' )
218- if not isinstance (target_categories , list ):
219- continue
220- if any (
221- cat_name .lower () == category_param
222- for cat_name in target_categories
223- ):
224- filtered_pks .append (promo .pk )
225- queryset = queryset .filter (pk__in = filtered_pks )
123+ needle = f'"{ category_param .lower ()} "'
124+ queryset = queryset .filter (target__categories__icontains = needle )
125+
226126 return queryset
227127
228128 def list (self , request , * args , ** kwargs ):
@@ -288,7 +188,21 @@ def delete(self, request, id):
288188 )
289189
290190
291- class PromoCommentListCreateView (rest_framework .generics .ListCreateAPIView ):
191+ class PromoObjectMixin :
192+ """Mixin for retrieving the Promo object and saving it to self.promo"""
193+
194+ def dispatch (self , request , * args , ** kwargs ):
195+ self .promo = django .shortcuts .get_object_or_404 (
196+ business .models .Promo .objects .select_for_update (),
197+ pk = self .kwargs .get ('promo_id' ),
198+ )
199+ return super ().dispatch (request , * args , ** kwargs )
200+
201+
202+ class PromoCommentListCreateView (
203+ PromoObjectMixin ,
204+ rest_framework .generics .ListCreateAPIView ,
205+ ):
292206 permission_classes = [rest_framework .permissions .IsAuthenticated ]
293207
294208 pagination_class = core .pagination .CustomLimitOffsetPagination
@@ -299,28 +213,14 @@ def get_serializer_class(self):
299213 return user .serializers .CommentSerializer
300214
301215 def get_queryset (self ):
302- promo_id = self .kwargs .get ('promo_id' )
303- try :
304- promo = business .models .Promo .objects .get (pk = promo_id )
305- except business .models .Promo .DoesNotExist :
306- raise rest_framework .exceptions .NotFound (detail = 'Promo not found.' )
307-
308216 return user .models .PromoComment .objects .filter (
309- promo = promo ,
217+ promo = self . promo ,
310218 ).select_related ('author' )
311219
312220 def perform_create (self , serializer ):
313- promo_id = self .kwargs .get ('promo_id' )
314- try :
315- promo = business .models .Promo .objects .get (pk = promo_id )
316- except business .models .Promo .DoesNotExist :
317- raise rest_framework .exceptions .ValidationError (
318- {'promo_id' : 'Promo not found.' },
319- )
320-
321- serializer .save (author = self .request .user , promo = promo )
322- promo .comment_count = django .db .models .F ('comment_count' ) + 1
323- promo .save (update_fields = ['comment_count' ])
221+ serializer .save (author = self .request .user , promo = self .promo )
222+ self .promo .comment_count = django .db .models .F ('comment_count' ) + 1
223+ self .promo .save (update_fields = ['comment_count' ])
324224
325225 def create (self , request , * args , ** kwargs ):
326226 create_serializer = self .get_serializer (data = request .data )
@@ -346,6 +246,7 @@ def list(self, request, *args, **kwargs):
346246
347247
348248class PromoCommentDetailView (
249+ PromoObjectMixin ,
349250 rest_framework .generics .RetrieveUpdateDestroyAPIView ,
350251):
351252 permission_classes = [
@@ -362,13 +263,8 @@ def get_serializer_class(self):
362263 return user .serializers .CommentSerializer
363264
364265 def get_queryset (self ):
365- promo_id = self .kwargs .get ('promo_id' )
366- try :
367- promo = business .models .Promo .objects .get (pk = promo_id )
368- except business .models .Promo .DoesNotExist :
369- raise rest_framework .exceptions .NotFound (detail = 'Promo not found.' )
370266 return user .models .PromoComment .objects .filter (
371- promo = promo ,
267+ promo = self . promo ,
372268 ).select_related ('author' )
373269
374270 def update (self , request , * args , ** kwargs ):
0 commit comments