Skip to main content

Rich URLs for Django models.

Project description

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:

  • /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!

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.

Resources

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django-urlmodel-0.12.tar.gz (6.7 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page