Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filter by property

Tags:

python

orm

django

Nope. Django filters operate at the database level, generating SQL. To filter based on Python properties, you have to load the object into Python to evaluate the property--and at that point, you've already done all the work to load it.


I might be misunderstanding your original question, but there is a filter builtin in python.

filtered = filter(myproperty, MyModel.objects)

But it's better to use a list comprehension:

filtered = [x for x in MyModel.objects if x.myproperty()]

or even better, a generator expression:

filtered = (x for x in MyModel.objects if x.myproperty())

Riffing off @TheGrimmScientist's suggested workaround, you can make these "sql properties" by defining them on the Manager or the QuerySet, and reuse/chain/compose them:

With a Manager:

class CompanyManager(models.Manager):
    def with_chairs_needed(self):
        return self.annotate(chairs_needed=F('num_employees') - F('num_chairs'))

class Company(models.Model):
    # ...
    objects = CompanyManager()

Company.objects.with_chairs_needed().filter(chairs_needed__lt=4)

With a QuerySet:

class CompanyQuerySet(models.QuerySet):
    def many_employees(self, n=50):
        return self.filter(num_employees__gte=n)

    def needs_fewer_chairs_than(self, n=5):
        return self.with_chairs_needed().filter(chairs_needed__lt=n)

    def with_chairs_needed(self):
        return self.annotate(chairs_needed=F('num_employees') - F('num_chairs'))

class Company(models.Model):
    # ...
    objects = CompanyQuerySet.as_manager()

Company.objects.needs_fewer_chairs_than(4).many_employees()

See https://docs.djangoproject.com/en/1.9/topics/db/managers/ for more. Note that I am going off the documentation and have not tested the above.


Looks like using F() with annotations will be my solution to this.

It's not going to filter by @property, since F talks to the databse before objects are brought into python. But still putting it here as an answer since my reason for wanting filter by property was really wanting to filter objects by the result of simple arithmetic on two different fields.

so, something along the lines of:

companies = Company.objects\
    .annotate(chairs_needed=F('num_employees') - F('num_chairs'))\
    .filter(chairs_needed__lt=4)

rather than defining the property to be:

@property
def chairs_needed(self):
    return self.num_employees - self.num_chairs

then doing a list comprehension across all objects.


I had the same problem, and I developed this simple solution:

objects_id = [x.id for x in MyModel.objects.all() if x.myProperty == [...]]
MyModel.objects.filter(id__in=objects_id)

I know it's not the most performatic solution, but may help in simple cases as mine