[snowy] Import autoslug, start using it
- From: Brad Taylor <btaylor src gnome org>
- To: svn-commits-list gnome org
- Subject: [snowy] Import autoslug, start using it
- Date: Mon, 18 May 2009 16:35:07 -0400 (EDT)
commit 1b9c82648d61e17bd5e4d08bd2b17931e3ab4cfe
Author: Brad Taylor <brad getcoded net>
Date: Mon May 18 16:34:48 2009 -0400
Import autoslug, start using it
---
lib/autoslug/__init__.py | 15 ++++
lib/autoslug/fields.py | 163 ++++++++++++++++++++++++++++++++++++++++++++++
lib/autoslug/settings.py | 33 +++++++++
notes/models.py | 5 +-
settings.py | 1 +
5 files changed, 216 insertions(+), 1 deletions(-)
diff --git a/lib/autoslug/__init__.py b/lib/autoslug/__init__.py
new file mode 100644
index 0000000..e10fbaf
--- /dev/null
+++ b/lib/autoslug/__init__.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2008â??2009 Andy Mikhailenko
+#
+# This file is part of django-autoslug.
+#
+# django-autoslug is free software under terms of the GNU Lesser
+# General Public License version 3 (LGPLv3) as published by the Free
+# Software Foundation. See the file README for copying conditions.
+#
+
+__author__ = 'Andy Mikhailenko'
+__license__ = 'GNU Lesser General Public License (GPL), Version 3'
+__url__ = 'http://bitbucket.org/neithere/django-autoslug/'
+__version__ = '1.0.1'
diff --git a/lib/autoslug/fields.py b/lib/autoslug/fields.py
new file mode 100644
index 0000000..8771431
--- /dev/null
+++ b/lib/autoslug/fields.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2008â??2009 Andy Mikhailenko
+#
+# This file is part of django-autoslug.
+#
+# django-autoslug is free software under terms of the GNU Lesser
+# General Public License version 3 (LGPLv3) as published by the Free
+# Software Foundation. See the file README for copying conditions.
+#
+
+# django
+from django.db.models.fields import DateField, SlugField
+
+# app
+from autoslug.settings import slugify
+
+class AutoSlugField(SlugField):
+ """
+ AutoSlugField is a slug field that can automatically do the following on save:
+ - populate itself from another field (using 'populate_from'),
+ - use custom slugify() function (can be defined in settings), and
+ - preserve uniqueness of the value (using 'unique' or 'unique_with').
+
+ None of the tasks is mandatory, i.e. you can have auto-populated non-unique fields,
+ manually entered unique ones (absolutely unique or within a given date) or both.
+
+ Uniqueness is preserved by checking if the slug is unique with given constraints
+ (unique_with) or globally (unique) and adding a number to the slug to make
+ it unique. See examples below.
+
+ IMPORTANT: always declare attributes with AutoSlugField *after* attributes
+ from which you wish to 'populate_from' or check 'unique_with' because autosaved
+ dates and other such fields must be already processed before checking occurs.
+
+ Usage examples:
+
+ # slugify but allow non-unique slugs
+ slug = AutoSlugField()
+
+ # globally unique, silently fix on conflict ("foo" --> "foo-1".."foo-n")
+ slug = AutoSlugField(unique=True)
+
+ # autoslugify value from title attr; default editable to False
+ slug = AutoSlugField(populate_from='title')
+
+ # same as above but force editable=True
+ slug = AutoSlugField(populate_from='title', editable=True)
+
+ # ensure that slug is unique with given date (not globally)
+ slug = AutoSlugField(unique_with='pub_date')
+
+ # ensure that slug is unique with given date AND category
+ slug = AutoSlugField(unique_with=('pub_date','category'))
+
+ # mix above-mentioned behaviour bits
+ slug = AutoSlugField(populate_from='title', unique_with='pub_date')
+
+ Please don't forget to declare your slug attribute after the fields
+ referenced in `populate_from` and `unique_with`.
+ """
+ def __init__(self, *args, **kwargs):
+ kwargs['max_length'] = kwargs.get('max_length', 50)
+
+ # autopopulated slug is not editable unless told so
+ self.populate_from = kwargs.pop('populate_from', None)
+ if self.populate_from:
+ kwargs.setdefault('editable', False)
+
+ # unique_with value can be string or tuple
+ self.unique_with = kwargs.pop('unique_with', ())
+ if isinstance(self.unique_with, basestring):
+ self.unique_with = (self.unique_with,)
+
+ # backward compatibility
+ if kwargs.get('unique_with_date'):
+ from warnings import warn
+ warn('Using unique_with_date="foo" in AutoSlugField is deprecated, '\
+ 'use unique_with=("foo",) instead.', DeprecationWarning)
+ self.unique_with += (kwargs['unique_with_date'],)
+
+ # force full uniqueness unless more granular is specified
+ if not self.unique_with:
+ kwargs.setdefault('unique', True)
+
+ # Set db_index=True unless it's been set manually.
+ if 'db_index' not in kwargs:
+ kwargs['db_index'] = True
+ super(SlugField, self).__init__(*args, **kwargs)
+
+ def pre_save(self, instance, add):
+ # get currently entered slug
+ value = self.value_from_object(instance)
+
+ # autopopulate (unless the field is editable and has some value)
+ # SNOWY: put self.populate_from before value so that slug is always updated
+ if self.populate_from: # and not self.editable:
+ slug = slugify(getattr(instance, self.populate_from))
+ elif value:
+ slug = slugify(value)
+
+ if not slug:
+ # no incoming value, use model name
+ slug = instance._meta.module_name
+
+ assert slug, 'slug is defined before trying to ensure uniqueness'
+
+ # ensure the slug is unique (if required)
+ if self.unique or self.unique_with:
+ slug = self._generate_unique_slug(instance, slug)
+
+ assert slug, 'value is filled before saving'
+
+ setattr(instance, self.name, slug) # XXX do we need this?
+
+ return slug
+ #return super(AutoSlugField, self).pre_save(instance, add)
+
+ def _generate_unique_slug(self, instance, slug):
+ """
+ Generates unique slug by adding a number to given value until no model
+ is found with such slug. If unique_with (a tuple of field names) was
+ specified for the field, all these fields are included together
+ in the query when looking for a "rival" model instance.
+ """
+ def _get_lookups(instance):
+ "Returns a dict'able tuple of lookups to ensure slug uniqueness"
+ for field_name in self.unique_with:
+ field = instance._meta.get_field(field_name)
+ value = getattr(instance, field_name)
+ if not value:
+ raise ValueError, 'Could not check uniqueness of %s.%s with'\
+ ' respect to %s.%s because the latter is empty. Please'\
+ ' ensure that "%s" is declared *after* all fields it'\
+ ' depends on (i.e. "%s"), and that they are not blank.'\
+ % (instance._meta.object_name, self.name,
+ instance._meta.object_name, field_name,
+ self.name, '", "'.join(self.unique_with))
+ if isinstance(field, DateField): # DateTimeField is a DateField subclass
+ for part in 'year', 'month', 'day':
+ lookup = '%s__%s' % (field_name, part)
+ yield (lookup, getattr(value, part))
+ else:
+ yield (field_name, value)
+ lookups = tuple(_get_lookups(instance))
+ model = instance.__class__
+ field_name = self.name
+ index = 1
+ orig_slug = slug
+ # keep changing the slug until it is unique
+ while True:
+ try:
+ # raise model.DoesNotExist if current slug is unique
+ rival = model.objects.get(**dict(lookups + ((self.name, slug),) ))
+ # not unique, but maybe the "rival" is the instance itself?
+ if rival.id == instance.id:
+ raise model.DoesNotExist
+ # the slug is not unique; change once more
+ index += 1
+ slug = '%s-%d' % (orig_slug, index)
+ except model.DoesNotExist:
+ # slug is unique, no model uses it
+ return slug
diff --git a/lib/autoslug/settings.py b/lib/autoslug/settings.py
new file mode 100644
index 0000000..fd8c6af
--- /dev/null
+++ b/lib/autoslug/settings.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2008â??2009 Andy Mikhailenko
+#
+# This file is part of django-autoslug.
+#
+# django-autoslug is free software under terms of the GNU Lesser
+# General Public License version 3 (LGPLv3) as published by the Free
+# Software Foundation. See the file README for copying conditions.
+#
+
+from django.conf import settings
+
+""" the AUTOSLUG_SLUGIFY_FUNCTION setting allows to define
+a custom slugifying function. Value can be a string or a callable.
+Default value is 'django.template.defaultfilters.slugify'.
+"""
+
+# use custom slugifying function if any
+slugify = getattr(settings, 'AUTOSLUG_SLUGIFY_FUNCTION', None)
+
+if not slugify:
+ try:
+ # more i18n-friendly slugify function (supports Russian transliteration)
+ from pytils.translit import slugify
+ except ImportError:
+ # fall back to Django's default one
+ slugify = 'django.template.defaultfilters.slugify'
+
+# find callable by string
+if isinstance(slugify, str):
+ from django.core.urlresolvers import get_callable
+ slugify = get_callable(slugify)
diff --git a/notes/models.py b/notes/models.py
index 1d496e9..44d747a 100644
--- a/notes/models.py
+++ b/notes/models.py
@@ -19,6 +19,8 @@ from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
+from autoslug.fields import AutoSlugField
+
class Note(models.Model):
NOTE_PERMISSIONS = (
(0, 'Private'), (1, 'Public'),
@@ -33,7 +35,8 @@ class Note(models.Model):
user_modified = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=128, blank=True)
- slug = models.SlugField(blank=True)
+ slug = AutoSlugField(unique_with='author', populate_from='title',
+ editable=True)
content = models.TextField(blank=True)
content_version = models.CharField(max_length=10, blank=True)
diff --git a/settings.py b/settings.py
index e0a9be7..9afbaf7 100644
--- a/settings.py
+++ b/settings.py
@@ -98,6 +98,7 @@ INSTALLED_APPS = (
'django_evolution',
'reversion',
'gravatar',
+ 'autoslug',
# Local apps
'notes',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]