Wandering Thoughts archives

2016-01-28

Modern Django makes me repeat myself in the name of something

One of the things that basically all web frameworks do is URL routing, where they let you specify how various different URL patterns are handled by various different functions, classes, or whatever. Once you have URL routing, you inevitably wind up wanting reverse URL routing: given a handler function or some abstract name for it (and perhaps some parameters), the framework will generate the actual URL that refers to it. This avoids forcing you to hard-code URLs into both code (for eg HTTP redirections) and templates (for links and so on), which is bad (and annoying) for all sorts of reasons. As a good framework, Django of course has both powerful URL routing and powerful reverse URL generation.

Our Django web app is now about five years old and has not been substantially revised since it was initially written for Django 1.2. Back in Django 1.2, you set up URL routing and reversed routing something like this:

urlpatterns = patterns('',
   (r'^request/$', 'accounts.views.makerequest'),
   [...]

And in templates, you got reverse routing as:

<a href="{% url "accounts.views.makerequest" %}"> ... </a>

Here accounts.views.makerequest is the actual function that handles this particular view. This is close to the minimum amount of information that you have to give the framework, since the framework has to know the URL pattern and what function (or class or etc) handles it.

Starting in Django 1.8 or so, Django changed its mind about how URL reversing should work. The modern Django approach is to require that all of your URL patterns be specifically named, with no default. This means that you now write something like this:

urlpatterns = [
   url(r'^request/$', accounts.views.makerequest, name="makerequest"),
   [...]

And in templates and so on you now use the explicit name, possibly with various levels of namespaces.

Now, Django has a few decent reasons for wanting support for explicit reverse names on URL patterns; you can have different URL patterns map to the same handler function, for example, and you may care about which one your template reverses to. But in the process of supporting this it has thrown the baby out with the bathwater, because it has made there be no default value for the name. If you want to be able to do reverse URL mapping for a pattern, you must explicitly name it, coming up with a name and adding an extra parameter.

Our Django web app has 38 different URL patterns, and I believe all of them get reversed at some point or another. All of them have unique handler functions, which means that all of them have unique names that are perfectly good. But modern Django has no defaults, so now Django wants me to add a completely repetitive name= parameter to every one of them. In short, modern Django wants me to repeat myself.

(It also wants me to revise all of the templates and other code to use the new names. Thanks, Django.)

As you may guess, I am not too happy about this. I am in fact considering ugly hacks simply because I am that irritated.

(The obvious ugly hack is to make a frontend function for url() that generates the name by looking up the __name__ of the handler function.)

PS: Django 1.8 technically still supports the old approach, but it's now officially deprecated and will be removed in Django 1.10.

DjangoUrlReversingRepeatingMyself written at 00:12:58; Add Comment

2016-01-25

A Python wish: an easy, widely supported way to turn a path into a module

Years ago I wrote a grumpy entry about Django 1.4's restructured directory layout and mentioned that I was not reorganizing our Django web app to match. In the time since then, it has become completely obvious that grimly sticking to my guns here is not a viable answer over the long term; sooner or later, ideally sooner, I need to restructure the app into what is now the proper Django directory layout.

One of the reasons that I objected to this (and still do) is the problem of how you make a directory into a module; simply adding the parent directory to $PYTHONPATH has several limitations. Which is where my wish comes in.

What I wish for is a simple and widely supported way to say 'directory /some/thing/here/fred-1 is the module fred'. This should be supported on the Python command line, in things like mod_wsgi, and in Python code itself (so you could write code that programmatically added modules this way, similar to how you can extend sys.path in code). The named module is not imported immediately, but if later code does 'import fred' (or any of its variants) it will be loaded from /some/thing/here/fred-1 (assuming that there is no other fred module found earlier on the import path). All of the usual things work from there, such as importing submodules ('import fred.bob') and so on. The fred-1 directory would have a __init__.py and so on as normal.

(Note that it is not necessary for the final directory name to be the same as the module name. Here we have a directory being fred-1 but the module is fred.)

Given PEP 302, I think it's probably possible to implement this in Python code. However, both PEP 302 and the entire current Python import process make my head hurt so I'm not sure (and there are probably differences between Python 2 and Python 3 here).

(I wrote some notes to myself about Python packaging a while back, which is partly relevant to this quest. I don't think .egg and .zip files let me do what I want here, even if I was willing to pack things up in .zips, since I believe their filenames are bound to the package/module name.)

PathIntoModuleWish written at 01:38:42; Add Comment


Page tools: See As Normal.
Search:
Login: Password:
Atom Syndication: Recent Pages, Recent Comments.

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.