mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-05 16:58:59 +00:00
213 lines
No EOL
7.3 KiB
Python
213 lines
No EOL
7.3 KiB
Python
"""
|
|
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 |