import re from django.template.defaultfilters import slugify def try_slug(instance, slug): """Check if the slug is free for corresponding model instance """ queryset = instance.__class__._default_manager.all() if not queryset.filter(**{"slug": slug}): return True else: return False def unique_slugify(instance, value, slug_field_name='slug', queryset=None, slug_separator='-'): """Create unique slug Creates unique slug across model class. """ slug_field = instance._meta.get_field(slug_field_name) slug_len = slug_field.max_length slug = slugify(value) slug = slug[:slug_len] slug = _slug_strip(slug, slug_separator) original_slug = slug if queryset is None: queryset = instance.__class__._default_manager.all() if instance.pk: queryset = queryset.exclude(pk=instance.pk) next = 2 while not slug or queryset.filter(**{slug_field_name: slug}): slug = original_slug end = '%s%s' % (slug_separator, next) if slug_len and len(slug) + len(end) > slug_len: slug = slug[:slug_len-len(end)] slug = _slug_strip(slug, slug_separator) slug = '%s%s' % (slug, end) next += 1 return slug def _slug_strip(value, separator='-'): """ Cleans up a slug by removing slug separator characters that occur at the beginning or end of a slug. """ separator = separator or '' if separator == '-' or not separator: re_sep = '-' else: re_sep = '(?:-|%s)' % re.escape(separator) if separator != re_sep: value = re.sub('%s+' % re_sep, separator, value) if separator: if separator != '-': re_sep = re.escape(separator) value = re.sub(r'^%s+|%s+$' % (re_sep, re_sep), '', value) return value
So now some explanations to the code. First function gives us possibility to check if slug is free for specific model class. It takes object instance and slug and returns boolean value. Inside the code we take default 'all' manager of the model and filter queryset with slug.
Second function is the key to solving our problem. The unique_slugify function takes shown parameters. At first we check the max length of the slug field, so that we won't exceed its value. Then we slugify our slug with built in django function, cut it if needed and strip of sepparators different than '-' or 'separator' parameter received by function. Next step is to get the queryset on which we will be working, excluding our model instance. Finally the fun begins. In our while loop condition we check existence of newly created slug or result of filtering our queryset. If both are none we take the separator and add next integer to it.
If the length of concatenated slug and identifier is too long, we cut it off. Finally we're creating slug that will be used in while loop condition filter.
Last function is stripping our slug by separators appearing at the beginning and end of slug. Because it uses many regexes I will leave in your hands work of decyphering it :)
And now for some short sample usage :
free = try_slug(self.instance, self.cleaned_data['slug']) if not free: self.cleaned_data['slug'] = unique_slugify(self.instance, self.cleaned_data['slug'])
No comments:
Post a Comment