Writing on-request filtering for related objects

Suppose that we have following models

   class Category(models.Model):
        name = models.CharField(max_length=254)
    
    class Item(models.Model):
        category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="items")
        name = models.CharField(max_length=254)
        state = models.ForeignKey(State, on_delete=models.CASCADE)

Categories and their items are listed like so

    def view(request):
       categories = Category.objects.all()
       pass

    {% for category in categories %}
    	{{ category.name }}
        {% for item in category.items.all %}
            {{ item.name }}
        {% endfor %}
    {% endfor %}

In this structure, I want to write on-request filtering for listed ‘items’.

    def view(request):
        ...
        queryset = ???
        state = request.GET.get('state')
        if state:
        	queryset = queryset.filter(state__name=state)

The problem is defining ‘queryset’. Because, Items are listed as related objects of category.

Can it be done properly? or Do I need to change design?

You can take a look at my low fidelity design to be more clear.

You could use a Prefetch object. Give that a look and let me know if you have questions.

1 Like

@CodenameTim, Thank you for the reply.

Firstly, I’m trying to get queryset using Prefetch.

In the document, the sample is,

Question.objects.prefetch_related(Prefetch('choice_set')).get().choice_set.all()

So when I use this for me,

items = Category.objects.prefetch_related(Prefetch('items')).get().items.all()

error : MultipleObjectsReturned → get() returned more than one Category – it returned 8!


Then, I try to use filter() instead of get()

items = Category.objects.prefetch_related(Prefetch('items')).filter().items.all()

error: QuerySet’ object has no attribute ‘items’

How can I fix this?

It sounds like there may be a misunderstanding with some other parts of the ORM. The first error, MultipleObjectsReturned, is because .get() expects one and only one value to be returned, otherwise it errors. It’s defensive mechanism of the ORM. You may be interested in the .first() function when you really only care about getting some value or None back. Please check out the ORM’s queryset documentation for more extensive information.

The second error is due to attempting to access the property items which is does not exist on a QuerySet which is what’s being returned by .filter(). If you’re looking to explore the orm, try .first() to get an instance rather than get, but be careful of the implications.

1 Like