Как обновить статус на истекший или активный в зависимости от даты и времени?
У меня есть приложение, созданное на Django. Приложение позволяет предприятиям управлять своей повседневной деятельностью и имеет такие функции, как: управление персоналом, продажи, торговые точки, бухгалтерский учет и т.д.
Для того чтобы предприятия могли прикреплять скидки к своим товарам, я создал Discount
модель:
class Discount(CommonField):
name = models.CharField(max_length=255, blank=True, null=True)
discount = models.DecimalField(max_digits=15, decimal_places=2)
discount_type = models.CharField(max_length=255, choices=DISCOUNT_TYPE_CHOICES, blank=True, null=True)
discounted_products_count = models.PositiveSmallIntegerField(default=0)
start_date = models.DateTimeField(blank=True, null=True)
expiry_date = models.DateTimeField(blank=True, null=True)
status = models.CharField(max_length=255, default="inactive", choices=DISCOUNT_STATUS)
objects = DiscountModelManager()
У скидок есть дата начала и дата окончания, которые были включены в модель, у них также есть поле статуса, которое определяет, является ли статус истекшим, активным или неактивным.
Одна из проблем, с которой я столкнулся, заключалась в том, в какой момент мне следует обновлять статус скидок. Чтобы решить эту проблему, я создал Model Manager, чтобы иметь центральное место, где размещается логика обновления статуса;
class DiscountModelManager(TenantAwareManager):
def get_queryset(self):
queryset = super().get_queryset()
self.change_promo_code_if_end_date_extended(queryset)
self.activate_discount_if_start_date_reached(queryset)
self.expire_discount_if_expiry_date_reached(queryset)
return super().get_queryset()
def change_promo_code_if_end_date_extended(self, queryset):
"""
Activates promo codes if expiry_date has been extended and the status is expired.
"""
queryset.filter(expiry_date__gte=timezone.now(), status="expired").update(status="active")
def activate_discount_if_start_date_reached(self, queryset):
"""
Activates promo codes if start_date has been reached and the status is inactive.
"""
queryset.filter(start_date__lte=timezone.now(), status="inactive").update(status="active")
def expire_discount_if_expiry_date_reached(self, queryset):
queryset.filter(expiry_date__lte=timezone.now()).update(status="expired")
На данный момент у меня есть четыре вида, которые занимаются скидками:
Dicount List View
: Где перечислены все скидки, относящиеся к данному конкретному бизнесу ( с пагинацией).Detail Discount View
: Где отображается детальный вид скидки.Edit Discount View
: Где пользователь может редактировать скидку после просмотра ее в детальном представлении .
Point of Sale View
: Где происходит продажа и скидка может быть использована во время оформления заказа .
Код работает отлично, за исключением того, что у меня есть еще одна проблема...
Если мы посмотрим на представление списка скидок:
class DiscountListView(LoginRequiredMixin, View):
def get(self, request):
business_id = current_business_id()
discounts = Discount.objects.filter(business__business_id=business_id)
paginator = Paginator(discounts, 25)
page_number = request.GET.get("page")
page = paginator.get_page(page_number)
context = {
"table_headers": HTMLTemplateTags().table_headers["discounts"],
"page": page,
"search_query": search_query,
"paginator": paginator,
}
return render(request, "pages/sales/discounts/discount_list.html", context)
Вы видите, что у нас есть queryset
, где мы получаем все скидки, относящиеся к текущему бизнесу
discounts = Discount.objects.filter(business__business_id=business_id)
Перед тем как этот набор запросов будет завершен, вызывается DiscountModelManager
и обновляются статусы. Сейчас это не является большой проблемой, поскольку мы только начинаем, но когда в таблице будут миллионы скидок, такой подход станет неоптимальным, поскольку будут обновляться все скидки, принадлежащие определенному бизнесу.
Есть ли лучший и более эффективный способ решения этой задачи?
Я думаю, что лучше не делать поле чувствительным ко времени. Просто определяйте статус, когда он вам нужен, например:
from django.db.models import Q
from django.db.models.functions import Now
class ActiveDiscountManager(models.Manager):
def get_queryset(self, *args, **kwargs):
return (
super()
.get_queryset(*args, **kwargs)
.filter(~Q(expiry_date__gte=Now()), start_date__gte=Now())
)
class Discount(CommonField):
start_date = models.DateTimeField(blank=True, null=True, db_index=True)
expiry_date = models.DateTimeField(blank=True, null=True, db_index=True)
# no status field
objects = models.Manager()
active = ActiveDiscountManager()
@property
def status(self):
if self.start_date is None or self.start_date < datetime.now():
return 'inactive'
elif self.end_date and self.end_date < datetime.now():
return 'expired'
else:
return 'active'
Таким образом, мы не меняем статус и не храним его, а просто определяем его при необходимости. Это также означает, что если end_date
будет расширен или сокращен, то фильтрация автоматически включит/исключит Discount
снова вместе с элементами Discount.active.all()
.
Поскольку мы добавляем индексы базы данных, это обычно эффективно извлекает Discount
, но это также более надежно, поскольку нам не нужно активно менять статусы.