Files
ITQ 9865480060 fix: changed active status logic
changed timezone for active status to utc+3
2025-01-26 15:52:14 +03:00

238 lines
6.8 KiB
Python

from datetime import datetime
import pytz
from django.core.exceptions import ValidationError
from django.core.validators import (
MaxValueValidator,
MinLengthValidator,
MinValueValidator,
)
from django.db import models
from django_countries.fields import CountryField
from apps.business.models import Business
from apps.core.models import BaseModel
from apps.promo.validators import (
PromocodeDurationValidator,
PromocodeUniqueValidator,
TargetAgeValidator,
TargetCategoriesValidator,
)
from apps.user.models import User
class PromocodeTarget(BaseModel):
age_from = models.PositiveSmallIntegerField(
blank=True,
null=True,
validators=[MinValueValidator(0), MaxValueValidator(100)],
)
age_until = models.PositiveSmallIntegerField(
blank=True,
null=True,
validators=[MinValueValidator(0), MaxValueValidator(100)],
)
country = CountryField(blank=True, null=True)
country_raw = models.CharField(max_length=2, blank=True, null=True)
categories = models.JSONField(
blank=True,
null=True,
default=list,
validators=[TargetCategoriesValidator()],
)
def __str__(self) -> str:
return str(self.id)
def clean(self) -> None:
super().clean()
TargetAgeValidator()(self)
class Promocode(BaseModel):
class ModeChoices(models.TextChoices):
COMMON = "COMMON"
UNIQUE = "UNIQUE"
business = models.ForeignKey(
Business,
on_delete=models.CASCADE,
related_name="promocodes",
)
description = models.TextField(
max_length=300,
validators=[MinLengthValidator(10)],
)
image_url = models.URLField(
max_length=350,
blank=True,
null=True,
)
target = models.ForeignKey(
PromocodeTarget,
on_delete=models.CASCADE,
related_name="promocodes",
)
max_count = models.PositiveIntegerField(
validators=[MaxValueValidator(100000000)],
)
active_from = models.DateField(blank=True, null=True)
active_until = models.DateField(blank=True, null=True)
mode = models.CharField(max_length=6, choices=ModeChoices)
promo_common = models.CharField(
max_length=30,
blank=True,
null=True,
validators=[MinLengthValidator(5)],
)
promo_unique = models.JSONField(
blank=True,
null=True,
default=list,
validators=[PromocodeUniqueValidator()],
)
promo_unique_activated = models.JSONField(
blank=True,
null=True,
default=list,
)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
return str(self.id)
def clean(self) -> None:
super().clean()
if self.image_url == "":
err = {
"image_url": "Field cannot be blank.",
}
raise ValidationError(err)
if self.mode == self.ModeChoices.COMMON:
if not self.promo_common:
err = {
"promo_common": "Field is required for COMMON mode.",
}
raise ValidationError(err)
if self.promo_unique:
err = {
"promo_unique": "Field must be empty for COMMON mode.",
}
raise ValidationError(err)
if self.max_count < self.activations.count():
err = {
"max_count": "Activations count is bigger than max_count",
}
raise ValidationError(err)
elif self.mode == self.ModeChoices.UNIQUE:
if not self.promo_unique:
err = {
"promo_unique": "Field is required for UNIQUE mode.",
}
raise ValidationError(err)
if self.promo_common:
err = {
"promo_common": "Field must be empty for UNIQUE mode.",
}
raise ValidationError(err)
if self.max_count != 1:
err = {
"max_count": "Field must be 1 for UNIQUE mode.",
}
raise ValidationError(err)
PromocodeDurationValidator()(self)
def activate_promocode(self, user: User) -> str:
promocode: str | None = None
if self.mode == self.ModeChoices.COMMON:
promocode = self.promo_common
elif self.mode == self.ModeChoices.UNIQUE:
unused_promocodes = self.promo_unique[
len(self.promo_unique_activated) : :
]
promocode = unused_promocodes[0]
self.promo_unique_activated.append(promocode)
self.save()
PromocodeActivation.objects.create(promocode=self, user=user)
return promocode
@property
def active(self) -> bool:
timezone_utc3 = pytz.timezone("Europe/Moscow")
current_date = datetime.now(timezone_utc3).date()
is_active_by_date = (
self.active_from is None or self.active_from <= current_date
) and (self.active_until is None or self.active_until >= current_date)
if self.mode == self.ModeChoices.COMMON:
is_active_by_mode = self.activations.count() < self.max_count
elif self.mode == self.ModeChoices.UNIQUE:
is_active_by_mode = len(self.promo_unique) > len(
self.promo_unique_activated
if self.promo_unique_activated
else []
)
return is_active_by_date and is_active_by_mode
class PromocodeActivation(BaseModel):
promocode = models.ForeignKey(
Promocode,
on_delete=models.CASCADE,
related_name="activations",
)
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="activations",
)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
return f"{self.promocode.id} | {self.user.id}"
class PromocodeComment(BaseModel):
promocode = models.ForeignKey(
Promocode,
on_delete=models.CASCADE,
related_name="comments",
)
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="comments",
)
text = models.TextField(
max_length=1000,
validators=[MinLengthValidator(10)],
)
date = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
return f"{self.promocode.id} | {self.author.id}"
class PromocodeLike(BaseModel):
promocode = models.ForeignKey(
Promocode, on_delete=models.CASCADE, related_name="likes"
)
user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="liked_promocodes"
)
def __str__(self) -> str:
return f"{self.promocode.id} | {self.user.id}"
class Meta:
unique_together = ("promocode", "user")