diff -r ebaad720f88b -r 526ebd3988b0 web/lib/django_extensions/db/fields/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/lib/django_extensions/db/fields/__init__.py Wed Jan 20 12:37:40 2010 +0100 @@ -0,0 +1,213 @@ +""" +Django Extensions additional model fields +""" + +from django.template.defaultfilters import slugify +from django.db.models import DateTimeField, CharField, SlugField +import datetime +import re + +try: + import uuid +except ImportError: + from django_extensions.utils import uuid + +class AutoSlugField(SlugField): + """ AutoSlugField + + By default, sets editable=False, blank=True. + + Required arguments: + + populate_from + Specifies which field or list of fields the slug is populated from. + + Optional arguments: + + separator + Defines the used separator (default: '-') + + overwrite + If set to True, overwrites the slug on every save (default: False) + + Inspired by SmileyChris' Unique Slugify snippet: + http://www.djangosnippets.org/snippets/690/ + """ + def __init__(self, *args, **kwargs): + kwargs.setdefault('blank', True) + kwargs.setdefault('editable', False) + + populate_from = kwargs.pop('populate_from', None) + if populate_from is None: + raise ValueError("missing 'populate_from' argument") + else: + self._populate_from = populate_from + self.separator = kwargs.pop('separator', u'-') + self.overwrite = kwargs.pop('overwrite', False) + super(AutoSlugField, self).__init__(*args, **kwargs) + + def _slug_strip(self, value): + """ + Cleans up a slug by removing slug separator characters that occur at + the beginning or end of a slug. + + If an alternate separator is used, it will also replace any instances + of the default '-' separator with the new separator. + """ + re_sep = '(?:-|%s)' % re.escape(self.separator) + value = re.sub('%s+' % re_sep, self.separator, value) + return re.sub(r'^%s+|%s+$' % (re_sep, re_sep), '', value) + + def slugify_func(self, content): + return slugify(content) + + def create_slug(self, model_instance, add): + # get fields to populate from and slug field to set + if not isinstance(self._populate_from, (list, tuple)): + self._populate_from = (self._populate_from, ) + slug_field = model_instance._meta.get_field(self.attname) + + if add or self.overwrite: + # slugify the original field content and set next step to 2 + slug_for_field = lambda field: self.slugify_func(getattr(model_instance, field)) + slug = self.separator.join(map(slug_for_field, self._populate_from)) + next = 2 + else: + # get slug from the current model instance and calculate next + # step from its number, clean-up + slug = self._slug_strip(getattr(model_instance, self.attname)) + next = slug.split(self.separator)[-1] + if next.isdigit(): + slug = self.separator.join(slug.split(self.separator)[:-1]) + next = int(next) + else: + next = 2 + + # strip slug depending on max_length attribute of the slug field + # and clean-up + slug_len = slug_field.max_length + if slug_len: + slug = slug[:slug_len] + slug = self._slug_strip(slug) + original_slug = slug + + # exclude the current model instance from the queryset used in finding + # the next valid slug + queryset = model_instance.__class__._default_manager.all() + if model_instance.pk: + queryset = queryset.exclude(pk=model_instance.pk) + + # form a kwarg dict used to impliment any unique_together contraints + kwargs = {} + for params in model_instance._meta.unique_together: + if self.attname in params: + for param in params: + kwargs[param] = getattr(model_instance, param, None) + kwargs[self.attname] = slug + + # increases the number while searching for the next valid slug + # depending on the given slug, clean-up + while not slug or queryset.filter(**kwargs): + slug = original_slug + end = '%s%s' % (self.separator, next) + end_len = len(end) + if slug_len and len(slug)+end_len > slug_len: + slug = slug[:slug_len-end_len] + slug = self._slug_strip(slug) + slug = '%s%s' % (slug, end) + kwargs[self.attname] = slug + next += 1 + return slug + + def pre_save(self, model_instance, add): + value = unicode(self.create_slug(model_instance, add)) + setattr(model_instance, self.attname, value) + return value + + def get_internal_type(self): + return "SlugField" + +class CreationDateTimeField(DateTimeField): + """ CreationDateTimeField + + By default, sets editable=False, blank=True, default=datetime.now + """ + + def __init__(self, *args, **kwargs): + kwargs.setdefault('editable', False) + kwargs.setdefault('blank', True) + kwargs.setdefault('default', datetime.datetime.now) + DateTimeField.__init__(self, *args, **kwargs) + + def get_internal_type(self): + return "DateTimeField" + +class ModificationDateTimeField(CreationDateTimeField): + """ ModificationDateTimeField + + By default, sets editable=False, blank=True, default=datetime.now + + Sets value to datetime.now() on each save of the model. + """ + + def pre_save(self, model, add): + value = datetime.datetime.now() + setattr(model, self.attname, value) + return value + + def get_internal_type(self): + return "DateTimeField" + +class UUIDVersionError(Exception): + pass + +class UUIDField(CharField): + """ UUIDField + + By default uses UUID version 1 (generate from host ID, sequence number and current time) + + The field support all uuid versions which are natively supported by the uuid python module. + For more information see: http://docs.python.org/lib/module-uuid.html + """ + + def __init__(self, verbose_name=None, name=None, auto=True, version=1, node=None, clock_seq=None, namespace=None, **kwargs): + kwargs['max_length'] = 36 + if auto: + kwargs['blank'] = True + kwargs.setdefault('editable', False) + self.auto = auto + self.version = version + if version==1: + self.node, self.clock_seq = node, clock_seq + elif version==3 or version==5: + self.namespace, self.name = namespace, name + CharField.__init__(self, verbose_name, name, **kwargs) + + def get_internal_type(self): + return CharField.__name__ + + def create_uuid(self): + if not self.version or self.version==4: + return uuid.uuid4() + elif self.version==1: + return uuid.uuid1(self.node, self.clock_seq) + elif self.version==2: + raise UUIDVersionError("UUID version 2 is not supported.") + elif self.version==3: + return uuid.uuid3(self.namespace, self.name) + elif self.version==5: + return uuid.uuid5(self.namespace, self.name) + else: + raise UUIDVersionError("UUID version %s is not valid." % self.version) + + def pre_save(self, model_instance, add): + if self.auto and add: + value = unicode(self.create_uuid()) + setattr(model_instance, self.attname, value) + return value + else: + value = super(UUIDField, self).pre_save(model_instance, add) + if self.auto and not value: + value = unicode(self.create_uuid()) + setattr(model_instance, self.attname, value) + return value \ No newline at end of file