Фильтрация через отношения в представлениях или шаблоне
У меня есть одна простая модель под названием 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 %}