{% url %} for generic views (proposal)

5 views
Skip to first unread message

Ivan Sagalaev

unread,
Mar 13, 2007, 5:27:48 AM3/13/07
to django-d...@googlegroups.com
Hello!

I was lazily thinking about making {% url %} and "reverse" to work for
generic views ("GV"). The main problem why reversing doesn't work for GV
is that it relies on view's name to be unique while GVs obviously have
the same name for many URLs (that makes them "generic"). Hence a
signature of a GV consists not only of its name but also of some (but
not all) parameters from info_dict.

## A common case

I've failed to invent a good general way of dealing with this and
decided to go for 80 from 80/20 rule. It looks like in most cases people
need reversing for GVs pointing to object pages: "object_list" and
"object_detail". May be also for date based GVs but let's leave them
aside for a while.

So for "object_list" and "object_detail" I propose this syntax:

{% obj_url "appname.ModelName" id %}
{% obj_list_url "appname.ModelName" %}

Both GVs have their own tag that knows the actual name of a GV (like
"django.views.generic.list_detail.object_list"). Though I like it this
way it's not set in stone and may be some parameter would be better
(like {% url "object_detail" "appname.ModelName" %}).

Tags accept a model name as a first argument. This will require a
special version of "reverse" that checks for a view name and for a model
name that it knows how to get from a "queryset" parameter from info_dict.

This looks to me both simple for a template tag and useful for most real
cases.

## More special case

There is one special case that I feel is not that special to not be
addresses. Sometimes you have to URLs that have the same GVs _and_
querysets against the same model:

(r'/objects/$', object_list, {
'queryset': Model.objects.all(),
}),
(r'/objects/active/$', object_list, {
'queryset': Model.objects.filter(active=True),
}),

The distinction by model name won't work here. For this I propose an
optional fourth member to an urlconf pattern: view's given name:

(r'/objects/$', object_list, {
'queryset': Model.objects.all(),
},
'objects'),
(r'/objects/active/$', object_list, {
'queryset': Model.objects.filter(active=True),
},
'active_objects'),

And the tag will accept it as a first parameter:

{% obj_list_url "active_objects" %}

This will require additional configuration but I think it's okay since
it's not required for a common case and is simple enough.

This "view name" will also solve other problems: reversing for the rest
of GVs and may be a permalink decorator that James Bennett was talking
about in django-user recently.

<small>Now I think that this proposal is better sounds like "introduce
view names, use model's name for this in known generic views"</small>

So what do you think about it?

Jacob Kaplan-Moss

unread,
Mar 13, 2007, 9:51:27 AM3/13/07
to django-d...@googlegroups.com
On 3/13/07, Ivan Sagalaev <Man...@softwaremaniacs.org> wrote:
> I was lazily thinking about making {% url %} and "reverse" to work for
> generic views ("GV").

I've been thinking about this a bunch myself. I came up with some
ideas similar to yours, but so far they're just in my head. I'll try
to write 'em up, but in the time being I'd love to see you (or
someone) start hacking on some code!

Jacob

Ivan Sagalaev

unread,
Mar 13, 2007, 10:32:44 AM3/13/07
to django-d...@googlegroups.com
Jacob Kaplan-Moss wrote:
> I've been thinking about this a bunch myself. I came up with some
> ideas similar to yours, but so far they're just in my head. I'll try
> to write 'em up, but in the time being I'd love to see you (or
> someone) start hacking on some code!

Ok!

For the reference: http://code.djangoproject.com/ticket/3717

Florian Apolloner

unread,
Mar 25, 2007, 6:14:39 AM3/25/07
to Django developers

But please make the url tag independent of the current active language
cause the urls are!
Her is an example I came a cross when writing a wrapper for
object_detail to be able to use the url tag:
Url to the detail page is /2007/mar/23/slug
I used the url tag and thanks to my wrapper I was able to use the
wrapper-view as view and gave it the arguments the generic view
needed.
To get the dates I used the date filter which filters the month
according to my language-setting. This is "de-at" in my case and
obviously the url tag didn't came back wth an url cause the result
was /2007/Mär/23/slug!

