Last active
December 17, 2015 03:09
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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