Как правильно запросить отношения "многие ко многим" в Django?

Возможно, я не совсем правильно сформулировал этот вопрос, поэтому прошу прощения. Также, вероятно, поэтому я не могу найти много информации, когда я google это. В любом случае, у меня есть проект Django, который я использую для создания API с помощью Django Rest Framework. Для этой проблемы у меня есть 2 модели, которые имеют отношение к делу. Модель продукта и модель предложения (немного псевдокода, поэтому не обращайте внимания на отсутствующие поля).

class Offer(models.Model):
    image = models.ImageField(upload_to=upload_to, validators=(validate_image_file_extension,))
    discount = models.IntegerField(validators=(MinValueValidator(0), MaxValueValidator(100)))
    start_date = models.DateField()
    expiration_date = models.DateField()
    products = models.ManyToManyField(Product, related_name='offers')
    customers = models.ManyToManyField(Customer)

Как вы можете видеть в этой модели. Существует связь многие-ко-многим с моделью продукта. Внутри модели продукта есть только некоторые стандартные вещи:

class Product(models.Model):
    name = models.CharField(max_length=256)
    price = models.FloatField()
    stock = models.IntegerField()
    product_photo = models.ImageField(null=True, upload_to=product_photo_path,
                                      validators=[validate_image_file_extension])

Итак, теперь собственно вопрос. Я пытаюсь добавить новую функцию, которая будет получать цену со скидкой на товар. Если есть несколько предложений, она должна возвращать самую высокую цену со скидкой. Также нужно учитывать клиента, который отправляет запрос, потому что предложения актуальны не для каждого клиента. Это должно быть сделано в конечной точке списка из API, поэтому у него довольно много записей. Мой вопрос заключается в том, какой самый простой и эффективный способ сделать это?

Некоторые из решений, которые я пробовал, заключались в добавлении функции к модели продукта следующим образом:

def discounted_price(self, customer):
        highest_discounted_offer = self.offers.filter(start_date__lte=datetime.now().date(),
                                                      expiration_date__gte=datetime.now().date(),
                                                      customers__in=[customer]).order_by(
            'discount').first()
  
        return self.price - (self.price * highest_discounted_offer.discount / 100)

Это решение работает, но проблема в том, что когда я использую его на сериализаторе, он запускается для каждого отдельного продукта, и у меня есть N+1 запрос. Также я использую prefetch_related, но поскольку мне нужно получить наибольшее значение, я не думаю, что это действительно поможет.

Затем я попытался сделать это с помощью пользовательского менеджера, но мне не понравилось то, насколько сложным он был, а также то, что я не совсем понимал код:

class ProductManager(models.Manager):
    def with_discounted_prices(self, customer):
        Offer = apps.get_model('offers', 'Offer') # avoid circular import.
         highest_discount = Offer.objects.filter(
                products=OuterRef('pk'),
                start_date__lte=timezone.now().date(),
                expiration_date__gte=timezone.now().date(),          
                customers__in=[customer]
            ).order_by('-discount')

            products = self.annotate(highest_discount=Subquery(highest_discount.values('discount')[:1]))

            products = products.annotate(
                discounted_price=ExpressionWrapper(F('price') - (F('price') * F('highest_discount') / 100),
                                                   output_field=FloatField()
                                                   ))

            return products

Это работает и гораздо эффективнее, но я не знаю, лучший ли это способ? Это также кажется не очень масштабируемым, если в будущем понадобится больше таких полей. Есть ли более простой способ, о котором я не подумал?

Вернуться на верх