I don't know whether making the gv_url_tag language independent is a
good idea, but it would solve my problem :)

Thx, Florian

Adrian Holovaty

unread,
Mar 27, 2007, 5:57:05 PM3/27/07
to django-d...@googlegroups.com
On 3/13/07, Ivan Sagalaev <Man...@softwaremaniacs.org> wrote:
> So for "object_list" and "object_detail" I propose this syntax:
>
> {% obj_url "appname.ModelName" id %}
> {% obj_list_url "appname.ModelName" %}
>
> Both GVs have their own tag that knows the actual name of a GV (like
> "django.views.generic.list_detail.object_list"). Though I like it this
> way it's not set in stone and may be some parameter would be better
> (like {% url "object_detail" "appname.ModelName" %}).

I'm a strong -1 on having generic-view-specific permalink functions
and template tags like this. This solution goes after the symptoms
rather than the fundamental problem, which is that the current reverse
URL implementation cannot handle multiple URL patterns for the same
view.

I've done some thinking on this, too, and I think the cleanest way to
solve it would be to introduce optional names for URL patterns.
Something like this:

url(r'^objects/$', some_view, name='object_view'),
url(r'^objects2/$', some_view, name='object_view2'),

This way, you could give an arbitrary name to a particular pattern, so
that you could target it via a reverse lookup. The Routes package does
something similar ("named routes"). Note that it would require URL
patterns to be function calls -- url() -- but that's probably a good
long-term switch, anyway, because it lets us do more interesting
things. We could likely keep it backwards compatible by changing the
patterns() function to convert any plain tuple into an object of the
type url() returns.

Adrian

--
Adrian Holovaty
holovaty.com | djangoproject.com

Malcolm Tredinnick

unread,
Mar 27, 2007, 8:54:33 PM3/27/07
to django-d...@googlegroups.com
On Tue, 2007-03-27 at 16:57 -0500, Adrian Holovaty wrote:
> On 3/13/07, Ivan Sagalaev <Man...@softwaremaniacs.org> wrote:
> > So for "object_list" and "object_detail" I propose this syntax:
> >
> > {% obj_url "appname.ModelName" id %}
> > {% obj_list_url "appname.ModelName" %}
> >
> > Both GVs have their own tag that knows the actual name of a GV (like
> > "django.views.generic.list_detail.object_list"). Though I like it this
> > way it's not set in stone and may be some parameter would be better
> > (like {% url "object_detail" "appname.ModelName" %}).
>
> I'm a strong -1 on having generic-view-specific permalink functions
> and template tags like this. This solution goes after the symptoms
> rather than the fundamental problem, which is that the current reverse
> URL implementation cannot handle multiple URL patterns for the same
> view.
>
> I've done some thinking on this, too, and I think the cleanest way to
> solve it would be to introduce optional names for URL patterns.
> Something like this:
>
> url(r'^objects/$', some_view, name='object_view'),
> url(r'^objects2/$', some_view, name='object_view2'),

I'd been slowly arriving at the same solution for a related set of
problems. I was going to whack in another optional (fourth) argument to
the tuple, but, you're right that a function call here makes things a
bit easier. So I'm in agreement here, for whatever it's worth.

Regards,
Malcolm


Nicolas E. Lara G.

unread,
Mar 28, 2007, 12:41:27 AM3/28/07
to Django developers
Hello,
I have a proposal for solving this problem on
http://hagen.ac.labf.usb.ve/nicolas/gsoc/url-objects/. Maybe you'll
like it. Personally I dislike the idea of having a different method
for reversing generic views. I believe that the same method should be
use for any view. But I like the labeling idea a lot. making it
optional and integrated in the urlconf is also a good idea (in my
proposal I put it apart). I'll give it more thinking and consider
changing (improving) my proposal.

