Фильтрация через отношения в представлениях или шаблоне

У меня есть одна простая модель под названием Product

class Product(models.Model):
    title = models.CharField(max_length=150)

    def __str__(self):
        return self.title

И еще одна модель под названием ExternalProduct

class ExternalProduct(models.Model):
    title = models.CharField(max_length=100)
    external_id = models.CharField(max_length=25)
    internal_product = models.ForeignKey(
        Product,
        on_delete=models.CASCADE,
        blank=True,
        related_name='external_products',
    )
    price = models.IntegerField()
    brand = models.ForeignKey(Brand, on_delete=models.CASCADE, blank=True)
    image_url = models.CharField(max_length=255)
    product_url = models.CharField(max_length=255)
    store = models.CharField(max_length=50)
    active = models.BooleanField(default=True)

    class Meta:
        ordering = ['price']

    def __str__(self):
        return self.title

В подробном представлении продукта я хочу отобразить цену всех внешних продуктов, связанных с продуктом. С этим представлением все работает нормально

# products/views.py
class ProductDetailView(DetailView):
    model = Product
    context_object_name = 'product'
    template_name = 'products/product_detail.html'

и этот шаблон

 # product_detail.html
 {% extends '_base.html' %}
    {% block title %}{{ product.title }}{% endblock title %}
    {% block content %}
    <div class="book-detail">
    <h2>{{ product.get_active_external_products }}</h2>
    </div>
        <div>
        <ul>
            {% for ep in product.external_products.all %}
                <li>{{ ep.price }}</li>
            {% endfor %}
        </ul>
        </div>
    {% endblock content %}

Проблема в том, что я просто хочу отобразить цену ExternalProduct, который имеет active=True

Пытаюсь решить эту проблему с помощью пользовательского метода в products views.py

class ProductDetailView(DetailView):
    model = Product
    context_object_name = 'product'
    template_name = 'products/product_detail.html'

    def get_active_external_products(self):
        return self.external_products.objects.filter(active=True)

И модифицированный цикл for внутри product_detail.html

{% for ep in product.get_active_external_products %}
    <li>{{ ep.price }}</li>
{% endfor %}

Но, к сожалению, безуспешно. Сайт не падает, просто не показывает ничего, кроме остальной части html-файла.

Есть совет?

Вы можете использовать prefetch_related вместе с Prefetch для фильтрации только активных внешних продуктов. Для этого вам необходимо переопределить метод get_queryset() в вашем представлении:

from django.db.models import Prefetch

class ProductDetailView(DetailView):
    model = Product
    context_object_name = 'product'
    template_name = 'products/product_detail.html'

    def get_queryset(self):
        return Product.objects.prefetch_related(Prefetch("external_products", queryset=ExternalProduct.objects.filter(active=True), to_attr="active_external_products"))

Обратите внимание на часть to_attr="active_external_products". В ней говорится, что активные внешние продукты будут доступны в виде атрибута active_external_products. Поэтому в шаблоне вы можете получить их следующим образом:

{% for ep in product.active_external_products %}
    <li>{{ ep.price }}</li>
{% endfor %}

Или вы можете просто переопределить get_context_data() для вставки внешних продуктов в контекст напрямую:

class ProductDetailView(DetailView):
    model = Product
    context_object_name = 'product'
    template_name = 'products/product_detail.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(****kwargs)
        context["active_external_products"] = self.object.external_products.filter(active=True)
        return context

В этом случае в шаблоне можно использовать active_external_products имя переменной напрямую:

{% for ep in active_external_products %}
    <li>{{ ep.price }}</li>
{% endfor %}
Вернуться на верх