Skip to content

Commit 2207797

Browse files
refactor(business): Extract constants, restructure promo logic, optimize queries
- Define constants for the Company model to improve maintainability. - Move promo code-related logic from serializers to the Promo model to encapsulate behavior. - Optimize querysets in views to reduce database load and improve performance.
1 parent 3118d3c commit 2207797

File tree

7 files changed

+106
-61
lines changed

7 files changed

+106
-61
lines changed

promo_code/business/constants.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# === Company ===
2+
COMPANY_NAME_MIN_LENGTH = 5
3+
COMPANY_NAME_MAX_LENGTH = 50
4+
COMPANY_EMAIL_MIN_LENGTH = 8
5+
COMPANY_EMAIL_MAX_LENGTH = 120
6+
COMPANY_PASSWORD_MIN_LENGTH = 8
7+
COMPANY_PASSWORD_MAX_LENGTH = 60
8+
19
# === Promo ===
210
PROMO_MODE_COMMON = 'COMMON'
311
PROMO_MODE_UNIQUE = 'UNIQUE'

promo_code/business/models.py

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

33
import django.contrib.auth.models
44
import django.db.models
5+
import django.utils.timezone
56

67
import business.constants
78
import business.managers
@@ -16,9 +17,11 @@ class Company(django.contrib.auth.models.AbstractBaseUser):
1617
)
1718
email = django.db.models.EmailField(
1819
unique=True,
19-
max_length=120,
20+
max_length=business.constants.COMPANY_EMAIL_MAX_LENGTH,
21+
)
22+
name = django.db.models.CharField(
23+
max_length=business.constants.COMPANY_NAME_MAX_LENGTH,
2024
)
21-
name = django.db.models.CharField(max_length=50)
2225

2326
token_version = django.db.models.IntegerField(default=0)
2427
created_at = django.db.models.DateTimeField(auto_now_add=True)
@@ -77,6 +80,32 @@ class Promo(django.db.models.Model):
7780
def __str__(self):
7881
return f'Promo {self.id} ({self.mode})'
7982

83+
@property
84+
def is_active(self) -> bool:
85+
today = django.utils.timezone.timezone.now().date()
86+
if self.active_from and self.active_from > today:
87+
return False
88+
if self.active_until and self.active_until < today:
89+
return False
90+
91+
if self.mode == business.constants.PROMO_MODE_UNIQUE:
92+
return self.unique_codes.filter(is_used=False).exists()
93+
# TODO: COMMON Promo
94+
return True
95+
96+
@property
97+
def get_used_codes_count(self) -> int:
98+
if self.mode == business.constants.PROMO_MODE_UNIQUE:
99+
return self.unique_codes.filter(is_used=True).count()
100+
# TODO: COMMON Promo
101+
return 0
102+
103+
@property
104+
def get_available_unique_codes(self) -> list[str] | None:
105+
if self.mode == business.constants.PROMO_MODE_UNIQUE:
106+
return [c.code for c in self.unique_codes.filter(is_used=False)]
107+
return None
108+
80109