Ivan Sagalaev

unread,
Mar 28, 2007, 12:22:38 AM3/28/07
to django-d...@googlegroups.com
Malcolm Tredinnick wrote:
> I'd been slowly arriving at the same solution for a related set of
> problems. I was going to whack in another optional (fourth) argument to
> the tuple

That was actually the second part of my proposal :-)

>, but, you're right that a function call here makes things a
> bit easier.

But I fail to see why having patterns as functions is easier... Fourth
parameter in a tuple is backwards compatible while url() is not.

Ivan Sagalaev

unread,
Mar 28, 2007, 12:32:39 AM3/28/07
to django-d...@googlegroups.com
Adrian Holovaty wrote:
> I'm a strong -1 on having generic-view-specific permalink functions
> and template tags like this. This solution goes after the symptoms
> rather than the fundamental problem, which is that the current reverse
> URL implementation cannot handle multiple URL patterns for the same
> view.

Right. I think we agree that naming of patterns is a good thing anyway
(read my reply to Malcolm further down the thread).

But talking about "urls for objects" I think it's useful as a separate
feature. Just yesterday I've implemented (in a local project) a tag that
actually works as a replacement for "get_absolute_url" only. I looks
like this:

{% object_url some_object %}

It searchs through url patterns for "object_detail" views solely and
exits on condition of isinstance(some_object, queryset.model). However
it doesn't work for non-generic views it looks to me so much nicer than:

{% url "object_view" some_object.id %}

So I couldn't resist to share it :-). What do you think?

Malcolm Tredinnick

unread,
Mar 28, 2007, 2:08:08 AM3/28/07
to django-d...@googlegroups.com

It's not an either/or decision. Differentiating between a tuple and the
object -- whatever it might be -- returned from url() inside the URL
resolver is easy. Or it can be done as part of the patterns() call as
Adrian suggested. There's no need to break backwards compatibility.

Extending the URL resolving system is easier if we have an object to
work with instead of a tuple for those entries. A lot of the real work
can be given to the object, which has a method to do the equivalent of
"do I match the path? If so, what should be called with what params?"
Now think about how you can replace that object with an object that
obeys the same interface but uses a different resolver. Not a compulsory
addition, but nice to have without much cost. Feels a bit more
declarative, too -- the URL patterns themselves answer the question as
to whether they should be applied, rather than the container doing that
work.

Regards,
Malcolm

Ivan Sagalaev

unread,
Mar 28, 2007, 1:45:46 AM3/28/07
to django-d...@googlegroups.com
Malcolm Tredinnick wrote:
> Extending the URL resolving system is easier if we have an object to
> work with instead of a tuple for those entries.

Ah, I see it.

However let's make it a separate issue. I'm afraid that if we start
solving "{% url %} for generic views" issue with the "urlpatterns
rewrite" solution we'll bury it for months.

David Danier

unread,
Mar 28, 2007, 4:28:47 AM3/28/07
to django-d...@googlegroups.com
> I've done some thinking on this, too, and I think the cleanest way to
> solve it would be to introduce optional names for URL patterns.

Sounds really nice. And it doesn't even enforce to break the {% url %} tag:
{% url some.view ... %} -> view lookup
{% url "some name" ... %} -> name lookup

But my current problem with reverse URLs is not generic views. I'm using
include() to put comments and full-history under the url of the object
(/article/foo/ -> /article/foo/comments/ or /article/foo/history/). As
these views are used for multiple objects (articles, news, ...) the
reverse lookup is not possible. Could be solved easily by having the
parameters used for the include() available when resolving the url (I
pass an argument that tells the views how to load the object). Even
nicer would be to be able to put this argument into the name.

Example:
article-patterns:
-----------8<--------------------------------------------------------
urlpatterns = patterns('foo.article.views',
(r'^$', 'index'),
# [...]
(r'^(?P<slug>[a-z0-9-]+)/comments/', include('foo.comment.urls'),
{'load_func': 'foo.article.views.get_article'}),
)
# load_func is passed the args/kwargs got from the resolver (slug here)
-------------------------------------------------------->8-----------

