Commits

Andy Mikhailenko committed 7a8b9c4 Merge

Merged. Improved callable populate_from (added support for dynamic model attributes). Added tests.

  • Participants
  • Parent commits d0a4179, 39d7b19
  • Tags 1.2.0

Comments (0)

Files changed (4)

 * Steve Steiner
 * Blake Imsland
 * Ollie Rutherfurd
+* Mikhail Korobov
+* Remco Wendt
 * Your Name Here  ;)

File autoslug/__init__.py

 __author__  = 'Andy Mikhailenko'
 __license__ = 'GNU Lesser General Public License (GPL), Version 3'
 __url__     = 'http://bitbucket.org/neithere/django-autoslug/'
-__version__ = '1.1.3'
+__version__ = '1.2.0'

File autoslug/fields.py

     (`unique_with`) or globally (`unique`) and adding a number to the slug to make
     it unique.
 
-    :param populate_from: string or callable: if string is given it is considered as 
-        a name of attribute from which to fill the slug. If callable is given it should 
-        accept `instance` parameter and return a value to fill the slug with.
+    :param populate_from: string or callable: if string is given, it is considered
+        as the name of attribute from which to fill the slug. If callable is given,
+        it should accept `instance` parameter and return a value to fill the slug with.
     :param unique: boolean: ensure total slug uniqueness (unless more precise `unique_with`
         is defined).
     :param unique_with: string or tuple of strings: name or names of attributes
         # globally unique, silently fix on conflict ("foo" --> "foo-1".."foo-n")
         slug = AutoSlugField(unique=True)
 
-        # autoslugify value from title attr; default editable to False
+        # autoslugify value from attribute named "title"; editable defaults to False
         slug = AutoSlugField(populate_from='title')
 
         # same as above but force editable=True
         # minimum date granularity is shifted from day to month
         slug = AutoSlugField(populate_from='title', unique_with='pub_date__month')
 
-        # autoslugify value from models's user name using callable 
-        # ex. usage: user profile models
+        # autoslugify value from a dynamic attribute using callable
+        # (ex. usage: user profile models)
         slug = AutoSlugField(populate_from=lambda instance: instance.user.get_full_name())
     """
     def __init__(self, *args, **kwargs):
         # get currently entered slug
         value = self.value_from_object(instance)
 
-        slug = None
+        # autopopulate (unless the field is editable and has some value)
+        if self.populate_from and not value: # and not self.editable:
+            value = self._get_prepopulated_value(instance)
 
-        # autopopulate (unless the field is editable and has some value)
-        if value:
-            slug = slugify(value)
-        elif self.populate_from: # and not self.editable:
-            if callable(self.populate_from):
-                slug = slugify(self.populate_from(instance))
-            else:
-                slug = slugify(getattr(instance, self.populate_from))
-            if __debug__ and not slug:
-                print 'Failed to populate slug %s.%s from an empty field %s' % \
+            if __debug__ and not value:
+                print 'Failed to populate slug %s.%s from %s' % \
                     (instance._meta.object_name, self.name, self.populate_from)
 
+        slug = slugify(value)
+
         if not slug:
             # no incoming value,  use model name
             slug = instance._meta.module_name
         return slug
         #return super(AutoSlugField, self).pre_save(instance, add)
 
+    def _get_prepopulated_value(self, instance):
+        """Returns preliminary value based on `populate_from`."""
+        if callable(self.populate_from):
+            # AutoSlugField(populate_from=lambda instance: ...)
+            return self.populate_from(instance)
+        else:
+            # AutoSlugField(populate_from='foo')
+            attr = getattr(instance, self.populate_from)
+            return callable(attr) and attr() or attr
+
     def _generate_unique_slug(self, instance, slug):
         """
         Generates unique slug by adding a number to given value until no model
                 # 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:
+                if rival == instance:
                     raise model.DoesNotExist
                 # the slug is not unique; change once more
                 index += 1

File autoslug/tests.py

 #  Software Foundation. See the file README for copying conditions.
 #
 
+# TODO: test cases for dates and unique_with
+
 
 from django.db.models import Model, CharField
 from autoslug.fields import AutoSlugField
 
 
-class Foo(Model):
+class SimpleModel(Model):
+    """
+    >>> a = SimpleModel(name='test')
+    >>> a.save()
+    >>> a.slug
+    'simplemodel'
+    """
+    name = CharField(max_length=200)
+    slug = AutoSlugField()
+
+
+class ModelWithUniqueSlug(Model):
+    """
+    >>> greeting = 'Hello world!'
+    >>> a = ModelWithUniqueSlug(name=greeting)
+    >>> a.save()
+    >>> a.slug
+    'hello-world'
+    >>> b = ModelWithUniqueSlug(name=greeting)
+    >>> b.save()
+    >>> b.slug
+    'hello-world-2'
+    """
     name = CharField(max_length=200)
     slug = AutoSlugField(populate_from='name', unique=True)
 
-__doc__ = """
->>> long_name = 'x' * 250
->>> foo = Foo(name=long_name)
->>> foo.save()
->>> len(foo.slug)
-50
->>> bar = Foo(name=long_name)
->>> bar.save()
->>> [len(x.slug) for x in Foo.objects.all()]
-[50, 50]
-"""
+
+class ModelWithLongName(Model):
+    """
+    >>> long_name = 'x' * 250
+    >>> a = ModelWithLongName(name=long_name)
+    >>> a.save()
+    >>> len(a.slug)    # original slug is cropped by field length
+    50
+    >>> b = ModelWithLongName(name=long_name)
+    >>> b.save()
+    >>> b.slug[-3:]    # uniqueness is forced
+    'x-2'
+    >>> len(b.slug)    # slug is cropped
+    50
+    """
+    name = CharField(max_length=200)
+    slug = AutoSlugField(populate_from='name', unique=True)
+
+
+class ModelWithCallable(Model):
+    """
+    >>> a = ModelWithCallable.objects.create(name='larch')
+    >>> a.slug
+    'the-larch'
+    """
+    name = CharField(max_length=200)
+    slug = AutoSlugField(populate_from=lambda instance: u'the %s' % instance.name)
+
+
+class ModelWithCallableAttr(Model):
+    """
+    >>> a = ModelWithCallableAttr.objects.create(name='albatross')
+    >>> a.slug
+    'spam-albatross-and-spam'
+    """
+    name = CharField(max_length=200)
+    slug = AutoSlugField(populate_from='get_name')
+
+    def get_name(self):
+        return u'spam, %s and spam' % self.name
+
+
+class ModelWithCustomPrimaryKey(Model):
+    """
+    # just check if models are created without exceptions
+    >>> a = ModelWithCustomPrimaryKey.objects.create(custom_primary_key='a',
+    ...                                              name='name used in slug')
+    >>> b = ModelWithCustomPrimaryKey.objects.create(custom_primary_key='b',
+    ...                                              name='name used in slug')
+    >>> a.slug
+    'name-used-in-slug'
+    """
+    custom_primary_key = CharField(primary_key=True, max_length=1)
+    name = CharField(max_length=200)
+    slug = AutoSlugField(populate_from='name', unique=True)