У меня есть приложение, построенное на 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()
Скидки имеют дату начала и дату истечения срока действия, которые включены в модель, а также поле статуса, которое определяет, истек ли статус, активен или неактивен.
Одна из проблем, с которой я столкнулся, заключалась в том, в какой момент мне следует обновить статус скидок. Чтобы решить эту проблему, я создал менеджер моделей, который имеет центральное место, где размещается логика обновления статуса;
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
и статусы обновляются. Сейчас это не большая проблема, поскольку мы только начинаем, но как только в таблице появятся миллионы скидок, этот подход вообще не является оптимальным, поскольку все скидки, принадлежащие конкретному бизнесу, обновляются.
Есть ли лучший и более эффективный способ подойти к этому?
@GideonKimutai, cronjobs не масштабируемы и не предоставляют информацию в реальном времени. Проверьте ответ ниже, я думаю, что это лучший на данный момент...
Интересно, когда будет обновление? это когда вы просматриваете страницу со списком скидок?
@GideonKimutai: каждый раз, когда вы получаете доступ к Discount.objects.something
, поскольку для этого требуется .get_queryset
, но, как вы видите, он будет быстро генерировать множество запросов, и это противоречит здравому смыслу, а также не запускается, например, в ListView
, поскольку там он клонирует набор запросов, а не звоню get_queryset
.
На самом деле вы можете получать обновления в реальном времени, но будет дополнительная логика, которая на данный момент не является целью.
Я думаю, что лучше не делать поле чувствительным ко времени. Просто определите статус, когда вам это нужно, например:
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
, но она также более надежна, поскольку нам не нужно активно менять статусы.
не могу его отредактировать, потому что он меньше 6 символов, но вы забыли '' вокруг active в операторе else return
Интересно, а что вы думаете, если бы у вас было фоновое задание, которое периодически вызывает хранимую процедуру для обновления статуса?