comment-patterns:
-----------8<--------------------------------------------------------
urlpatterns = patterns('foo.comment.views',
(r'^$', 'view_comments'),
(r'^post$', 'post_comment'),
)
-------------------------------------------------------->8-----------

Now, something like this would be nice (a additional parameter should
look cleaner, but this shows what I mean I think):
-----------8<--------------------------------------------------------
urlpatterns = patterns('foo.comment.views',
url(r'^$', 'view_comments', name="%(load_func)s.view_comments"),
url(r'^post$', 'post_comment', name="%(load_func)s.post_comment"),
)
-------------------------------------------------------->8-----------

Perhaps this case is to specific to be added to a {% url %} refactoring,
but if it can be done on the way it should be considered. Would really
improve what you can do with include().

Greetings, David Danier

Malcolm Tredinnick

unread,
Mar 28, 2007, 9:42:32 AM3/28/07
to django-d...@googlegroups.com

Relax, Ivan. Changing the call from a tuple to url(...) is a ten line
change in django/conf/urls/defaults.py. It's only that long because I
had to move some code from one place to another, so the diff size
increases. Turned out that most of the work was already present in the
urlresolver.

Regards,
Malcolm

Ivan Sagalaev

unread,
Mar 28, 2007, 9:50:37 AM3/28/07
to django-d...@googlegroups.com
Malcolm Tredinnick wrote:
> Relax, Ivan. Changing the call from a tuple to url(...) is a ten line
> change in django/conf/urls/defaults.py.

Uhmmm... Pardon me, what is the change you are taliking about? :-) I
actually didn't mean any specific code changes, just was worried about
things going wild, theoretically.

Malcolm Tredinnick

unread,
Mar 28, 2007, 10:41:37 AM3/28/07
to django-d...@googlegroups.com
Adrian,

On Tue, 2007-03-27 at 16:57 -0500, Adrian Holovaty wrote:

[...]


> I've done some thinking on this, too, and I think the cleanest way to
> solve it would be to introduce optional names for URL patterns.
> Something like this:
>
> url(r'^objects/$', some_view, name='object_view'),
> url(r'^objects2/$', some_view, name='object_view2'),
>
> This way, you could give an arbitrary name to a particular pattern, so
> that you could target it via a reverse lookup. The Routes package does
> something similar ("named routes"). Note that it would require URL
> patterns to be function calls -- url() -- but that's probably a good
> long-term switch, anyway, because it lets us do more interesting
> things. We could likely keep it backwards compatible by changing the
> patterns() function to convert any plain tuple into an object of the
> type url() returns.

Are you happy with the url template tag being a little bit flexible in
how it does the matching? I mean, rather than having to specify whether
you are giving the view function's name or the name parameter's value,
you just specify the string and it will match whichever one is
available?

If you're happy with that, the attached patch does the change (just to
prove it's possible). I still need to update the docs, but I wanted to
check on the interface first. It adds the url(...) function to
urlpatterns and allows a "name" parameter (or optional fourth argument)
in the pattern. That name is used in the match.

The only place for confusion is if you have a name parameter that
precisely matches an importable function. However, the alternative (to
ensure that never happens) would mean you would need to somehow say "I
am giving you a view function" or "I am giving you a name".

FWIW, I don't think just doing the right thing is a violation of
explicit vs. implicit, since names can be much more descriptive and
views will generally contain dots, so the confusion is limited to people
with unusual ideas about naming things.

I don't like the suggestion elsewhere in this thread that we use string
quoting in the template tag to suggest the name is being used. We
already have an issue there that every other tag uses string quoting to
suggest a literal and unquoted values to say "resolve the template
variable to get the value". To wit:

{% include "foo" %} vs. {% include foo %}
(the latter resolves variable foo to get the value)

whereas

