41
votes

I've got a function built into my Django model class and I want to use that function to filter my query results.

  class service:
       ......
       def is_active(self):
            if datetime.now() > self.end_time:
                  return False
            return True

Now I want to use this function into my query filter, something like

nserv = service.objects.filter(is_active=True)

I know, for this simple 'is_active' case, I can directly make this comparision in filter query, but for more complex situations, that may not be possible. How should I make a query, based on custom functions?

4
By the way, you could do return datetime.now() <= self.end_time :-) - Rikki
I had exactly the same problem! Even the function was called the same - Adrian Grzywaczewski
The answers proposed here first do the query and then filter in it. For huge datasets and restrictive filtering, it would be more efficient to store the output of the function in a field of the model in order to do the filtering directly on the database query, and not afterwards. - Hugo Trentesaux

4 Answers

19
votes

I would suggest you to use a custom manager for your class, like this you could use :

nserv = service.objects.are_active()

This would be achieved with something like:

class ServiceManager(models.Manager):
    def are_active(self):
        # use your method to filter results
        return you_custom_queryset

See custom managers

28
votes

I just had a similar issue. The problem was i had to return a QuerySet instance. A quick solution for me was to do something like:

active_serv_ids = [service.id for service in Service.objects.all() if service.is_active()]
nserv = Service.objects.filter(id__in=active_serv_ids)

pretty sure this is not the prettiest and performant way to do this, but i works for me.

a more verbose way of doing this would be:

active_serv_ids = []

for service in Service.objects.all():
if service.is_active():
    active_serv_ids.append(service.id)

nserv = Service.objects.filter(id__in=active_serv_ids)
17
votes

You may not be able to, instead you can post-process the queryset with a list comprehension or generator expression.

For example:

[x for x in Q if x.somecond()]
9
votes

The answer by Ignacio is interesting, but it does not return a queryset. This one does:

def users_by_role(role):
    users = User.objects.all()
    ids = [user.id for user in users if user.role == role]
    return users.filter(id__in=ids)