My current project has me building a page where the output can be filtered by category. Meaning by default you get a full list of items but you can narrow the output by clicking on ( multiple or a single) check boxes. The idea would be if you only want to see the results from two categories you can choose those two and just see items from those two categories. (think like an Amazon page where you can can search for an item but filter the results)
This works through a form using the get method. So the url will look something like:
ip/?family=category_one&family=category_two
My code is as below:
Please note the Firmware model is related to ProductModel through a ManyToManyField and ProductModel is related to Family through a ForeignKey relation
*models
class Family(models.Model):
family_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
slug = models.SlugField(null=False, unique=True)
family_name = models.CharField(max_length=50, editable=True, unique=True)
class ProductModel(models.Model):
model_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
model_number = models.CharField(max_length=25, editable=True, unique=True)
family_id = models.ForeignKey(Family, on_delete=models.DO_NOTHING, editable=True, null=False, blank=False)
class Firmware(models.Model):
firmware_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
version = models.CharField(max_length=50, help_text='The \'version number\' of the firmware',
editable=True)
model_id = models.ManyToManyField(ProductModel, editable=True)
*View:
class FirmwareListView(ListView):
model = Firmware
context_object_name = 'firmwares'
qs = QuerySet()
def get(self, request, *args, **kwargs):
requested_families = request.GET.getlist("family")
if requested_families:
query = Q(model_id__family_id__family_name__iexact=requested_families[0])
for f in range(1, len(requested_families)):
query = query | Q(model_id__family_id__family_name__iexact=requested_families[f])
self.qs = Firmware.objects.filter(query)
else:
self.qs = Firmware.objects.all()
return super().get(request, *args, **kwargs)
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data()
context['firmware_list'] = self.qs
return context
The most important parts here are:
requested_families = request.GET.getlist("family")
if requested_families:
query = Q(model_id__family_id__family_name__iexact=requested_families[0])
for f in range(1, len(requested_families)):
query = query | Q(model_id__family_id__family_name__iexact=requested_families[f])
self.qs = Firmware.objects.filter(query)
This query will work (in that it runs and returns results but the results are wrong. The results seem to be as if they ignore the exact requirement and only run a contains query. In other words, if I filter by the category foo I will get results for food and foo. If I run the query by doing the below:
self.qs = Firmware.objects.filter(Q(model_id__family_id__family_name__iexact=requested_families[0]) | Q(model_id__family_id__family_name__iexact=requested_families[1]))
and I choose two categories the result is perfect. Of course I cannot use the above method in production since I will never know how many categories a user will choose.
I have seen this post ( Django query with variable number of filter arguments ) and tried all of the suggestions there with no luck.
I believe the issue is I am trying to filter on a field from a model that runs through a many to many field then a foreign key. There is no way to get around this (that I can think of at least)
Any help offered is greatly appreciated. I am a bit stuck on this one but there must be a way to do it. For now the only method I can think of is to perform a separate query on each term and then combine the results at the end.