Skip to content

Commit 6d2482a

Browse files
refactor(business): Enhance promo validation and data representation
This commit improves the validation logic within the promo serializers and refines how promotion data is handled and displayed. Key changes: - **Improved Validation:** The validation logic now correctly handles partial updates (`PATCH`) by checking the `mode` from the instance if it's not provided in the request data. It also prevents `max_count` from being set lower than the current `used_count` for a promo. - **Accurate Used Count:** The `get_used_codes_count` method on the `Promo` model now correctly returns the `used_count` for promotions in `COMMON` mode.
1 parent 42ca243 commit 6d2482a

File tree

2 files changed

+81
-64
lines changed

2 files changed

+81
-64
lines changed

promo_code/business/models.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,7 @@ def get_comment_count(self) -> int:
119119
def get_used_codes_count(self) -> int:
120120
if self.mode == business.constants.PROMO_MODE_UNIQUE:
121121
return self.unique_codes.filter(is_used=True).count()
122-
# TODO: COMMON Promo
123-
return 0
122+
return self.used_count
124123

125124
@property
126125
def get_available_unique_codes(self) -> list[str] | None:

promo_code/business/serializers.py

Lines changed: 80 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -285,81 +285,67 @@ class Meta:
285285
)
286286

287287
def validate(self, data):
288-
full_data = self._get_full_data(data)
288+
"""
289+
Main validation method.
290+
Determines the mode and calls the corresponding validation method.
291+
"""
289292

290-
mode = full_data.get('mode')
293+
mode = data.get('mode', getattr(self.instance, 'mode', None))
291294

292295
if mode == business.constants.PROMO_MODE_COMMON:
293-
self._validate_common(full_data)
294-
296+
self._validate_common(data)
295297
elif mode == business.constants.PROMO_MODE_UNIQUE:
296-
self._validate_unique(full_data)
297-
298+
self._validate_unique(data)
299+
elif mode is None:
300+
raise rest_framework.serializers.ValidationError(
301+
{'mode': 'This field is required.'},
302+
)
298303
else:
299304
raise rest_framework.serializers.ValidationError(
300305
{'mode': 'Invalid mode.'},
301306
)
302307

303308
return data
304309

305-
def to_representation(self, instance):
306-
"""
307-
Controls the display of fields in the response.
308-
"""
309-
data = super().to_representation(instance)
310-
311-
if not instance.image_url:
312-
data.pop('image_url', None)
313-
314-
if instance.mode == business.constants.PROMO_MODE_UNIQUE:
315-
data.pop('promo_common', None)
316-
if 'promo_unique' in self.fields and isinstance(
317-
self.fields['promo_unique'],
318-
rest_framework.serializers.SerializerMethodField,
319-
):
320-
data['promo_unique'] = self.get_promo_unique(instance)
321-
else:
322-
data['promo_unique'] = [
323-
code.code for code in instance.unique_codes.all()
324-
]
325-
else:
326-
data.pop('promo_unique', None)
327-
328-
return data
329-
330-
def _get_full_data(self, data):
331-
"""
332-
Build the full data dict by merging existing instance data
333-
with new input.
334-
"""
335-
if self.instance:
336-
full_data = self.to_representation(self.instance)
337-
full_data.update(data)
338-
else:
339-
full_data = data
340-
return full_data
341-
342-
def _validate_common(self, full_data):
310+
def _validate_common(self, data):
343311
"""
344312
Validations for COMMON promo mode.
345313
"""
346-
promo_common = full_data.get('promo_common')
347-
promo_unique = full_data.get('promo_unique')
348-
max_count = full_data.get('max_count')
349314

350-
if not promo_common:
315+
if 'promo_unique' in data and data['promo_unique'] is not None:
351316
raise rest_framework.serializers.ValidationError(
352-
{'promo_common': 'This field is required for COMMON mode.'},
317+
{'promo_unique': 'This field is not allowed for COMMON mode.'},
353318
)
354319

355-
if promo_unique is not None:
320+
if self.instance is None and not data.get('promo_common'):
356321
raise rest_framework.serializers.ValidationError(
357-
{'promo_unique': 'This field is not allowed for COMMON mode.'},
322+
{'promo_common': 'This field is required for COMMON mode.'},
358323
)
359324

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+
360344
min_c = business.constants.PROMO_COMMON_MIN_COUNT
361345
max_c = business.constants.PROMO_COMMON_MAX_COUNT
362-
if not (min_c <= max_count <= max_c):
346+
if effective_max_count is not None and not (
347+
min_c <= effective_max_count <= max_c
348+
):
363349
raise rest_framework.serializers.ValidationError(
364350
{
365351
'max_count': (
@@ -368,35 +354,67 @@ def _validate_common(self, full_data):
368354
},
369355
)
370356

371-
def _validate_unique(self, full_data):
357+
def _validate_unique(self, data):
372358
"""
373359
Validations for UNIQUE promo mode.
374360
"""
375-
promo_common = full_data.get('promo_common')
376-
promo_unique = full_data.get('promo_unique')
377-
max_count = full_data.get('max_count')
378361

379-
if not promo_unique:
362+
if 'promo_common' in data and data['promo_common'] is not None:
380363
raise rest_framework.serializers.ValidationError(
381-
{'promo_unique': 'This field is required for UNIQUE mode.'},
364+
{'promo_common': 'This field is not allowed for UNIQUE mode.'},
382365
)
383366

384-
if promo_common is not None:
367+
if self.instance is None and not data.get('promo_unique'):
385368
raise rest_framework.serializers.ValidationError(
386-
{'promo_common': 'This field is not allowed for UNIQUE mode.'},
369+
{'promo_unique': 'This field is required for UNIQUE mode.'},
387370
)
388371

389-
if max_count != business.constants.PROMO_UNIQUE_MAX_COUNT:
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+
):
390382
raise rest_framework.serializers.ValidationError(
391383
{
392384
'max_count': (
393385
'Must be equal to '
394-
f'{business.constants.PROMO_UNIQUE_MAX_COUNT}'
386+
f'{business.constants.PROMO_UNIQUE_MAX_COUNT} '
395387
'for UNIQUE mode.'
396388
),
397389
},
398390
)
399391

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+
400418

401419
class PromoCreateSerializer(BasePromoSerializer):
402420
url = rest_framework.serializers.HyperlinkedIdentityField(

0 commit comments

Comments
 (0)