web/lib/django_extensions/db/fields/__init__.py
changeset 3 526ebd3988b0
--- /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