{% url foo %} uses the literal string foo.

At some future time, I might be arguing we fix that prior to 1.0 because
it's inconsistent and removes the ability to use a variable in the url
tag.

Cheers,
Malcolm

url.diff

Ivan Sagalaev

unread,
Mar 28, 2007, 2:14:13 PM3/28/07
to django-d...@googlegroups.com
Ivan Sagalaev wrote:
> Uhmmm... Pardon me, what is the change you are taliking about? :-)

Ok, I see now your post in this thread later.

jkoch...@gmail.com

unread,
Mar 29, 2007, 9:43:27 PM3/29/07
to Django developers

On Mar 27, 4:57 pm, "Adrian Holovaty" <holov...@gmail.com> wrote:

> I've done some thinking on this, too, and I think the cleanest way to
> solve it would be to introduce optional names for URL patterns.
> Something like this:
>
> url(r'^objects/$', some_view, name='object_view'),
> url(r'^objects2/$', some_view, name='object_view2'),
>
> This way, you could give an arbitrary name to a particular pattern, so
> that you could target it via a reverse lookup. The Routes package does
> something similar ("named routes"). Note that it would require URL
> patterns to be function calls -- url() -- but that's probably a good
> long-term switch, anyway, because it lets us do more interesting
> things. We could likely keep it backwards compatible by changing the
> patterns() function to convert any plain tuple into an object of the
> type url() returns.

This could be really hot. Pretend for a moment that this url is a
class which has has a regex() method that is called when we, you know,
want to know what the regex should be. This would allow people to
write new url classes that took routes style patterns, or better yet
(IMHO) URI templates [1]. The class would just have to know how to
turn it's configuration arguments into the proper regex. Under the
covers, dispatch still happens via regex, but there are kinder,
gentler, and extensible ways of setting up those regexes without have
to actually write them.

</random_speculation>

Joseph

[1] http://bitworking.org/projects/URI-Templates/draft-gregorio-uritemplate-00.html

SmileyChris

unread,
Mar 29, 2007, 9:57:19 PM3/29/07
to Django developers
On Mar 28, 9:57 am, "Adrian Holovaty" <holov...@gmail.com> wrote:
> I've done some thinking on this, too, and I think the cleanest way to
> solve it would be to introduce optional names for URL patterns.
> Something like this:
>
> url(r'^objects/$', some_view, name='object_view'),
> url(r'^objects2/$', some_view, name='object_view2'),
>

How would the reverse end work? (I worry that this could easily lead
to view name collision between applications)

Malcolm Tredinnick

unread,
Mar 29, 2007, 10:13:56 PM3/29/07
to django-d...@googlegroups.com

Have a look at the patch I posted to this list the other day. Reverse
works by just specifying the name -- it's even in the test in that
patch. The string you specify can be either the "name" name or the
function name, since they are unlikely to clash unless you have a
twisted way of naming things.

You can't completely avoid name collisions for patterns (or apps or
command line tools or anything) and somebody using very common words as
a name for a URL pattern in a reusable app is asking for trouble. They
probably drive around without wearing a seatbelt, too, though. There's
just no accounting for taste. If you expect your URL patterns to be
reusable, make the name something that is likely to be unique (e.g. add
the app name as a prefix, so myapp-url, for example). That reduces the
likelihood of name collisions to the same as that for app name
collisions, which have to be handled in the same way -- by using
uncommon names.

I thought about allowing the "name" parameter on the include() directive
as a way of controlling the prefix for the included patterns. However,
that would break all templates that already used the name prior to the
prefix being added.

Regards,
Malcolm

Nicolas E. Lara G.

unread,
Mar 30, 2007, 12:15:20 AM3/30/07
to Django developers
Other than using uncommon names for the url one could just use the
most obvious name and let the url tag have a disambiguation function
so if the name is: "name" you could write both:
{% url 'name' %} and {% url 'myapp.name' %} (or "myapp/name" or
"myapp-name")

