I've been using django for almost a year and I was always frustrated
by its cumbersome urlpatterns system. While it is really flexible, it
doesn't provide any shortcuts for widely-used url and views naming
schemes.

Let me show in examples what I mean.

As everyone, I started with the tutorial and worked with the following
system:

=== urls.py ===
urlpatterns = patterns('',
        (r'^$', 'apps.app1.views.index'),
        (r'^news/$', 'apps.app1.views.news'),
        (r'^members/profile/$', 'apps.app1.views.members.profile'),
        (r'^members/secure/$', 'apps.app1.views.members.secure'),
        (r'^app2/page/$', 'apps.app2.views.page'), # some other app
        ...
)

It becomes quite a large and hardy maintainable list when the number
of applications and views grows. I do believe in DRY principle and I
didn't like much that I had to repeat the URL bases and parent module
names again and again. So I kept reading the docs and involved the
system to the following:

=== urls.py ===
urlpatterns = patterns('',
        (r'^', include('apps.app1.urls')),
        (r'^app2', include('apps.app2.urls')),
)

=== apps/app1/urls/__init__.py ===
urlpatterns = patterns('apps.app1.views',
        (r'^index/$', 'index'),
        (r'^news/$', 'news'),
        (r'^members', include('apps.app1.urls.members'),
)

=== apps/app1/urls/members.py ===
urlpatterns = patterns('apps.app1.views.members',
        (r'^profile/$', 'profile'),
        (r'^secure/$', 'secure'),
)

=== apps/app1/views/__init__.py ===
def index(request):
        ...
def news(request):
        ...

=== apps/app1/views/members.py ===
def profile(request):
        ....
def secure(request):
        ...

(I skipped app2.* files for easier reading)

While this system had less redundancy and easier to maintain (the DRY
benefits), it suffered from another DRY problem - there were two
packages with the same structure (apps.app1.urls.* and
apps.app1.views.*), with the highly related content. When I was
renaming a view, I had to browse through two package structures and
change the things twice. Still frustrating, you see.

So what I decided, why do we have to keep urlpatterns apart of the
views? Why wouldn't I put them in the same files? The architecture
became:

=== urls.py ===
urlpatterns = patterns('',
        (r'^', include('apps.app1.views')), # note views, not urls
        (r'^app2', include('apps.app2.views')),
)

=== apps/app1/views/__init__.py ===
urlpatterns = patterns(__name__, # sic!
        (r'^index/$', 'index'),
        (r'^news/$', 'news'),
        (r'^members', include('apps.app1.views.members'),
)

def index(request):
        ...
def news(request):
        ...

=== apps/app1/views/members.py ===
urlpatterns = patterns(__name__,
        (r'^profile/$', 'profile'),
        (r'^secure/$', 'secure'),
)

def profile(request):
        ....
def secure(request):
        ...

This was a good change. I had 50% less files, and I put the related
info within the same modules. Also note the usage of __name__, which
increased the DRY factor a bit more :-)

Yet I wasn't fully satisfied. Whenever I renamed a view I had to patch
the urlpatterns as well.
I also remembered an old inconvenience I always felt with views
modules. If I place views functions in a views module, and place like
"normal" helper functions there as well, they got mixed. By looking at
the code it is sometimes hard to understand which function is a view,
and which function is a helper.
So what I created is the @url decorator which solved the both
problems:

=== urls.py ===
urlpatterns = patterns('',
        (r'^', include('apps.app1.views')),
        (r'^app2', include('apps.app2.views')),
)

=== apps/app1/views/__init__.py ===
@url(r'^index/$')
def index(request):
        ...

@url(r'^news/$')
def news(request):
        ...

urlpatterns += include_urlpatterns(r'^members',
'apps.app1.views.members')

=== apps/app1/views/members.py ===
@url(r'^profile/$)
def profile(request):
        ....

@url(r'^secure/$)
def secure(request):
        ...

@url(r'^path1/$', '^path2/$') # you can specify several patterns
def multipath_view(request):
        ...

def helper(): # easily distinguishable - no @url!
        ...

Summarizing, the benefits are:
- no more creating and supporting urlpattern maps (less files, less
code, more DRY)
- have the url associated with a view in-place
- easily see if a function is a view
- fully compatible with other chained decorators

Implementation problems, or possible improvements:
- it is hackish
- the speed isn't constant time, it is O(N) where N is the number of
currently loaded modules
- I would like to make it support the no-arguments syntax:

@url()
def profile(request):
        ....

and have the decorator automatically pull the function name as the url
pattern, but that doesn't seem possible if there are further
decorators (like @user_passes_test or @render_to)

The source code can be found at http://www.djangosnippets.org/snippets/395/


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To post to this group, send email to django-users@googlegroups.com
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-users?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to