| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- # -*- coding: utf-8 -*-
- """
- MongoDB model fields emulating Django Extensions' additional model fields
- These fields are essentially identical to existing Extensions fields, but South hooks have been removed (since mongo requires no schema migration)
- """
- import re
- import six
- import datetime
- from django import forms
- from django.db.models.constants import LOOKUP_SEP
- from django.template.defaultfilters import slugify
- from django.utils.translation import gettext_lazy as _
- from mongoengine.fields import StringField, DateTimeField
- import uuid
- class SlugField(StringField):
- description = _("String (up to %(max_length)s)")
- def __init__(self, *args, **kwargs):
- kwargs['max_length'] = kwargs.get('max_length', 50)
- # Set db_index=True unless it's been set manually.
- if 'db_index' not in kwargs:
- kwargs['db_index'] = True
- super().__init__(*args, **kwargs)
- def get_internal_type(self):
- return "SlugField"
- def formfield(self, **kwargs):
- defaults = {'form_class': forms.SlugField}
- defaults.update(kwargs)
- return super().formfield(**defaults)
- class AutoSlugField(SlugField):
- """
- AutoSlugField, adapted for MongoDB
- 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)
- super().__init__(*args, **kwargs)
- def _slug_strip(self, value):
- """
- Clean 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 self.slugify_function(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 lookup_value: self.slugify_func(self.get_slug_fields(model_instance, lookup_value))
- 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 get_slug_fields(self, model_instance, lookup_value):
- lookup_value_path = lookup_value.split(LOOKUP_SEP)
- attr = model_instance
- for elem in lookup_value_path:
- try:
- attr = getattr(attr, elem)
- except AttributeError:
- raise AttributeError(
- "value {} in AutoSlugField's 'populate_from' argument {} returned an error - {} has no attribute {}".format(
- elem, lookup_value, attr, elem))
- if callable(attr):
- return "%s" % attr()
- return attr
- def pre_save(self, model_instance, add):
- value = six.u(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('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(StringField):
- """
- 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
- 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
- StringField.__init__(self, verbose_name, name, **kwargs)
- def get_internal_type(self):
- return StringField.__name__
- def contribute_to_class(self, cls, name):
- if self.primary_key:
- assert not cls._meta.has_auto_field, "A model can't have more than one AutoField: %s %s %s; have %s" % (self, cls, name, cls._meta.auto_field)
- super().contribute_to_class(cls, name)
- cls._meta.has_auto_field = True
- cls._meta.auto_field = self
- else:
- super().contribute_to_class(cls, 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 = six.u(self.create_uuid())
- setattr(model_instance, self.attname, value)
- return value
- else:
- value = super().pre_save(model_instance, add)
- if self.auto and not value:
- value = six.u(self.create_uuid())
- setattr(model_instance, self.attname, value)
- return value
|