I am new to Django and trying to create a page that shows a list of products with couple of filters. The page works fine using standard method and shows filter and update the page as selected. What I want to do is to add a count of products available based on the seelcted filters in; in this case just for category.
models.py
class Category(index.Indexed, models.Model):
name = models.CharField(max_length=1024)
class Meta:
verbose_name = "Category"
verbose_name_plural = "Categories"
class Product(models.Model):
title = models.CharField(max_length=1024, verbose_name="Product title")
last_modified = models.DateTimeField(null=True, blank=True)
category = models.ForeignKey("Category", on_delete=models.SET_NULL, null=True, blank=True)
filters.py
class ProductFilter(django_filters.FilterSet):
category = django_filters.ModelChoiceFilter(
queryset=Category.objects.annotate(num_product=Count("product"))
)
last_modified = django_filters.DateRangeFilter(label="Date_Range")
class Meta:
model = Product
fields = ["category", "last_modified"]
views.py
def product_explorer_view(request):
products = Product.objects.all()
product_filter = ProductFilter(request.GET, queryset=products)
products = product_filter.qs
context = {
"products": products,
"product_filter": product_filter,
}
return render(request, "/products_index.html", context)
products_index.html
<form class="mb-1">
<div class="row g-2">
<div class="col-md-2 col-sm-4">
Category:
<div class="form-floating">
<!-- <label for="searchTerm" class="form-label">Category</label> -->
{{ product_filter.form.category }}
</div>
</div>
<div class="col-md-2 col-sm-4">
Date:
<div class="form-floating">
<!-- <label for="searchTerm" class="form-label">Category</label> -->
{{ chart_filter.form.last_modified }}
</div>
</div>
<div class="col-md-1 col-sm-4 pt-1">
</br>
<button class="btn btn-secondary btn-lg" type="submit">Search</button>
</div>
</div>
Number of available products: {{ products|length }}
</form>
Right now this Category
option field in the form is filled as below.
Output on page
<th><label for="id_category">Category:</label></th>
<td><select name="category" id="id_category">
<option value="">---------</option>
<option value="11" selected>Category1</option>
<option value="12">Category2</option>
<option value="13">Category3</option>
<option value="14">Category4</option>
<option value="16">Category5</option>
<option value="17">Category6</option>
</select></td>
What I want is to get
<th><label for="id_category">Category:</label></th>
<td>
<select name="category" id="id_category">
<option value="">---------</option>
<option value="11" selected>Category1 (3)</option>
<option value="12">Category2 (5)</option>
<option value="13">Category3 (7)</option>
<option value="14">Category4 (18)</option>
<option value="16">Category5 (20)</option>
<option value="17">Category6 (0)</option>
</select></td>
The added annotation in the filters.py
add that extra variable in the queryset, but it does not help include that in the <option> labels
.
Is there some configuration/parameter to change that labels? Or in which other way can I make that other than manually building the select options and then linking it back to product filters?
I assume there must be some Django way of doing it, as it is/should be quite a standard requirement.
Attempt #2 :)
By overriding the form @property
you can override the labels of the select
class ProductFilter(django_filters.FilterSet):
category = django_filters.ModelChoiceFilter(
queryset=Category.objects.annotate(num_product=Count("product"))
)
last_modified = django_filters.DateRangeFilter(label="Date_Range")
class Meta:
model = Product
fields = ["category", "last_modified"]
@property
def form(self):
if not hasattr(self, "_form"):
Form = self.get_form_class()
if self.is_bound:
self._form = Form(self.data, prefix=self.form_prefix)
else:
self._form = Form(prefix=self.form_prefix)
# This line
self._form.fields['category'].label_from_instance = lambda obj: f'{obj.name} ({obj.num_product})'
# <- we DO NOT place it here, else it runs 3 times,
# we want only during the init!
return self._form
FilterSet
Object, CTRL-SHIFT-F, it's inside django_filters/filterset.py
def form(self)
ProductFilter
, Overriding default.print()
-ing stuff out
vars()
is handyStructure (trial and error):
print(self._form.fields)
# {
# 'category': <django_filters.fields.ModelChoiceField object at 0x7fd960c26890>,
# 'last_modified': <django_filters.fields.ChoiceField object at 0x7fd960c269b0>
# }
django override modelchoicefield display