81110
class PromoCode(django.db.models.Model):
82111
promo = django.db.models.ForeignKey(

promo_code/business/serializers.py

Lines changed: 21 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,12 @@
33
import django.contrib.auth.password_validation
44
import django.core.exceptions
55
import django.core.validators
6-
import django.utils.timezone
76
import pycountry
87
import rest_framework.exceptions
98
import rest_framework.serializers
10-
import rest_framework.status
119
import rest_framework_simplejwt.exceptions
1210
import rest_framework_simplejwt.serializers
1311
import rest_framework_simplejwt.tokens
14-
import rest_framework_simplejwt.views
1512

1613
import business.constants
1714
import business.models as business_models
@@ -24,19 +21,19 @@ class CompanySignUpSerializer(rest_framework.serializers.ModelSerializer):
2421
write_only=True,
2522
required=True,
2623
validators=[django.contrib.auth.password_validation.validate_password],
27-
min_length=8,
28-
max_length=60,
24+
min_length=business.constants.COMPANY_PASSWORD_MIN_LENGTH,
25+
max_length=business.constants.COMPANY_PASSWORD_MAX_LENGTH,
2926
style={'input_type': 'password'},
3027
)
3128
name = rest_framework.serializers.CharField(
3229
required=True,
33-
min_length=5,
34-
max_length=50,
30+
min_length=business.constants.COMPANY_NAME_MIN_LENGTH,
31+
max_length=business.constants.COMPANY_NAME_MAX_LENGTH,
3532
)
3633
email = rest_framework.serializers.EmailField(
3734
required=True,
38-
min_length=8,
39-
max_length=120,
35+
min_length=business.constants.COMPANY_EMAIL_MIN_LENGTH,
36+
max_length=business.constants.COMPANY_EMAIL_MAX_LENGTH,
4037
validators=[
4138
business.validators.UniqueEmailValidator(
4239
'This email address is already registered.',
@@ -293,10 +290,17 @@ class PromoReadOnlySerializer(rest_framework.serializers.ModelSerializer):
293290
read_only=True,
294291
)
295292
target = TargetSerializer()
293+
296294
promo_unique = rest_framework.serializers.SerializerMethodField()
297295
like_count = rest_framework.serializers.SerializerMethodField()
298-
used_count = rest_framework.serializers.SerializerMethodField()
299-
active = rest_framework.serializers.SerializerMethodField()
296+
used_count = rest_framework.serializers.IntegerField(
297+
source='get_used_codes_count',
298+
read_only=True,
299+
)
300+
active = rest_framework.serializers.BooleanField(
301+
source='is_active',
302+
read_only=True,
303+
)
300304

301305
class Meta:
302306
model = business_models.Promo
@@ -319,42 +323,12 @@ class Meta:
319323
)
320324

321325
def get_promo_unique(self, obj):
322-
if obj.mode == business.constants.PROMO_MODE_UNIQUE:
323-
return [code.code for code in obj.unique_codes.all()]
324-
325-
return None
326+
return obj.get_available_unique_codes
326327

327328
def get_like_count(self, obj):
328329
# TODO
329330
return 0
330331

331-
def get_used_count(self, obj):
332-
if obj.mode == business.constants.PROMO_MODE_UNIQUE:
333-
return obj.unique_codes.filter(is_used=True).count()
334-
335-
# TODO
336-
return 0
337-
338-
def get_active(self, obj):
339-
now = django.utils.timezone.now().date()
340-
active_from = obj.active_from
341-
active_until = obj.active_until
342-
343-
date_active = True
344-
345-
if active_from and active_from > now:
346-
date_active = False
347-
348-
if active_until and active_until < now:
349-
date_active = False
350-
351-
else:
352-
max_count_condition = obj.unique_codes.filter(
353-
is_used=False,
354-
).exists()
355-
356-
return date_active and max_count_condition
357-
358332
def to_representation(self, instance):
359333
data = super().to_representation(instance)
360334
if instance.mode == business.constants.PROMO_MODE_COMMON:
@@ -389,7 +363,10 @@ class PromoDetailSerializer(rest_framework.serializers.ModelSerializer):
389363
read_only=True,
390364
)
391365
like_count = rest_framework.serializers.SerializerMethodField()
392-
used_count = rest_framework.serializers.SerializerMethodField()
366+
used_count = rest_framework.serializers.IntegerField(
367+
source='get_used_codes_count',
368+
read_only=True,
369+
)
393370

394371
class Meta:
395372
model = business_models.Promo
@@ -410,10 +387,7 @@ class Meta:
410387
)
411388

412389
def get_promo_unique(self, obj):
413-
if obj.mode == business.constants.PROMO_MODE_UNIQUE:
414-
return [code.code for code in obj.unique_codes.all()]
415-
416-
return None
390+
return obj.get_available_unique_codes
417391

418392
def update(self, instance, validated_data):
419393
target_data = validated_data.pop('target', None)
@@ -437,10 +411,3 @@ def validate(self, data):
437411
def get_like_count(self, obj):
438412
# TODO
439413
return 0
440-
441-
def get_used_count(self, obj):
442-
if obj.mode == business.constants.PROMO_MODE_UNIQUE:
443-
return obj.unique_codes.filter(is_used=True).count()
444-
445-
# TODO
446-
return 0

promo_code/business/views.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,27 @@ class CompanyPromoListView(rest_framework.generics.ListAPIView):
150150
pagination_class = business.pagination.CustomLimitOffsetPagination
151151

152152
def get_queryset(self):
153-
queryset = business.models.Promo.objects.filter(
154-
company=self.request.user,
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+
)
155174
)
156175

157176
countries = [
@@ -287,6 +306,31 @@ class CompanyPromoDetailView(rest_framework.views.APIView):
287306

288307
lookup_field = 'id'
289308

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+
290334
def get(self, request, id):
291335
try:
292336
promo = business.models.Promo.objects.get(

promo_code/user/tests/auth/test_tokens.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@ def test_refresh_token_invalidation_after_new_login(self):
141141
)
142142

143143
def test_blacklist_storage(self):
144-
145144
self.client.post(self.signin_url, self.user_data, format='json')
146145

147146
self.client.post(self.signin_url, self.user_data, format='json')

promo_code/user/tests/auth/test_validation.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,6 @@ def test_empty_name_field(self):
257257
)
258258

259259
def test_empty_surname_field(self):
260-
261260
data = {
262261
'name': 'Emma',
263262
'surname': '',

promo_code/user/views.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ class SignInView(
4444
serializer_class = user.serializers.SignInSerializer
4545

4646
def post(self, request, *args, **kwargs):
47-
4847
try:
4948
serializer = self.get_serializer(data=request.data)
5049
serializer.is_valid(raise_exception=True)

0 commit comments

Comments
 (0)