django-urlmodel 0.12
Rich URLs for Django models.
License: MIT

========
urlmodel
========

Creating rich and dry URLs for a Django model.

------------
Introduction
------------

    **Disclaimer.** Until release of version 1.0, API could significantly change.

As I started using class-based views for Django, I understood what the `get_absolute_url` method was about. Then I encountered the problem of using URLs that required more than once piece of information from a model instance, let's say, not a single primary key or slug. That's a bit abstract, take:

- ``/regions/cat-cat-province/towns/mieaou/``

This identifies Mieaou town within Cat-Cat Province (there's another Mieaou town within Copy-Cat Province). Now let's say the usual list of things you'll do with a model is listing, reading detail, creating, updating, deleting (roughly, CRUD_). Roughly, you'll have this URLs:

.. _CRUD: http://en.wikipedia.org/wiki/Create,_read,_update_and_delete

- ``/regions/cat-cat-province/towns/list``
- ``/regions/cat-cat-province/towns/create``
- ``/regions/cat-cat-province/towns/mieaou/edit``
- ``/regions/cat-cat-province/towns/mieaou/delete``

An important part to notice is that we're using *more than one piece of information per instance*, which makes things a bit more messy than just a pk. Why not just use a pk? First, I *think* there are security implications. Second, nobody wants to remember a pk. Third, programmers respect well-formed URLs and look down to senseless sequences of numbers with unfathomable disdain.

What if you provide a link to the edit and delete sections? You'll probably go for::

    # urls.py

    urlpatterns = patterns('',
        # ...
        url(
            '^regions/(?P<region_slug>[\w-]+)/towns/(?P<town_slug>[\w-]+)/edit/?$',
            name='town-update'
        ),
        # ...
    )

With something in your template like::

    # html
    
    <a href="{% url 'town-update' region_slug=town.region.name_slug town_slug=town.name_slug %}">
      Update this town
    </a>

..

    Note: I mostly use "update" for internal stuff for convention and readability ("update" has the length as "create" and "delete" and "detail" and "search"), but I use "edit" for usability when it'll be read by the end-user.

The latter one is not very DRY_. So I thought I could start writing a ``get_update_url`` method, and so on. Then every model class definition would have a set of ``get_detail_url``, ``get_update_url``, ``get_delete_url`` methods, plus ``get_create_url`` and ``get_list_url`` methods (in the original project, it was "search" instead of "list"). Now, given that my URLs have roughly the same naming format for every model (``town-list``, ``town-create``, ``town-detail``, ``town-update``, ``town-delete``), that is not very DRY either!

.. _DRY: https://docs.djangoproject.com/en/dev/misc/design-philosophies/#don-t-repeat-yourself-dry

I decided to write a smart and flexible URL system so that the next time I would need a list-create-detail-update-delete set of URLs, things would be as easy as adding a mixin to the inheritance tree of the model.

------------
Installation
------------

I know, my requirements suck:

- Python 3
- Django 1.6

I'm not sure if this app will not work on previous versions of Django, but I think I will not with Python 2.

To install version 0.1::

    pip install django-urlmodel

To install the latest version, run the command::

    pip install git+git://github.com/jleeothon/urlmodel.git

----------
How to use
----------

Using the default CRUD urls is as easy as extending funcionality as a mixin::

    # models.py

    from django.db import models

    class Town(CrudUrlModelMixin, models.Model):
        pass
        
    # or, if you're only going to extend the urlmodel functionality...

    class Town(CrudUrlModel):
        pass

The former example will create a set of five methods (class-level: list, create; instance-level: detail, update, delete) that expect an URL based on the model name and the name of the "action". Also, these expect an URL keyword argument called "pk".

In order to use a slug field, you should override ``slug_kwarg_name`` to match the URL kwarg::

    # urls.py

    urlpatterns = patterns('',
        # ...
        url(
            '^digimons/(?P<name>[\w-]+)/?$',
            name='digimon-detail'
        ),
        # ...
    )

and ``slug_field_name``, to match the model slug::

    # models.py

    class Digimon(UrlModelMixin, models.Model):
        name = CharField(max_length=100)
        sluggified_name = SlugField(max_length=100)
        
        slug_kwarg_name = 'name'
        slug_field_name = 'sluggified_name'

..

    Try out their lazy equivalents, ``LazyCrudUrlModelMixin`` and ``LazyCrudUrlModel``!

..

    **Note.** CrudUrlModel provides implementation for ``get_absolute_url`` too, and defaults to ``get_detail_url``.

--------------
Advanced stuff
--------------

If the default "CRUD" system (here: list, create, detail, update, delete) do not work for you, you can try out ``UrlModel`` and ``UrlModelMixin`` (plus their lazy counterparts).

~~~~~~~~~~~
Action URLs
~~~~~~~~~~~

These are URLs based on "action names", such as "detail", "create", etc. For convention, ease and readability, you should try to stick to these methods, e.g.::

    class TownPerson(UrlModelMixin, Model):

        def get_defenestrate_url(self):
            # expects townperson-defenestrate to exist.
            return self.get_instance_action_url("defenestrate")

        @classmethod
        def get_last_defenestrated(cls):
            # expects townperson-last-defenestrated to exist.
            return cls.get_class_action_url("last-defenestrated")

~~~~~~~~~~~~~~~~~~~~~~~~~
Extra information for URL
~~~~~~~~~~~~~~~~~~~~~~~~~

If you need to rely on more information than a single pk or slug, use ``*args`` and ``**kwargs`` to pass on this information to the ``urlresolvers.reverse`` method::

    class Town(CrudUrlModelMixin, Model):
        
        def get_detail_url(self):
            return super().get_detail_url(region_slug=self.region.sluggified_name)

The above example does also work similarly for the simpler ``UrlModelMixin`` class.

Be careful!,

    instance url methods (``get_instance_url`` and ``get_instance_action_url``) always pass the pk or slug argument to ``urlresolvers.reverse``.

~~~~~~~~~~~~~~~~
Custom URL names
~~~~~~~~~~~~~~~~

If you want to provide a custom URL instead of an automatic ``modelname-action``, use ``@classmethod get_class_url(cls, url_name, *args, **kwargs)`` and ``get_instance_url(self, url_name, *args, **kwargs)``.

~~~~~~~~~~~~~~~~~~~~~~
Format for action URLs
~~~~~~~~~~~~~~~~~~~~~~

**Note:** this section is subject to change in upcoming versions.

Action URLs are formatted ``modelname-action`` by default. To change this format, override the ``action_url_formatter`` object or provide a ``format_action`` class method. If you provide the latter (method), the former (object) will be ignored::

    action_url_formatter = lambda model, action: '%s-%s' % (model, action)
    # this signature will override action_url_formatter
    # @classmethod
    # def format_action(cls, modelname, action):
    #     pass

---------
Proposals
---------

- Provide support for Python 2, probably through six_.
- Submit this project to PyPI.

.. _six: https://pypi.python.org/pypi/six/1.7.3

---------
Resources
---------

- `url template tag`_
- `Reversing URLs`_
- `Slug fields`_
- `Class-based views`_

.. _`url template tag`: https://docs.djangoproject.com/en/dev/ref/templates/builtins/#url
.. _`Reversing URLs`: https://docs.djangoproject.com/en/dev/ref/urlresolvers/#reverse
.. _`Slug fields`: https://docs.djangoproject.com/en/dev/ref/models/fields/#slugfield
.. _`Class-based views`: https://docs.djangoproject.com/en/dev/topics/class-based-views/