django_url_alias 0.3.0
Allow Django URLS to be completely rewritten (alias names for system URLs)

================
django_url_alias
================

About
-----

django_url_alias allows you to completely rewrite certain URLs, if you need to. This is done for incoming URL handling
(URL resolving) and outgoing URLs (via `{% url … %}` or `resolve`). URL rewrites are modular, so you may have static
rewrites (like a dict in settings) and database based rewrites. You may even mix both types by having multiple URL
rewriters.

How it works
------------

django_url_alias provides a special ROOT_URLCONF which intercepts URL resolving on the lowest level. This way it can
rewrite all incoming URLs, so other views will be resolved.

For outgoing URLs (to clarify: "outgoing" does only mean URLs generated by the system, these URLs still may be
internal) The process is a little bit more complex. django_url_alias provides a special `{% url %}` template tag and
its own version of `reverse`. If you use these, everything will just work fine. Sadly there is no sane way to just
replace Django's default behaviour.

Usecase
-------

Although Django's URL handling is really great there may be scanarios it just cannot handle. The flatpage app is one
of the examples URL handling get weird if you need a catchall pattern. As this still works for flatpages you run
into trouble when more then one app needs a similar mechanism (catchall pattern), as only the first one will be handled
correctly (URL matches, further urlconf entries will be ignored).

Other frameworks / CMS / … often use a completely different way to accomplish great URL structure. Instead of having
easy to read system / internal URLs they just put an abstraction layer above all URL handling. This means your
blog entry (for example) may still have an ugly (internal) URL like "blog/entry/15". Now the new URL layer will
take that ugly URL and allow the user or system to define aliases. An alias could for example rewrite "blog/entry/15"
to just "my-trip-to-paris.html". On input URL handling this needs to be reversed, so the requested URL
"my-trip-to-paris.html" will resolve to "blog/entry/15" again.

django_url_alias allows you to just do exactly this. As it does not provide any predefined rules / mechanisms for
rewriting the URL you are free to use whatever rule you want. In theory you could even reuse the good old `SlugField`
and regular expressions. Of course you may use a DB based mapping like so many systems use, too. Below you will find
an example for flatpages, without the need for an catchall pattern.

Usage
-----

Installation
~~~~~~~~~~~~

#. Get django_url_alias into your python `sys.path` (`pip install django_url_alias`)
#. Replace `settings.ROOT_URLCONF` with `"django_url_alias.urls"`
#. Put your old root urlconf into `settings.URL_ALIAS_ROOT_URLCONF`
#. Define your URL rewriting modules using `settings.URL_ALIAS_MODULES`
   (see https://github.com/ddanier/django_url_alias/blob/master/example/example/url_aliases.py
   and example below)
#. Put `{% load url_alias %}` into your templates, so `{% url %}` gets replaced
#. Use `django_url_alias.resolver.reverse` in your Python code

About URL_ALIAS_MODULES
~~~~~~~~~~~~~~~~~~~~~~~

URL_ALIAS_MODULES is just a list of simple classes to rewrite the URLs Django handles or generates.

.. code:: python

    URL_ALIAS_MODULES = (
        'path.to.module.aliases.ExampleAliasModule',
    )

Each class will be instantiated without any parameters and should provide two methods:

* resolve(self, path): Change incoming URLs
* reverse(self, path): Change outgoing URLs

A simple example may look like this::

    class ExampleURLAliasModule(object):
        def resolve(self, path):
            if path == 'foo/':
                return 'bar/'

        def reverse(self, path):
            if path == 'bar/':
                return 'foo/'

Both methods must return nothing (/None) when no rewrite is done. The defined classes will be called in order for
incoming and outgoing URLs. The first module which returns a replacement URL will shortcut further processing, thus
will define the final URL.

Flatpage example
----------------

The flatpages app is in core, so it just gives an nice example. We don't want to use the catchall pattern any more.
For this example to work you will need to go through installation first.

First of all we need to define our internal URL structure. This will not be visible to your visitors, although
the internal URLs are still accessible.

urls.py:: python

    urlpatterns = patterns('',
        url(r'^flatpage/(?P<pk>[0-9]+)/$', 'example.views.flatpage', name='flatpage'),
    )

Then the rewrite needs to be done, so `flatpage.url` will be used to present the external URL structure, while
the internal ID-based URL gets used internally.

URL Rewrite module:: python

    from django.contrib.flatpages.models import FlatPage
    import re

    class FlatpagesURLAliasModule(object):
        FLATPAGE_RE = re.compile('^/flatpage/(?P<pk>[0-9]+)/$')

        def resolve(self, path):
            path = '/' + path  # we need a trailing slash for flatpages
            try:
                flatpage = FlatPage.objects.get(url=path)
                return '/flatpage/%d/' % flatpage.pk
            except FlatPage.DoesNotExist:
                pass  # just return nothing

        def reverse(self, path):
            match = self.FLATPAGE_RE.match(path)
            if match:
                try:
                    flatpage = FlatPage.objects.get(pk=match.group('pk'))
                    return flatpage.url
                except FlatPage.DoesNotExist:
                    pass  # just return nothing

*Note:* This is just an example. In an production envirionment you would need to a) check the sites relation and more
importantly b) use some caching. The above code is very inefficient and should NEVER BE USED IN A PRODUCTION
ENVIRIONMENT.


Advantages of implementation
----------------------------

* `request` objects stay clean. You could rewrite URLs using middleware classes which fiddle with `request.path_info`,
  but this way you will loose information (or even worse: rewrite information).
* Least obtrusive way of implementation.

Notes
-----

* You should define canonical tags, so internal URLs will not get indexed by search engines.