| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- """
- AutoSlugField taken from django-extensions at
- 15d3eb305957cee4768dd86e44df1bdad341a10e
- Uses Oscar's slugify function instead of Django's
-
- Copyright (c) 2007 Michael Trier
-
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- """
-
- import re
- from django.utils import six
-
- from django.db.models import SlugField
-
- try:
- from django.utils.encoding import force_unicode # NOQA
- except ImportError:
- from django.utils.encoding import force_text as force_unicode # NOQA
-
- from oscar.core.utils import slugify
-
-
- 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', six.u('-'))
- self.overwrite = kwargs.pop('overwrite', False)
- self.uppercase = kwargs.pop('uppercase', False)
- self.allow_duplicates = kwargs.pop('allow_duplicates', 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 slugify(content)
- return ''
-
- def create_slug(self, model_instance, add): # NOQA (too complex)
- # 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)
-
- # only set slug if empty and first-time save, or when overwrite=True
- if add and not getattr(model_instance, self.attname) 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)) # NOQA
- slug = self.separator.join(map(slug_for_field, self._populate_from)) # NOQA
- 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)
-
- if self.uppercase:
- slug = slug.upper()
-
- 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
|