JordanChina
JordanChina

Reputation: 345

Reuse filter() queryset in Django

I want to write a filter once and reuse it everywhere, how can I do it?

For example: there is a model Student with flag field. I want to write a filter to get non-graduate students (flag=0). But in many views and functions we need list non-graduate students, and I am lazy and don't want to write the filter again and again in these views and functions, as this will make it hard to maintain the source code.

Can I use meta in model Student? I did not find any filter related meta options. Or can I write a function to filter in model Student? In my mind, function in model only works in one Student object instead of list.

Upvotes: 5

Views: 3913

Answers (2)

joneskoo
joneskoo

Reputation: 111

Using a custom QuerySet and QuerySet.as_manager() is the best solution now. Jamie Matthews of Dabapps has discussed reusable filters in detail in his blog post Building a higher-level query API: the right way to use Django's ORM.

“Using Django's low-level ORM query methods directly in a view is (usually) an anti-pattern”
— Jamie Matthews

The blog post is written before Django got the .as_manager() method for QuerySet though.

I'd use something like this now (based on the currently accepted answer):

class StudentQuerySet(models.query.QuerySet):
    def graduate(self):
        return self.filter(graduated=True)

    def undergraduate(self):
        return self.filter(graduated=False)

class Student(models.Model):
    graduated = BooleanField()

    objects = StudentQuerySet.as_manager()

In other, more complex cases, it's useful to be able to define complex filters in the custom query set since filters are chainable when implemented like this.

If you want this to be made available to related managers in other models (ie. if you had another model referencing the Student model and were using otherinstance.student_set.all()), see Django: Using managers for related object access. In short, do this:

class Student(models.Model):
    graduated = BooleanField()

    objects = StudentQuerySet.as_manager()
    objects.use_for_related_fields = True

Upvotes: 7

bakkal
bakkal

Reputation: 55448

You can use custom managers to keep things DRY and enhance readability (an explicitly named filter is always better than dragging a complex filter)

class GraduateManager(models.Manager):
    def get_queryset(self):
        return super(GraduateManager, self).get_queryset().filter(graduated=True)

class UndergraduateManager(models.Manager):
    def get_queryset(self):
        return super(Undergraduate, self).get_queryset().filter(graduated=False)

class Student(models.Model):
    graduated = BooleanField()

    graduates = GraduateManager()
    undergraduates = UndergraduateManager()

To use it, you get a normal queryset that you can manipulate as needed

Student.graduates.all(), or .filter(), or .count() etc

Reference https://docs.djangoproject.com/en/1.8/topics/db/managers/#modifying-initial-manager-querysets

Upvotes: 6

Related Questions