Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ POSTGRES_PASSWORD=your_postgres_password
POSTGRES_PORT=5432
POSTGRES_USERNAME=your_postgres_username

REDIS_URL=redis://redis:6379/0
REDIS_HOST=redis
REDIS_PORT=6379
39 changes: 18 additions & 21 deletions promo_code/business/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,32 +122,29 @@ def _q_is_targeted(self, country, age):
"""
empty = django.db.models.Q(target={})

if country:
match_country = django.db.models.Q(target__country__iexact=country)
else:
match_country = django.db.models.Q()
match_country = (
django.db.models.Q(target__country__iexact=country)
if country
else django.db.models.Q()
)

no_country = ~django.db.models.Q(
target__has_key='country',
) | django.db.models.Q(target__country__isnull=True)

country_ok = match_country | no_country

no_age_limits = ~django.db.models.Q(
target__has_key='age_from',
) & ~django.db.models.Q(target__has_key='age_until')
if age is None:
age_ok = no_age_limits
else:
from_ok = (
~django.db.models.Q(target__has_key='age_from')
| django.db.models.Q(target__age_from__isnull=True)
| django.db.models.Q(target__age_from__lte=age)
)
until_ok = (
~django.db.models.Q(target__has_key='age_until')
| django.db.models.Q(target__age_until__isnull=True)
| django.db.models.Q(target__age_until__gte=age)
)
age_ok = no_age_limits | (from_ok & until_ok)
from_ok = (
~django.db.models.Q(target__has_key='age_from')
| django.db.models.Q(target__age_from__isnull=True)
| django.db.models.Q(target__age_from__lte=age)
)
until_ok = (
~django.db.models.Q(target__has_key='age_until')
| django.db.models.Q(target__age_until__isnull=True)
| django.db.models.Q(target__age_until__gte=age)
)
age_ok = from_ok & until_ok

return empty | (country_ok & age_ok)

Expand Down
11 changes: 3 additions & 8 deletions promo_code/business/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,7 @@ def is_active(self) -> bool:

if self.mode == business.constants.PROMO_MODE_UNIQUE:
return self.unique_codes.filter(is_used=False).exists()
if self.mode == business.constants.PROMO_MODE_COMMON:
return self.used_count < self.max_count

return True
return self.used_count < self.max_count

@property
def get_like_count(self) -> int:
Expand All @@ -122,10 +119,8 @@ def get_used_codes_count(self) -> int:
return self.used_count

@property
def get_available_unique_codes(self) -> list[str] | None:
if self.mode == business.constants.PROMO_MODE_UNIQUE:
return [c.code for c in self.unique_codes.filter(is_used=False)]
return None
def get_available_unique_codes(self) -> list[str]:
return [c.code for c in self.unique_codes.filter(is_used=False)]


class PromoCode(django.db.models.Model):
Expand Down
29 changes: 6 additions & 23 deletions promo_code/business/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,6 @@ def validate(self, attrs):
email = attrs.get('email')
password = attrs.get('password')

if not email or not password:
raise rest_framework.serializers.ValidationError(
'Both email and password are required.',
)

try:
company = business.models.Company.objects.get(email=email)
except business.models.Company.DoesNotExist:
Expand Down Expand Up @@ -151,24 +146,12 @@ def __init__(self, **kwargs):
super().__init__(**kwargs)

def to_internal_value(self, data):
if not data or not isinstance(data, list):
raise rest_framework.serializers.ValidationError(
'At least one country must be specified.',
)

# (&country=us,fr)
if len(data) == 1 and ',' in data[0]:
countries_str = data[0]
if '' in [s.strip() for s in countries_str.split(',')]:
raise rest_framework.serializers.ValidationError(
'Invalid country format.',
)
data = [country.strip() for country in countries_str.split(',')]

if any(not item for item in data):
raise rest_framework.serializers.ValidationError(
'Empty value for country is not allowed.',
)
if (
isinstance(data, list)
and len(data) == 1
and isinstance(data[0], str)
):
data = [item.strip() for item in data[0].split(',')]

return super().to_internal_value(data)

Expand Down
33 changes: 6 additions & 27 deletions promo_code/core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,8 @@ def validate(self, data):

if mode == business.constants.PROMO_MODE_COMMON:
self._validate_common(data)
elif mode == business.constants.PROMO_MODE_UNIQUE:
self._validate_unique(data)
elif mode is None:
raise rest_framework.serializers.ValidationError(
{'mode': 'This field is required.'},
)
else:
raise rest_framework.serializers.ValidationError(
{'mode': 'Invalid mode.'},
)
self._validate_unique(data)

return data

Expand Down Expand Up @@ -374,31 +366,18 @@ def get_is_liked_by_user(self, obj: business.models.Promo) -> bool:
"""
Checks whether the current user has liked this promo.
"""
request = self.context.get('request')
if (
request
and hasattr(request, 'user')
and request.user.is_authenticated
):
return user.models.PromoLike.objects.filter(
promo=obj,
user=request.user,
).exists()
return False
request = self.context['request']
return user.models.PromoLike.objects.filter(
promo=obj,
user=request.user,
).exists()

def get_is_activated_by_user(self, obj: business.models.Promo) -> bool:
"""
Checks whether the current user has activated this promo code.
"""
request = self.context.get('request')

if not (
request
and hasattr(request, 'user')
and request.user.is_authenticated
):
return False

return user.models.PromoActivationHistory.objects.filter(
promo=obj,
user=request.user,
Expand Down
14 changes: 8 additions & 6 deletions promo_code/promo_code/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,13 @@ def load_bool(name, default):
'PORT': os.getenv('POSTGRES_PORT', '5432'),
},
}
REDIS_HOST = os.getenv('REDIS_HOST', 'redis')
REDIS_PORT = os.getenv('REDIS_PORT', '6379')

CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': os.getenv('REDIS_URL', 'redis://127.0.0.1:6379/0'),
'LOCATION': f'redis://{REDIS_HOST}:{REDIS_PORT}/0',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
},
Expand Down Expand Up @@ -178,20 +180,20 @@ def load_bool(name, default):
'.NumericPasswordValidator',
},
{
'NAME': 'promo_code.validators.AsciiValidator',
'NAME': 'user.validators.AsciiValidator',
},
{
'NAME': 'promo_code.validators.SpecialCharacterValidator',
'NAME': 'user.validators.SpecialCharacterValidator',
'OPTIONS': {'special_chars': '[@$!%*?&]'},
},
{
'NAME': 'promo_code.validators.NumericValidator',
'NAME': 'user.validators.NumericValidator',
},
{
'NAME': 'promo_code.validators.LowercaseValidator',
'NAME': 'user.validators.LowercaseValidator',
},
{
'NAME': 'promo_code.validators.UppercaseValidator',
'NAME': 'user.validators.UppercaseValidator',
},
]

Expand Down
6 changes: 0 additions & 6 deletions promo_code/user/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,6 @@ class User(
def __str__(self):
return self.email

def save(self, *args, **kwargs):
if not self.pk:
self.last_login = django.utils.timezone.now()

super().save(*args, **kwargs)


class PromoLike(django.db.models.Model):
id = django.db.models.UUIDField(
Expand Down
11 changes: 3 additions & 8 deletions promo_code/user/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,6 @@ def authenticate_user(self, attrs):
email = attrs.get('email')
password = attrs.get('password')

if not email or not password:
raise rest_framework.exceptions.ValidationError(
{'detail': 'Both email and password are required'},
code='required',
)

user = django.contrib.auth.authenticate(
request=self.context.get('request'),
email=email,
Expand Down Expand Up @@ -153,8 +147,9 @@ def _invalidate_cache(self, instance):

user_type = instance.__class__.__name__.lower()
token_version = getattr(instance, 'token_version', None)
cache_key = f'auth_instance_{user_type}_{instance.id}_v{token_version}'
django.core.cache.cache.delete(cache_key)
django.core.cache.cache.delete(
f'auth_instance_{user_type}_{instance.id}_v{token_version}',
)


class UserFeedQuerySerializer(
Expand Down
Loading