22
33import django .contrib .auth .password_validation
44import django .db .transaction
5- import pycountry
65import rest_framework .exceptions
76import rest_framework .serializers
87import rest_framework_simplejwt .exceptions
@@ -140,36 +139,14 @@ def get_active_company_from_token(self, token):
140139 return company
141140
142141
143- class CountryField (rest_framework .serializers .CharField ):
144- """
145- Custom field for validating country codes according to ISO 3166-1 alpha-2.
146- """
147-
148- def __init__ (self , ** kwargs ):
149- kwargs ['allow_blank' ] = False
150- kwargs ['min_length' ] = business .constants .TARGET_COUNTRY_CODE_LENGTH
151- kwargs ['max_length' ] = business .constants .TARGET_COUNTRY_CODE_LENGTH
152- super ().__init__ (** kwargs )
153-
154- def to_internal_value (self , data ):
155- code = super ().to_internal_value (data )
156- try :
157- pycountry .countries .lookup (code .upper ())
158- except LookupError :
159- raise rest_framework .serializers .ValidationError (
160- 'Invalid ISO 3166-1 alpha-2 country code.' ,
161- )
162- return code
163-
164-
165142class MultiCountryField (rest_framework .serializers .ListField ):
166143 """
167144 Custom field for handling multiple country codes,
168145 passed either as a comma-separated list or as multiple parameters.
169146 """
170147
171148 def __init__ (self , ** kwargs ):
172- kwargs ['child' ] = CountryField ()
149+ kwargs ['child' ] = core . serializers . CountryField ()
173150 kwargs ['allow_empty' ] = False
174151 super ().__init__ (** kwargs )
175152
@@ -196,234 +173,16 @@ def to_internal_value(self, data):
196173 return super ().to_internal_value (data )
197174
198175
199- class TargetSerializer (rest_framework .serializers .Serializer ):
200- age_from = rest_framework .serializers .IntegerField (
201- min_value = business .constants .TARGET_AGE_MIN ,
202- max_value = business .constants .TARGET_AGE_MAX ,
203- required = False ,
204- )
205- age_until = rest_framework .serializers .IntegerField (
206- min_value = business .constants .TARGET_AGE_MIN ,
207- max_value = business .constants .TARGET_AGE_MAX ,
208- required = False ,
209- )
210- country = CountryField (required = False )
211-
212- categories = rest_framework .serializers .ListField (
213- child = rest_framework .serializers .CharField (
214- min_length = business .constants .TARGET_CATEGORY_MIN_LENGTH ,
215- max_length = business .constants .TARGET_CATEGORY_MAX_LENGTH ,
216- allow_blank = False ,
217- ),
218- max_length = business .constants .TARGET_CATEGORY_MAX_ITEMS ,
219- required = False ,
220- allow_empty = True ,
221- )
222-
223- def validate (self , data ):
224- age_from = data .get ('age_from' )
225- age_until = data .get ('age_until' )
226-
227- if (
228- age_from is not None
229- and age_until is not None
230- and age_from > age_until
231- ):
232- raise rest_framework .serializers .ValidationError (
233- {'age_until' : 'Must be greater than or equal to age_from.' },
234- )
235- return data
236-
237-
238- class BasePromoSerializer (rest_framework .serializers .ModelSerializer ):
239- """
240- Base serializer for promo, containing validation and representation logic.
241- """
242-
243- image_url = rest_framework .serializers .URLField (
244- required = False ,
245- allow_blank = False ,
246- max_length = business .constants .PROMO_IMAGE_URL_MAX_LENGTH ,
247- )
248- description = rest_framework .serializers .CharField (
249- min_length = business .constants .PROMO_DESC_MIN_LENGTH ,
250- max_length = business .constants .PROMO_DESC_MAX_LENGTH ,
251- required = True ,
252- )
253- target = TargetSerializer (required = True , allow_null = True )
254- promo_common = rest_framework .serializers .CharField (
255- min_length = business .constants .PROMO_COMMON_CODE_MIN_LENGTH ,
256- max_length = business .constants .PROMO_COMMON_CODE_MAX_LENGTH ,
257- required = False ,
258- allow_null = True ,
259- allow_blank = False ,
260- )
261- promo_unique = rest_framework .serializers .ListField (
262- child = rest_framework .serializers .CharField (
263- min_length = business .constants .PROMO_UNIQUE_CODE_MIN_LENGTH ,
264- max_length = business .constants .PROMO_UNIQUE_CODE_MAX_LENGTH ,
265- allow_blank = False ,
266- ),
267- min_length = business .constants .PROMO_UNIQUE_LIST_MIN_ITEMS ,
268- max_length = business .constants .PROMO_UNIQUE_LIST_MAX_ITEMS ,
269- required = False ,
270- allow_null = True ,
271- )
272-
273- class Meta :
274- model = business .models .Promo
275- fields = (
276- 'description' ,
277- 'image_url' ,
278- 'target' ,
279- 'max_count' ,
280- 'active_from' ,
281- 'active_until' ,
282- 'mode' ,
283- 'promo_common' ,
284- 'promo_unique' ,
285- )
286-
287- def validate (self , data ):
288- """
289- Main validation method.
290- Determines the mode and calls the corresponding validation method.
291- """
292-
293- mode = data .get ('mode' , getattr (self .instance , 'mode' , None ))
294-
295- if mode == business .constants .PROMO_MODE_COMMON :
296- self ._validate_common (data )
297- elif mode == business .constants .PROMO_MODE_UNIQUE :
298- self ._validate_unique (data )
299- elif mode is None :
300- raise rest_framework .serializers .ValidationError (
301- {'mode' : 'This field is required.' },
302- )
303- else :
304- raise rest_framework .serializers .ValidationError (
305- {'mode' : 'Invalid mode.' },
306- )
307-
308- return data
309-
310- def _validate_common (self , data ):
311- """
312- Validations for COMMON promo mode.
313- """
314-
315- if 'promo_unique' in data and data ['promo_unique' ] is not None :
316- raise rest_framework .serializers .ValidationError (
317- {'promo_unique' : 'This field is not allowed for COMMON mode.' },
318- )
319-
320- if self .instance is None and not data .get ('promo_common' ):
321- raise rest_framework .serializers .ValidationError (
322- {'promo_common' : 'This field is required for COMMON mode.' },
323- )
324-
325- new_max_count = data .get ('max_count' )
326- if self .instance and new_max_count is not None :
327- used_count = self .instance .get_used_codes_count
328- if used_count > new_max_count :
329- raise rest_framework .serializers .ValidationError (
330- {
331- 'max_count' : (
332- f'max_count ({ new_max_count } ) cannot be less than '
333- f'used_count ({ used_count } ).'
334- ),
335- },
336- )
337-
338- effective_max_count = (
339- new_max_count
340- if new_max_count is not None
341- else getattr (self .instance , 'max_count' , None )
342- )
343-
344- min_c = business .constants .PROMO_COMMON_MIN_COUNT
345- max_c = business .constants .PROMO_COMMON_MAX_COUNT
346- if effective_max_count is not None and not (
347- min_c <= effective_max_count <= max_c
348- ):
349- raise rest_framework .serializers .ValidationError (
350- {
351- 'max_count' : (
352- f'Must be between { min_c } and { max_c } for COMMON mode.'
353- ),
354- },
355- )
356-
357- def _validate_unique (self , data ):
358- """
359- Validations for UNIQUE promo mode.
360- """
361-
362- if 'promo_common' in data and data ['promo_common' ] is not None :
363- raise rest_framework .serializers .ValidationError (
364- {'promo_common' : 'This field is not allowed for UNIQUE mode.' },
365- )
366-
367- if self .instance is None and not data .get ('promo_unique' ):
368- raise rest_framework .serializers .ValidationError (
369- {'promo_unique' : 'This field is required for UNIQUE mode.' },
370- )
371-
372- effective_max_count = data .get (
373- 'max_count' ,
374- getattr (self .instance , 'max_count' , None ),
375- )
376-
377- if (
378- effective_max_count is not None
379- and effective_max_count
380- != business .constants .PROMO_UNIQUE_MAX_COUNT
381- ):
382- raise rest_framework .serializers .ValidationError (
383- {
384- 'max_count' : (
385- 'Must be equal to '
386- f'{ business .constants .PROMO_UNIQUE_MAX_COUNT } '
387- 'for UNIQUE mode.'
388- ),
389- },
390- )
391-
392- def to_representation (self , instance ):
393- """
394- Controls the display of fields in the response.
395- """
396-
397- data = super ().to_representation (instance )
398-
399- if not instance .image_url :
400- data .pop ('image_url' , None )
401-
402- if instance .mode == business .constants .PROMO_MODE_UNIQUE :
403- data .pop ('promo_common' , None )
404- if 'promo_unique' in self .fields and isinstance (
405- self .fields ['promo_unique' ],
406- rest_framework .serializers .SerializerMethodField ,
407- ):
408- data ['promo_unique' ] = self .get_promo_unique (instance )
409- else :
410- data ['promo_unique' ] = [
411- code .code for code in instance .unique_codes .all ()
412- ]
413- else :
414- data .pop ('promo_unique' , None )
415-
416- return data
417-
418-
419- class PromoCreateSerializer (BasePromoSerializer ):
176+ class PromoCreateSerializer (core .serializers .BaseCompanyPromoSerializer ):
420177 url = rest_framework .serializers .HyperlinkedIdentityField (
421178 view_name = 'api-business:promo-detail' ,
422179 lookup_field = 'id' ,
423180 )
424181
425- class Meta (BasePromoSerializer .Meta ):
426- fields = ('url' ,) + BasePromoSerializer .Meta .fields
182+ class Meta (core .serializers .BaseCompanyPromoSerializer .Meta ):
183+ fields = (
184+ 'url' ,
185+ ) + core .serializers .BaseCompanyPromoSerializer .Meta .fields
427186
428187 def create (self , validated_data ):
429188 target_data = validated_data .pop ('target' )
@@ -468,7 +227,7 @@ def validate(self, attrs):
468227 return attrs
469228
470229
471- class PromoDetailSerializer (BasePromoSerializer ):
230+ class PromoDetailSerializer (core . serializers . BaseCompanyPromoSerializer ):
472231 promo_id = rest_framework .serializers .UUIDField (
473232 source = 'id' ,
474233 read_only = True ,
@@ -496,8 +255,8 @@ class PromoDetailSerializer(BasePromoSerializer):
496255
497256 promo_unique = rest_framework .serializers .SerializerMethodField ()
498257
499- class Meta (BasePromoSerializer .Meta ):
500- fields = BasePromoSerializer .Meta .fields + (
258+ class Meta (core . serializers . BaseCompanyPromoSerializer .Meta ):
259+ fields = core . serializers . BaseCompanyPromoSerializer .Meta .fields + (
501260 'promo_id' ,
502261 'company_name' ,
503262 'like_count' ,
@@ -514,13 +273,12 @@ def get_promo_unique(self, obj):
514273 def update (self , instance , validated_data ):
515274 target_data = validated_data .pop ('target' , None )
516275
517- for attr , value in validated_data .items ():
518- setattr (instance , attr , value )
276+ instance = super ().update (instance , validated_data )
519277
520278 if target_data is not None :
521279 instance .target = target_data
280+ instance .save (update_fields = ['target' ])
522281
523- instance .save ()
524282 return instance
525283
526284
0 commit comments