Home > Mobile >  Django filtering relation in ListView
Django filtering relation in ListView

Time:11-25

Given the models

class TaskGroup(models.Model):
    name = models.CharField(max_length=256)

class Task(models.Model):
    name = models.CharField(max_length=256)
    group = models.ForeignKey(TaskGroup, on_delete=models.CASCADE)
    completed = models.BooleanField(default=False)
    completed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True)

and the list view

class TaskGroupListView(ListView):
   model = TaskGroup

I'd like to display the list of task groups with corresponding tasks. The catch is - I only want to show the tasks that have not been completed or have been completed by the user, or if the user as the attribute user.type == "ADMIN" set show all groups and all tasks.

Right now I have a template that looks like:

{% for taskgroup in object_list %}
<h1>{{ taskgroup.name }}</h1>
<ul>
    {% for task in taskgroup.task_set.all %}
    <li>{{ task.name }}</li>
    {% endfor %}
</ul>
{% endfor %}

I know that I can modify the queryset of a list view by overriding get_queryset like:

def get_queryset(self):
    if self.request.user == "ADMIN":
        return TaskGroup.objects.all()
    else:
        ...

but I'm not sure how to go about filtering the Task relations on the TaskGroups in the else clause.

I've thought about creating a manager subclass for Task that can filter based on .completed and .completed_by that I can use in the template but that seems against the philosophy of Django - I'd like to keep all the logic in the view (this may be way off the mark so please correct me, its been a while since I've touched django/read two scoops of django).

Is there some idiomatic way to do this? Should I discard ListView entirely and write some custom logic? any guidance here is helpful. Thanks.

CodePudding user response:

You can use prefetch_related with a Prefetch that uses a custom filtered queryset like this:

from django.db.models import Prefetch, Q


def get_queryset(self):
    if self.request.user.is_admin:
        return TaskGroup.objects.all()

    return TaskGroup.objects.prefetch_related(
        Prefetch(
            'task_set',
            queryset=Task.objects.filter(Q(completed=False) | Q(completed_by=self.request.user))
        )
    )

This will get all the TaskGroups with the related Taskss (in task_set) filtered by those that are not yet completed or are completed by the current user.

  • Related