Andy Mikhailenko committed 94c27c4

Added complete Sphinx documentation. Added exception, improved wording for other exceptions.

Comments (0)

Files changed (9)



 __author__  = 'Andy Mikhailenko'
 __license__ = 'GNU Lesser General Public License (GPL), Version 3'
 __url__     = ''
-__version__ = '1.1.0'
+__version__ = '1.1.1'


 #  Software Foundation. See the file README for copying conditions.
+# python
+from warnings import warn
 # django
-from django.db.models.fields import DateField, SlugField
+from django.db.models.fields import FieldDoesNotExist, DateField, SlugField
 # app
 from autoslug.settings import slugify
 class AutoSlugField(SlugField):
-    AutoSlugField is a slug field that can automatically do the following on save:
+    AutoSlugField is an extended SlugField able to automatically resolve name
+    clashes.
+    AutoSlugField can also perform the following tasks on save:
     - populate itself from another field (using `populate_from`),
-    - use custom slugify() function (can be defined in settings), and
+    - use custom `slugify` function (can be defined in :doc:`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.
+    (`unique_with`) or globally (`unique`) and adding a number to the slug to make
+    it unique.
-    .. warning:: always declare attributes with AutoSlugField *after* attributes
-        from which you wish to 'populate_from' or check 'unique_with' because
-        autosaved ates and other such fields must be already processed before
-        the checking occurs.
+    :param populate_from: string: name of attribute from which to fill the slug.
+    :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
+        to check for "partial uniqueness", i.e. there will not be two objects
+        with identical slugs if these objects share the same values of given
+        attributes. For instance, ``unique_with='pub_date'`` tells AutoSlugField
+        to enforce slug uniqueness of all items published on given date. The
+        slug, however, may reappear on another date. If more than one field is
+        given, e.g. ``unique_with=('pub_date', 'author')``, then the same slug may
+        reappear within a day or within some author's articles but never within
+        a day for the same author.
-    Usage examples::
+    .. note:: always place any slug attribute *after* attributes referenced
+        by it (i.e. those from which you wish to `populate_from` or check
+        `unique_with`). The reasoning is that autosaved dates and other such
+        fields must be already processed before using them in the AutoSlugField.
+    Example usage::
+        from django.db import models
+        from autoslug.fields import AutoSlugField
+        class Article(models.Model):
+            '''An article with title, date and slug. The slug is not totally
+            unique but there will be no two articles with the same slug within
+            any month.
+            '''
+            title = models.CharField(max_length=200)
+            pub_date = models.DateField(auto_now_add=True)
+            slug = AutoSlugField(populate_from='title', unique_with='pub_date__month')
+    More options::
         # slugify but allow non-unique slugs
         slug = AutoSlugField()
         # minimum date granularity is shifted from day to month
         slug = AutoSlugField(populate_from='title', unique_with='pub_date__month')
-    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)
         # 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'],)
             slug = slugify(value)
         elif self.populate_from: # and not self.editable:
             slug = slugify(getattr(instance, self.populate_from))
+            if __debug__ and not slug:
+                print 'Failed to populate slug %s.%s from an empty field %s' % \
+                    (instance._meta.object_name,, self.populate_from)
         if not slug:
             # no incoming value,  use model name
         def _get_lookups(instance):
             "Returns a dict'able tuple of lookups to ensure slug uniqueness"
             for _field_name in self.unique_with:
-                field_name, inner = _field_name, None
-                field = instance._meta.get_field(field_name)
                 # `inner` is the 'month' part in 'pub_date__month'.
                 # it *only* applies to dates, otherwise it's just ignored.
-                if '__' in field:
-                    field_name, inner = field.split('__')
+                if '__' in _field_name:
+                    if _field_name.count('__') > 1:
+                        raise ValueError('The `unique_with` constraint in %s.%s'
+                                         ' is set to "%s", but AutoSlugField only'
+                                         ' accepts one level of nesting for dates'
+                                         ' (e.g. "date__month").'
+                                         % (instance._meta.object_name,,
+                                            _field_name))
+                    field_name, inner = _field_name.split('__')
+                else:
+                    field_name, inner = _field_name, None
+                try:
+                    field = instance._meta.get_field(field_name)
+                except FieldDoesNotExist:
+                    raise ValueError('Could not find attribute %s.%s referenced'
+                                     ' by %s.%s (see constraint `unique_with`)'
+                                     % (instance._meta.object_name, field_name,
+                                        instance._meta.object_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,,
-                           instance._meta.object_name, field_name,
-                 , '", "'.join(self.unique_with))
+                    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,,
+                                        instance._meta.object_name, field_name,
+                              , '", "'.join(self.unique_with)))
                 if isinstance(field, DateField):    # DateTimeField is a DateField subclass
                     inner = inner or 'day'
                     parts = 'year', 'month', 'day'
                             lookup = '%s__%s' % (field_name, part)
                             yield (lookup, getattr(value, part))
+                    if inner:
+                        raise ValueError('Wrong value "%s__%s" for constraint'
+                                         ' `unique_with` in %s.%s. Compound field'
+                                         ' names are only accepted for DateField'
+                                         ' attributes (such as "date__month").'
+                                         % (field_name, inner,
+                                            instance._meta.object_name,
                     yield (field_name, value)
         lookups = tuple(_get_lookups(instance))
         model = instance.__class__


 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'.
+__doc__ = """Available settings for django-autoslug:
+* `AUTOSLUG_SLUGIFY_FUNCTION` allows to define a custom slugifying function.
+  The function can be repsesented as string or callable, e.g.::
+      # custom function, path as string:
+      AUTOSLUG_SLUGIFY_FUNCTION = 'some_app.slugify_func'
+      # custom function, callable:
+      AUTOSLUG_SLUGIFY_FUNCTION = some_app.slugify_func
+      # custom function, defined inline:
+      AUTOSLUG_SLUGIFY_FUNCTION = lambda slug: 'can i haz %s?' % slug
+  Default value is 'django.template.defaultfilters.slugify'.
-# use custom slugifying function if any 
+# use custom slugifying function if any
 slugify = getattr(settings, 'AUTOSLUG_SLUGIFY_FUNCTION', None)
 if not slugify:
+# Makefile for Sphinx documentation
+# You can set these variables from the command line.
+SPHINXBUILD   = sphinx-build
+PAPER         =
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html      to make standalone HTML files"
+	@echo "  dirhtml   to make HTML files named index.html in directories"
+	@echo "  pickle    to make pickle files"
+	@echo "  json      to make JSON files"
+	@echo "  htmlhelp  to make HTML files and a HTML help project"
+	@echo "  qthelp    to make HTML files and a qthelp project"
+	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  changes   to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck to check all external links for integrity"
+	@echo "  doctest   to run all doctests embedded in the documentation (if enabled)"
+	-rm -rf _build/*
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html
+	@echo
+	@echo "Build finished. The HTML pages are in _build/html."
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in _build/dirhtml."
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in _build/htmlhelp."
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in _build/qthelp, like this:"
+	@echo "# qcollectiongenerator _build/qthelp/DjangoAutoslug.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile _build/qthelp/DjangoAutoslug.qhc"
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in _build/latex."
+	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+	      "run these through (pdf)latex."
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes
+	@echo
+	@echo "The overview file is in _build/changes."
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in _build/linkcheck/output.txt."
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in _build/doctest/output.txt."
+# -*- coding: utf-8 -*-
+# Django Autoslug documentation build configuration file, created by
+# sphinx-quickstart on Tue Jul 21 22:35:51 2009.
+# This file is execfile()d with the current directory set to its containing dir.
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+import sys, os
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.insert(0, os.path.abspath('..'))
+from django.conf import settings
+import autoslug
+# -- General configuration -----------------------------------------------------
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.coverage']
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+# The suffix of source filenames.
+source_suffix = '.rst'
+# The encoding of source files.
+#source_encoding = 'utf-8'
+# The master toctree document.
+master_doc = 'index'
+# General information about the project.
+project = u'Django Autoslug'
+copyright = u'2009, Andy Mikhailenko'
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+# The short X.Y version.
+version = '.'.join(autoslug.__version__.split('.')[:2])
+# The full version, including alpha/beta/rc tags.
+release = autoslug.__version__
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = ['_build']
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+# -- Options for HTML output ---------------------------------------------------
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+# If false, no module index is generated.
+#html_use_modindex = True
+# If false, no index is generated.
+#html_use_index = True
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'DjangoAutoslugdoc'
+# -- Options for LaTeX output --------------------------------------------------
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'DjangoAutoslug.tex', u'Django Autoslug Documentation',
+   u'Andy Mikhailenko', 'manual'),
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+# If false, no module index is generated.
+#latex_use_modindex = True
+.. automodule:: autoslug.fields
+   :members:
+.. Django Autoslug documentation master file, created by
+   sphinx-quickstart on Tue Jul 21 22:35:51 2009.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+Welcome to Django Autoslug's documentation!
+.. toctree::
+   :maxdepth: 2
+   fields
+   settings
+Indices and tables
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+.. automodule:: autoslug.settings
+   :members:
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.