Skip to content

Commit 9a70256

Browse files
refactor(core): Centralize core serializer components
This commit centralizes several core serializer components into the `core` app to promote reusability and improve code organization across the project. Key changes: - **Moved Serializers to Core:** `BasePromoSerializer`, `TargetSerializer`, and `CountryField` have been moved from the `business` app to `core.serializers`. This allows them to be reused by other apps, such as `user`. - **Simplified User Serializers:** The `UserFeedQuerySerializer` now inherits from the `BaseLimitOffsetPaginationSerializer`, and the `OtherFieldSerializer` now uses the centralized `core.serializers.CountryField`, reducing code duplication and simplifying validation.
1 parent 6d2482a commit 9a70256

File tree

4 files changed

+271
-332
lines changed

4 files changed

+271
-332
lines changed

promo_code/business/serializers.py

Lines changed: 7 additions & 250 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import django.contrib.auth.password_validation
44
import django.db.transaction
5-
import pycountry
65
import rest_framework.exceptions
76
import rest_framework.serializers
87
import 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-
165142
class 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,14 @@ 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.BasePromoSerializer):
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.BasePromoSerializer.Meta):
183+
fields = ('url',) + core.serializers.BasePromoSerializer.Meta.fields
427184

428185
def create(self, validated_data):
429186
target_data = validated_data.pop('target')
@@ -468,7 +225,7 @@ def validate(self, attrs):
468225
return attrs
469226

470227

471-
class PromoDetailSerializer(BasePromoSerializer):
228+
class PromoDetailSerializer(core.serializers.BasePromoSerializer):
472229
promo_id = rest_framework.serializers.UUIDField(
473230
source='id',
474231
read_only=True,
@@ -496,8 +253,8 @@ class PromoDetailSerializer(BasePromoSerializer):
496253

497254
promo_unique = rest_framework.serializers.SerializerMethodField()
498255

499-
class Meta(BasePromoSerializer.Meta):
500-
fields = BasePromoSerializer.Meta.fields + (
256+
class Meta(core.serializers.BasePromoSerializer.Meta):
257+
fields = core.serializers.BasePromoSerializer.Meta.fields + (
501258
'promo_id',
502259
'company_name',
503260
'like_count',

promo_code/core/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

0 commit comments

Comments
 (0)