reverse() chicken and egg problem
I wound up in a chicken and egg situation today using Django’s syndication
framework and the 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<url>[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
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<url>[ad])/feed/$', 'django.contrib.syndication.views.feed', {'feed_dict':feed_dict}),
)