+++ /dev/null
-from itertools import chain, dropwhile
-from operator import mul, attrgetter, __not__
-
-from django.db.models.query import REPR_OUTPUT_SIZE, EmptyQuerySet
-
-# Django Snippets
-# https://djangosnippets.org/snippets/1933/
-
-def mul_it(it1, it2):
- '''
- Element-wise iterables multiplications.
- '''
- assert len(it1) == len(it2),\
- "Can not element-wise multiply iterables of different length."
- return map(mul, it1, it2)
-
-
-def chain_sing(*iterables_or_items):
- '''
- As itertools.chain except that if an argument is not iterable then chain it
- as a singleton.
- '''
- for iter_or_item in iterables_or_items:
- if hasattr(iter_or_item, '__iter__'):
- for item in iter_or_item:
- yield item
- else:
- yield iter_or_item
-
-
-class IableSequence(object):
- '''
- Wrapper for sequence of iterable and indexable by non-negative integers
- objects. That is a sequence of objects which implement __iter__, __len__ and
- __getitem__ for slices, ints and longs.
-
- Note: not a Django-specific class.
- '''
- def __init__(self, *args, **kwargs):
- self.iables = args # wrapped sequence
- self._len = None # length cache
- self._collapsed = [] # collapsed elements cache
-
- def __len__(self):
- if not self._len:
- self._len = sum(len(iable) for iable in self.iables)
- return self._len
-
-
- def __iter__(self):
- return chain(*self.iables)
-
- def __nonzero__(self):
- try:
- iter(self).__next__()
- except StopIteration:
- return False
- return True
-
-
- def _collect(self, start=0, stop=None, step=1):
- if not stop:
- stop = len(self)
- sub_iables = []
- # collect sub sets
- it = self.iables.__iter__()
- try:
- while stop>start:
- i = it.__next__()
- i_len = len(i)
- if i_len > start:
- # no problem with 'stop' being too big
- sub_iables.append(i[start:stop:step])
- start = max(0, start-i_len)
- stop -= i_len
- except StopIteration:
- pass
- return sub_iables
-
- def __getitem__(self, key):
- '''
- Preserves wrapped indexable sequences.
- Does not support negative indices.
- '''
- # params validation
- if not isinstance(key, (slice, int, long)):
- raise TypeError
- assert ((not isinstance(key, slice) and (key >= 0))
- or (isinstance(key, slice) and (key.start is None or key.start >= 0)
- and (key.stop is None or key.stop >= 0))), \
- "Negative indexing is not supported."
- # initialization
- if isinstance(key, slice):
- start, stop, step = key.indices(len(self))
- ret_item=False
- else: # isinstance(key, (int,long))
- start, stop, step = key, key+1, 1
- ret_item=True
- # collect sub sets
- ret_iables = self._collect(start, stop, step)
- # return the simplest possible answer
- if not len(ret_iables):
- if ret_item:
- raise IndexError("'%s' index out of range" % self.__class__.__name__)
- return ()
- if ret_item:
- # we have exactly one query set with exactly one item
- assert len(ret_iables) == 1 and len(ret_iables[0]) == 1
- return ret_iables[0][0]
- # otherwise we have more then one item in at least one query set
- if len(ret_iables) == 1:
- return ret_iables[0]
- # Note: this can't be self.__class__ instead of IableSequence; exemplary
- # cause is that indexing over query sets returns lists so we can not
- # return QuerySetSequence by default. Some type checking enhancement can
- # be implemented in subclasses.
- return IableSequence(*ret_iables)
-
-
- def collapse(self, stop=None):
- '''
- Collapses sequence into a list.
-
- Try to do it effectively with caching.
- '''
- if not stop:
- stop = len(self)
- # if we already calculated sufficient collapse then return it
- if len(self._collapsed) >= stop:
- return self._collapsed[:stop]
- # otherwise collapse only the missing part
- items = self._collapsed
- sub_iables = self._collect(len(self._collapsed), stop)
- for sub_iable in sub_iables:
- items+=sub_iable
- # cache new collapsed items
- self._collapsed = items
- return self._collapsed
-
- def __repr__(self):
- # get +1 element for the truncation msg if applicable
- items = self.collapse(stop=REPR_OUTPUT_SIZE+1)
- if len(items) > REPR_OUTPUT_SIZE:
- items[-1] = "...(remaining elements truncated)..."
- return repr(items)
-
-# http://stackoverflow.com/questions/1516249/python-list-sorting-with-multiple-attributes-and-mixed-order
-class QuerySetSequence(IableSequence):
- '''
- Wrapper for the query sets sequence without the restriction on the identity
- of the base models.
- '''
- def count(self):
- if not self._len:
- self._len = sum(qs.count() for qs in self.iables)
- return self._len
-
- def __len__(self):
- # override: use DB effective count's instead of len()
- return self.count()
-
- def order_by(self, *field_names):
- '''
- Returns a list of the QuerySetSequence items with the ordering changed.
- '''
- # construct a comparator function based on the field names prefixes
- reverses = [1] * len(field_names)
- field_names = list(field_names)
- for i in range(len(field_names)):
- field_name = field_names[i]
- if field_name[0] == '-':
- reverses[i] = -1
- field_names[i] = field_name[1:]
- # wanna iterable and attrgetter returns single item if 1 arg supplied
- fields_getter = lambda i: chain_sing(attrgetter(*field_names)(i))
- print("************************")
- print(fields_getter)
- print("************************")
- # comparator gets the first non-zero value of the field comparison
- # results taking into account reverse order for fields prefixed with '-'
- comparator = lambda i1, i2:\
- dropwhile(__not__,
- mul_it(map(key, fields_getter(i1), fields_getter(i2)), reverses)
- ).__next__()
-
- # return new sorted list
- return sorted(self.collapse(), key=fields_getter)
-
- def filter(self, *args, **kwargs):
- """
- Returns a new QuerySetSequence or instance with the args ANDed to the
- existing set.
-
- QuerySetSequence is simplified thus result actually can be one of:
- QuerySetSequence, QuerySet, EmptyQuerySet.
- """
- return self._filter_or_exclude(False, *args, **kwargs)
-
- def exclude(self, *args, **kwargs):
- """
- Returns a new QuerySetSequence instance with NOT (args) ANDed to the
- existing set.
-
- QuerySetSequence is simplified thus result actually can be one of:
- QuerySetSequence, QuerySet, EmptyQuerySet.
- """
- return self._filter_or_exclude(True, *args, **kwargs)
-
- def _simplify(self, qss=None):
- '''
- Returns QuerySetSequence, QuerySet or EmptyQuerySet depending on the
- contents of items, i.e. at least two non empty QuerySets, exactly one
- non empty QuerySet and all empty QuerySets respectively.
-
- Does not modify original QuerySetSequence.
- '''
- not_empty_qss = filter(None, qss if qss else self.iables)
- if not len(not_empty_qss):
- return EmptyQuerySet()
- if len(not_empty_qss) == 1:
- return not_empty_qss[0]
- return QuerySetSequence(*not_empty_qss)
-
- def _filter_or_exclude(self, negate, *args, **kwargs):
- '''
- Maps _filter_or_exclude over QuerySet items and simplifies the result.
- '''
- # each Query set is cloned separately
- return self._simplify(*map(lambda qs:
- qs._filter_or_exclude(negate, *args, **kwargs), self.iables))
-
- def exists(self):
- for qs in self.iables:
- if qs.exists():
- return True
- return False
-
-
-def cmp_to_key(mycmp):
- 'Convert a cmp= function into a key= function'
- class K(object):
- def __init__(self, obj, *args):
- self.obj = obj
- def __lt__(self, other):
- return mycmp(self.obj, other.obj) < 0
- def __gt__(self, other):
- return mycmp(self.obj, other.obj) > 0
- def __eq__(self, other):
- return mycmp(self.obj, other.obj) == 0
- def __le__(self, other):
- return mycmp(self.obj, other.obj) <= 0
- def __ge__(self, other):
- return mycmp(self.obj, other.obj) >= 0
- def __ne__(self, other):
- return mycmp(self.obj, other.obj) != 0
- return K