|
|
@@ -1,390 +0,0 @@
|
|
|
-"""
|
|
|
-Django Extensions additional model fields
|
|
|
-"""
|
|
|
-import re
|
|
|
-import six
|
|
|
-import warnings
|
|
|
-
|
|
|
-try:
|
|
|
- import uuid
|
|
|
- HAS_UUID = True
|
|
|
-except ImportError:
|
|
|
- HAS_UUID = False
|
|
|
-
|
|
|
-try:
|
|
|
- import shortuuid
|
|
|
- HAS_SHORT_UUID = True
|
|
|
-except ImportError:
|
|
|
- HAS_SHORT_UUID = False
|
|
|
-
|
|
|
-from django.core.exceptions import ImproperlyConfigured
|
|
|
-from django.template.defaultfilters import slugify
|
|
|
-from django.db.models import DateTimeField, CharField, SlugField
|
|
|
-
|
|
|
-try:
|
|
|
- from django.utils.timezone import now as datetime_now
|
|
|
- assert datetime_now
|
|
|
-except ImportError:
|
|
|
- import datetime
|
|
|
- datetime_now = datetime.datetime.now
|
|
|
-
|
|
|
-try:
|
|
|
- from django.utils.encoding import force_unicode # NOQA
|
|
|
-except ImportError:
|
|
|
- from django.utils.encoding import force_text as force_unicode # NOQA
|
|
|
-
|
|
|
-
|
|
|
-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.slugify_function = kwargs.pop('slugify_function', slugify)
|
|
|
- self.separator = kwargs.pop('separator', six.u('-'))
|
|
|
- self.overwrite = kwargs.pop('overwrite', False)
|
|
|
- if not isinstance(self.overwrite, bool):
|
|
|
- raise ValueError("'overwrite' argument must be True or False")
|
|
|
- self.allow_duplicates = kwargs.pop('allow_duplicates', False)
|
|
|
- if not isinstance(self.allow_duplicates, bool):
|
|
|
- raise ValueError("'allow_duplicates' argument must be True or 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 get_queryset(self, model_cls, slug_field):
|
|
|
- for field, model in model_cls._meta.get_fields_with_model():
|
|
|
- if model and field == slug_field:
|
|
|
- return model._default_manager.all()
|
|
|
- return model_cls._default_manager.all()
|
|
|
-
|
|
|
- def slugify_func(self, content):
|
|
|
- if content:
|
|
|
- return self.slugify_function(content)
|
|
|
- return ''
|
|
|
-
|
|
|
- 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
|
|
|
- slug = getattr(model_instance, self.attname)
|
|
|
- # model_instance is being modified, and overwrite is False,
|
|
|
- # so instead of doing anything, just return the current slug
|
|
|
- return slug
|
|
|
-
|
|
|
- # 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
|
|
|
-
|
|
|
- if self.allow_duplicates:
|
|
|
- return slug
|
|
|
-
|
|
|
- # exclude the current model instance from the queryset used in finding
|
|
|
- # the next valid slug
|
|
|
- queryset = self.get_queryset(model_instance.__class__, slug_field)
|
|
|
- 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 = force_unicode(self.create_slug(model_instance, add))
|
|
|
- setattr(model_instance, self.attname, value)
|
|
|
- return value
|
|
|
-
|
|
|
- def get_internal_type(self):
|
|
|
- return "SlugField"
|
|
|
-
|
|
|
- def south_field_triple(self):
|
|
|
- "Returns a suitable description of this field for South."
|
|
|
- # We'll just introspect the _actual_ field.
|
|
|
- from south.modelsinspector import introspector
|
|
|
- field_class = '%s.AutoSlugField' % self.__module__
|
|
|
- args, kwargs = introspector(self)
|
|
|
- kwargs.update({
|
|
|
- 'populate_from': repr(self._populate_from),
|
|
|
- 'separator': repr(self.separator),
|
|
|
- 'overwrite': repr(self.overwrite),
|
|
|
- 'allow_duplicates': repr(self.allow_duplicates),
|
|
|
- })
|
|
|
- # That's our definition!
|
|
|
- return (field_class, args, kwargs)
|
|
|
-
|
|
|
- def deconstruct(self):
|
|
|
- name, path, args, kwargs = super(AutoSlugField, self).deconstruct()
|
|
|
- kwargs['populate_from'] = self._populate_from
|
|
|
- if not self.separator == six.u('-'):
|
|
|
- kwargs['separator'] = self.separator
|
|
|
- if self.overwrite is not False:
|
|
|
- kwargs['overwrite'] = True
|
|
|
- if self.allow_duplicates is not False:
|
|
|
- kwargs['allow_duplicates'] = True
|
|
|
- return name, path, args, kwargs
|
|
|
-
|
|
|
-
|
|
|
-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_now)
|
|
|
- DateTimeField.__init__(self, *args, **kwargs)
|
|
|
-
|
|
|
- def get_internal_type(self):
|
|
|
- return "DateTimeField"
|
|
|
-
|
|
|
- def south_field_triple(self):
|
|
|
- "Returns a suitable description of this field for South."
|
|
|
- # We'll just introspect ourselves, since we inherit.
|
|
|
- from south.modelsinspector import introspector
|
|
|
- field_class = "django.db.models.fields.DateTimeField"
|
|
|
- args, kwargs = introspector(self)
|
|
|
- return (field_class, args, kwargs)
|
|
|
-
|
|
|
- def deconstruct(self):
|
|
|
- name, path, args, kwargs = super(CreationDateTimeField, self).deconstruct()
|
|
|
- if self.editable is not False:
|
|
|
- kwargs['editable'] = True
|
|
|
- if self.blank is not True:
|
|
|
- kwargs['blank'] = False
|
|
|
- if self.default is not datetime_now:
|
|
|
- kwargs['default'] = self.default
|
|
|
- return name, path, args, kwargs
|
|
|
-
|
|
|
-
|
|
|
-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_now()
|
|
|
- setattr(model, self.attname, value)
|
|
|
- return value
|
|
|
-
|
|
|
- def get_internal_type(self):
|
|
|
- return "DateTimeField"
|
|
|
-
|
|
|
- def south_field_triple(self):
|
|
|
- "Returns a suitable description of this field for South."
|
|
|
- # We'll just introspect ourselves, since we inherit.
|
|
|
- from south.modelsinspector import introspector
|
|
|
- field_class = "django.db.models.fields.DateTimeField"
|
|
|
- args, kwargs = introspector(self)
|
|
|
- return (field_class, args, kwargs)
|
|
|
-
|
|
|
-
|
|
|
-class UUIDVersionError(Exception):
|
|
|
- pass
|
|
|
-
|
|
|
-
|
|
|
-class UUIDField(CharField):
|
|
|
- """ UUIDField
|
|
|
-
|
|
|
- By default uses UUID version 4 (randomly generated UUID).
|
|
|
-
|
|
|
- The field support all uuid versions which are natively supported by the uuid python module, except version 2.
|
|
|
- For more information see: http://docs.python.org/lib/module-uuid.html
|
|
|
- """
|
|
|
- DEFAULT_MAX_LENGTH = 36
|
|
|
-
|
|
|
- def __init__(self, verbose_name=None, name=None, auto=True, version=4, node=None, clock_seq=None, namespace=None, uuid_name=None, *args, **kwargs):
|
|
|
- warnings.warn("Django 1.8 features a native UUIDField, this UUIDField will be removed after Django 1.7 becomes unsupported.", DeprecationWarning)
|
|
|
-
|
|
|
- if not HAS_UUID:
|
|
|
- raise ImproperlyConfigured("'uuid' module is required for UUIDField. (Do you have Python 2.5 or higher installed ?)")
|
|
|
- kwargs.setdefault('max_length', self.DEFAULT_MAX_LENGTH)
|
|
|
- if auto:
|
|
|
- self.empty_strings_allowed = False
|
|
|
- kwargs['blank'] = True
|
|
|
- kwargs.setdefault('editable', False)
|
|
|
- self.auto = auto
|
|
|
- self.version = version
|
|
|
- self.node = node
|
|
|
- self.clock_seq = clock_seq
|
|
|
- self.namespace = namespace
|
|
|
- self.uuid_name = uuid_name or name
|
|
|
- super(UUIDField, self).__init__(verbose_name=verbose_name, *args, **kwargs)
|
|
|
-
|
|
|
- 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.uuid_name)
|
|
|
- elif self.version == 5:
|
|
|
- return uuid.uuid5(self.namespace, self.uuid_name)
|
|
|
- else:
|
|
|
- raise UUIDVersionError("UUID version %s is not valid." % self.version)
|
|
|
-
|
|
|
- def pre_save(self, model_instance, add):
|
|
|
- value = super(UUIDField, self).pre_save(model_instance, add)
|
|
|
- if self.auto and add and value is None:
|
|
|
- value = force_unicode(self.create_uuid())
|
|
|
- setattr(model_instance, self.attname, value)
|
|
|
- return value
|
|
|
- else:
|
|
|
- if self.auto and not value:
|
|
|
- value = force_unicode(self.create_uuid())
|
|
|
- setattr(model_instance, self.attname, value)
|
|
|
- return value
|
|
|
-
|
|
|
- def formfield(self, **kwargs):
|
|
|
- if self.auto:
|
|
|
- return None
|
|
|
- return super(UUIDField, self).formfield(**kwargs)
|
|
|
-
|
|
|
- def south_field_triple(self):
|
|
|
- "Returns a suitable description of this field for South."
|
|
|
- # We'll just introspect the _actual_ field.
|
|
|
- from south.modelsinspector import introspector
|
|
|
- field_class = "django.db.models.fields.CharField"
|
|
|
- args, kwargs = introspector(self)
|
|
|
- # That's our definition!
|
|
|
- return (field_class, args, kwargs)
|
|
|
-
|
|
|
- def deconstruct(self):
|
|
|
- name, path, args, kwargs = super(UUIDField, self).deconstruct()
|
|
|
- if kwargs.get('max_length', None) == self.DEFAULT_MAX_LENGTH:
|
|
|
- del kwargs['max_length']
|
|
|
- if self.auto is not True:
|
|
|
- kwargs['auto'] = self.auto
|
|
|
- if self.version != 4:
|
|
|
- kwargs['version'] = self.version
|
|
|
- if self.node is not None:
|
|
|
- kwargs['node'] = self.node
|
|
|
- if self.clock_seq is not None:
|
|
|
- kwargs['clock_seq'] = self.clock_seq
|
|
|
- if self.namespace is not None:
|
|
|
- kwargs['namespace'] = self.namespace
|
|
|
- if self.uuid_name is not None:
|
|
|
- kwargs['uuid_name'] = self.name
|
|
|
- return name, path, args, kwargs
|
|
|
-
|
|
|
-
|
|
|
-class PostgreSQLUUIDField(UUIDField):
|
|
|
- def __init__(self, *args, **kwargs):
|
|
|
- warnings.warn("Django 1.8 features a native UUIDField, this UUIDField will be removed after Django 1.7 becomes unsupported.", DeprecationWarning)
|
|
|
- super(PostgreSQLUUIDField, self).__init__(*args, **kwargs)
|
|
|
-
|
|
|
- def db_type(self, connection=None):
|
|
|
- return "UUID"
|
|
|
-
|
|
|
- def get_db_prep_value(self, value, connection, prepared=False):
|
|
|
- if isinstance(value, six.integer_types):
|
|
|
- value = uuid.UUID(int=value)
|
|
|
- elif isinstance(value, (six.string_types, six.binary_type)):
|
|
|
- if len(value) == 16:
|
|
|
- value = uuid.UUID(bytes=value)
|
|
|
- else:
|
|
|
- value = uuid.UUID(value)
|
|
|
- return super(PostgreSQLUUIDField, self).get_db_prep_value(
|
|
|
- value, connection, prepared=False)
|
|
|
-
|
|
|
-
|
|
|
-class ShortUUIDField(UUIDField):
|
|
|
- """ ShortUUIDFied
|
|
|
-
|
|
|
- Generates concise (22 characters instead of 36), unambiguous, URL-safe UUIDs.
|
|
|
-
|
|
|
- Based on `shortuuid`: https://github.com/stochastic-technologies/shortuuid
|
|
|
- """
|
|
|
- DEFAULT_MAX_LENGTH = 22
|
|
|
-
|
|
|
- def __init__(self, *args, **kwargs):
|
|
|
- super(ShortUUIDField, self).__init__(*args, **kwargs)
|
|
|
- if not HAS_SHORT_UUID:
|
|
|
- raise ImproperlyConfigured("'shortuuid' module is required for ShortUUIDField. (Do you have Python 2.5 or higher installed ?)")
|
|
|
- kwargs.setdefault('max_length', self.DEFAULT_MAX_LENGTH)
|
|
|
-
|
|
|
- def create_uuid(self):
|
|
|
- if not self.version or self.version == 4:
|
|
|
- return shortuuid.uuid()
|
|
|
- elif self.version == 1:
|
|
|
- return shortuuid.uuid()
|
|
|
- elif self.version == 2:
|
|
|
- raise UUIDVersionError("UUID version 2 is not supported.")
|
|
|
- elif self.version == 3:
|
|
|
- raise UUIDVersionError("UUID version 3 is not supported.")
|
|
|
- elif self.version == 5:
|
|
|
- return shortuuid.uuid(name=self.namespace)
|
|
|
- else:
|
|
|
- raise UUIDVersionError("UUID version %s is not valid." % self.version)
|