From fec342df5a9c6967787c9b4304da5a3b20d184c6 Mon Sep 17 00:00:00 2001 From: Őry Máté <ory.mate@cloud.bme.hu> Date: Wed, 19 Mar 2014 00:13:14 +0100 Subject: [PATCH] common: add HumanSortField --- circle/common/models.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/circle/common/models.py b/circle/common/models.py index 8701698..ecbc922 100644 --- a/circle/common/models.py +++ b/circle/common/models.py @@ -1,3 +1,4 @@ +from collections import deque from hashlib import sha224 from logging import getLogger from time import time @@ -139,3 +140,78 @@ def method_cache(memcached_seconds=60, instance_seconds=5): # noqa return x return inner_cache + + +class HumanSortField(CharField): + """ + A CharField that monitors another field on the same model and sets itself + to a normalized value, which can be used for sensible lexicographycal + sorting for fields containing numerals. (Avoiding technically correct + orderings like [a1, a10, a2], which can be annoying for file or host + names.) + + Apart from CharField's default arguments, an argument is requered: + - monitor sets the base field, whose value is normalized. + - maximum_number_length can also be provided, and defaults to 4. If + you have to sort values containing numbers greater than 9999, you + should increase it. + + Code is based on carljm's django-model-utils. + """ + def __init__(self, *args, **kwargs): + logger.debug('Initing HumanSortField(%s %s)', + unicode(args), unicode(kwargs)) + kwargs.setdefault('default', "") + self.maximum_number_length = kwargs.pop('maximum_number_length', 4) + monitor = kwargs.pop('monitor', None) + if not monitor: + raise TypeError( + '%s requires a "monitor" argument' % self.__class__.__name__) + self.monitor = monitor + kwargs['blank'] = True + super(HumanSortField, self).__init__(*args, **kwargs) + + def get_monitored_value(self, instance): + return getattr(instance, self.monitor) + + def get_normalized_value(self, val): + + def partition(s, pred): + match, notmatch = deque(), deque() + while s and pred(s[0]): + match.append(s.popleft()) + while s and not pred(s[0]): + notmatch.append(s.popleft()) + return (''.join(match), ''.join(notmatch), s) + + logger.debug('Normalizing value: %s', val) + norm = "" + val = deque(val) + while val: + numbers, letters, val = partition(val, lambda s: s[0].isdigit()) + norm += numbers and numbers.rjust(self.maximum_number_length, '0') + norm += letters + logger.debug('Normalized value: %s', norm) + return norm + + def pre_save(self, model_instance, add): + logger.debug('Pre-saving %s.%s. %s', + model_instance, self.attname, add) + value = self.get_normalized_value( + self.get_monitored_value(model_instance)) + setattr(model_instance, self.attname, value[:self.max_length]) + return super(HumanSortField, self).pre_save(model_instance, add) + +# allow South to handle these fields smoothly +try: + from south.modelsinspector import add_introspection_rules + add_introspection_rules(rules=[ + ( + (HumanSortField,), + [], + {'monitor': ('monitor', {}), + 'maximum_number_length': ('maximum_number_length', {}), } + ), + ], patterns=['common\.models\.']) +except ImportError: + pass -- libgit2 0.26.0