Skip to content

Instantly share code, notes, and snippets.

@tymofij
Last active December 17, 2015 03:09
Show Gist options
  • Save tymofij/5541543 to your computer and use it in GitHub Desktop.
Save tymofij/5541543 to your computer and use it in GitHub Desktop.
QuerySet that lets you sort on object fields, and filter with lambdas. Of course, those actions will go though all the recordset. Use wisely.
from operator import attrgetter
from django.db import models
class DiligentQuerySet(models.query.QuerySet):
"""
Represents a QuerySet that allows filtering with lambdas,
and sorting on object properties.
"""
def __init__(self, *args, **kwargs):
self._custom_filters = []
super(DiligentQuerySet, self).__init__(*args, **kwargs)
def _clone(self, *args, **kwargs):
c = super(DiligentQuerySet, self)._clone(*args, **kwargs)
c._custom_filters = self._custom_filters
return c
def __iter__(self):
comparers = [ ((attrgetter(field[1:].strip()), -1)
if field.startswith('-')
else (attrgetter(field.strip()), 1))
for field in self.query.order_by
]
def comparer(left, right):
for fn, mult in comparers:
fn_left, fn_right = fn(left), fn(right)
# case-insensitive compare for strings
if hasattr(fn_left, 'lower'):
fn_left = fn_left.lower()
if hasattr(fn_right, 'lower'):
fn_right = fn_right.lower()
result = cmp(fn_left, fn_right)
# do NULL compares as PostgreSQL does them,
# putting None at the end of the descending list
if fn_left is None or fn_right is None:
result = -1 * result
if result:
return mult * result
else:
return 0
# saving-restoring it for some possible future uses
order_by = self.query.order_by
# but running the query without sorting, which might have non-db fields
self.query.order_by = []
res = list(super(DiligentQuerySet, self).__iter__())
self.query.order_by = order_by
for func in self._custom_filters:
res = filter(func, res)
if comparers:
res.sort(cmp=comparer)
return iter(res)
def __getitem__(self, k):
return list(self._clone())[k]
def custom_filter(self, func):
obj = self._clone()
obj._custom_filters.append(func)
return obj
class DiligentManager(models.Manager):
def get_query_set(self):
return DiligentQuerySet(self.model, using=self._db)
def custom_filter(self, *args, **kwargs):
return self.get_query_set().custom_filter(*args, **kwargs)
# usage:
# plug DiligentManager as a manager in our record object,
# class SomeRecord(models.Model):
# ...
# objects = DiligentManager()
# ...
# and you will be able to run queries like those:
#
# SomeRecord.objects.custom_filter(lambda obj: 'Joe' in obj.get_full_name()).order_by('-some_property')]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment