Как обновить статус на «истекший» или «активный» в зависимости от даты и времени?

У меня есть приложение, построенное на 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")

На данный момент у меня есть четыре взгляда на скидки:

  1. Dicount List View: Здесь указаны все скидки, относящиеся к конкретному бизнесу (с нумерацией страниц).
  2. Detail Discount View: Здесь показано подробное описание скидки.
  3. Edit Discount View: где пользователь может редактировать скидку после просмотра ее в подробном представлении.
  4. 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 и статусы обновляются. Сейчас это не большая проблема, поскольку мы только начинаем, но как только в таблице появятся миллионы скидок, этот подход вообще не является оптимальным, поскольку все скидки, принадлежащие конкретному бизнесу, обновляются.

Есть ли лучший и более эффективный способ подойти к этому?

Интересно, а что вы думаете, если бы у вас было фоновое задание, которое периодически вызывает хранимую процедуру для обновления статуса?

Gideon Kimutai 26.04.2024 09:51

@GideonKimutai, cronjobs не масштабируемы и не предоставляют информацию в реальном времени. Проверьте ответ ниже, я думаю, что это лучший на данный момент...

Harith 26.04.2024 10:14

Интересно, когда будет обновление? это когда вы просматриваете страницу со списком скидок?

Gideon Kimutai 26.04.2024 11:47

@GideonKimutai: каждый раз, когда вы получаете доступ к Discount.objects.something, поскольку для этого требуется .get_queryset, но, как вы видите, он будет быстро генерировать множество запросов, и это противоречит здравому смыслу, а также не запускается, например, в ListView, поскольку там он клонирует набор запросов, а не звоню get_queryset.

willeM_ Van Onsem 26.04.2024 11:49

На самом деле вы можете получать обновления в реальном времени, но будет дополнительная логика, которая на данный момент не является целью.

Gideon Kimutai 26.04.2024 11:50
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
5
77
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я думаю, что лучше не делать поле чувствительным ко времени. Просто определите статус, когда вам это нужно, например:

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

PTomasz 26.04.2024 09:54

Другие вопросы по теме