reverse() chicken and egg problem

I wound up in a chicken and egg situation today using [Django’s syndication
framework][syndication] and the [`reverse`][reverse] helper. The problem was
that immediately after starting the development server, Django would throw a
`NoReverseMatch` exception on the first client visit, followed by `AttributeError`
on all subsequent visits.

It all started so innocently… I had wanted a set of urls for my application
like this:

* [http://example.com/a/]() # List view of arrivals
* [http://example.com/d/]() # List view of departures
* [http://example.com/a/feed/]() # Syndication feed for arrivals
* [http://example.com/d/feed/]() # Syndication feed for departures

So I put the following in the application’s `urls.py`:

# myapp/urls.py
from django.conf.urls.defaults import *
from views import arrivals_list, departures_list
from feeds import LatestArrivals, LatestDepartures

feed_dict = {‘a’: LatestArrivals, ‘d’: LatestDepartures}

urlpatterns = patterns(”,
(r’^a/$’, arrivals_list, {}, ‘arrivals’),
(r’^d/$’, departures_list, {}, ‘departures’),
(r’^(?P[ad])/feed/$’, ‘django.contrib.syndication.views.feed’, {‘feed_dict’:feed_dict}),
)

That covers my URL wishes, and because I have named the URL patterns I
can use that name in templates with the [`{% url %} template tag`][urltag]
and in Python code using the `reverse` helper.

So naturally the feed classes in `feeds.py` look like this:

# myapp/feeds.py
from django.contrib.syndication.feeds import Feed
from django.core.urlresolvers import reverse
from django.utils.feedgenerator import Atom1Feed
from models import Tx

class LatestArrivals(Feed):
“””Produces an Atom feed of recent arrival tickets.”””
feed_type = Atom1Feed
title = ‘Arrivals’
link = reverse(‘arrivals’)
subtitle = ‘Most recent arrivals’

def items(self):
return Tx.objects.arrivals()[:10]

class LatestDepartures(Feed):
“””Produces an Atom feed of recent departure tickets.”””
feed_type = Atom1Feed
title = ‘Departures’
link = reverse(‘departures’)
subtitle = ‘Most recent departures’

def items(self):
return Tx.objects.departures()[:10]

Note I used `reverse` on the link attribute of each class so that I can
define the URL in one place, the `urls.py` module, and a change there will
be reflected in the feed’s link too.

But this doesn’t work! When Django imports my `urls.py` module, it imports
`LatestDepartures` and `LatestArrivals`, and they in turn use `reverse` to
find the named URL patterns – except those names aren’t defined until after
`urlpatterns` has been defined in `urls.py` *so Django throws an exception
and never imports my `urls.py` module*.

You could work around this either by defining your syndication feeds in an
entirely different `urls.py` module. But you can also split up `urlpatterns`
within the same module and import the feed classes after their named URL
patterns have been defined.

Here’s the working `urls.py` module:

from django.conf.urls.defaults import *
from views import arrivals_list, departures_list

urlpatterns = patterns(”,
(r’^a/$’, arrivals_list, {}, ‘arrivals’),
(r’^d/$’, departures_list, {}, ‘departures’),
)

from feeds import LatestArrivals, LatestDepartures
feed_dict = {‘a’: LatestArrivals, ‘d’: LatestDepartures}

urlpatterns += patterns(”,
(r’^(?P[ad])/feed/$’, ‘django.contrib.syndication.views.feed’, {‘feed_dict’:feed_dict}),
)

[syndication]: http://docs.djangoproject.com/en/dev/ref/contrib/syndication/
[reverse]: http://docs.djangoproject.com/en/dev/topics/http/urls/#reverse
[urltag]: http://docs.djangoproject.com/en/dev/ref/templates/builtins/#url

Leave a Reply

Your email address will not be published. Required fields are marked *