With this you could keep the url management simple for most cases and
allow users to differentiate colliding names (it would be terrible
that if a name is taken by, lets say, comments or another contrib app
users had problems for setting up url names)

On Mar 29, 10:13 pm, Malcolm Tredinnick <malc...@pointy-stick.com>
wrote:

Malcolm Tredinnick

unread,
Mar 30, 2007, 12:27:20 AM3/30/07
to django-d...@googlegroups.com
On Fri, 2007-03-30 at 04:15 +0000, Nicolas E. Lara G. wrote:
> Other than using uncommon names for the url one could just use the
> most obvious name and let the url tag have a disambiguation function
> so if the name is: "name" you could write both:
> {% url 'name' %} and {% url 'myapp.name' %} (or "myapp/name" or
> "myapp-name")

The problem is predicting what we be called if you use the unprefixed
argument. It's going be non-deterministic from an application's point of
view, so they will *always* have to use a name that is unlikely to
collide. Remember that templates have no point of reference -- they
don't know that they were called from a particular app, so they'll
always be scanning the URL configuration from the top level.

> With this you could keep the url management simple for most cases and
> allow users to differentiate colliding names (it would be terrible
> that if a name is taken by, lets say, comments or another contrib app
> users had problems for setting up url names)

Which is the argument for using uncommon names for your urls. The exact
same problem exists for application names. You can't create an app
called django and expect it to work. Fortunately, django is one of many
uncommon names you can use for an app, so it's easy to pick
non-colliding names. If you choose "comments" for you redistributable
app and somebody else does, too, you're both screwed. Name collision
potential exists all the time (try writing an executable called "ls" and
seeing how wide a distribution you get) and society hasn't ground to a
halt just for asking people to be a little creative.

Regards,
Malcolm

Nicolas E. Lara G.

unread,
Mar 30, 2007, 10:53:51 AM3/30/07
to Django developers
That is true, there are always cases where name will collide but the
disambiguation allows users a little more flexibility. I'm not saying
it will avoid all collisions but it is a simple extension that will
help most users (specially if you've already made your apps and views
and when writing templates you realize that a name collides with a
django contrib app, lets say, admin-add). If your names collide you
have a choice. If, even using the application name, they still collide
then you change them but it wouldn't hurt to allow users to use names
like comments or admin-add and, if they include those contrib apps in
their project, they can disambiguate them .
Its not a critique of your proposal, I propose the same thing, its an
extension.

Nicolas

On Mar 30, 12:27 am, Malcolm Tredinnick <malc...@pointy-stick.com>
wrote:

Malcolm Tredinnick

unread,
Mar 30, 2007, 10:36:13 PM3/30/07
to django-d...@googlegroups.com
Hi Nicolas,

On Fri, 2007-03-30 at 14:53 +0000, Nicolas E. Lara G. wrote:
> That is true, there are always cases where name will collide but the
> disambiguation allows users a little more flexibility. I'm not saying
> it will avoid all collisions but it is a simple extension that will
> help most users (specially if you've already made your apps and views
> and when writing templates you realize that a name collides with a
> django contrib app, lets say, admin-add). If your names collide you
> have a choice. If, even using the application name, they still collide
> then you change them but it wouldn't hurt to allow users to use names
> like comments or admin-add and, if they include those contrib apps in
> their project, they can disambiguate them .
> Its not a critique of your proposal, I propose the same thing, its an
> extension.

I don't really think this is worth the extra confusion with name
resolution. You need unique names in any case because apps cannot rely
on the user using a particular extension. So the extension should be
just coded into the name itself. Just like with template names and app
names.

You say it wouldn't hurt to allow this, but it does hurt: if two
applications use the name "comments" on their URL and a template refers
to {% url comments %}, there is no guarantees about which URL will be
returned. Prefixing doesn't solve that problem because the template
doesn't know which prefix is being used.

Regards,
Malcolm


Reply all
Reply to author
Forward
0 new messages