A question related to URLConf, Reverse URL, etc...

5 views
Skip to first unread message

medhat

unread,
Jan 25, 2007, 7:45:21 PM1/25/07
to Django developers
Ok,

I have been thinking about this for about an hour now... and I would
like to get some feedback from you.

First, the problem that I was initially trying to solve:

I have multiple models, all of which can be tagged. I also have a Tag
model with a custom manager that can return tags for an item, a list of
items, or for a model type; or inversly you can give it some tags, and
it will give you back the items that are tagged with that tag
(optionally you can restrict the returned items to a type)

And I have the following urls:

/tags/x/ in the root urls.py (will return all items that
have tag x irrespective of their type)
/photos/tags/x/ in the photos application's urls.py (will return all
photos that have tag x)

both of these urls call the same view, but the second one will pass the
type in the extra options where in the first one it will default to
None (from the default valie in the view)

In the final html will have a list of other tags that you can click.
For example, first I will get all the photos that have tag x, then get
all the tags of these photos, and have a link for each one of these
tags on the final page.

My problem is that the link for tag y in the second case should be
/photos/tags/y and in the first case should be /tags/y

At first this seemed like a good candidate for some Reverse URL
hackery... but then I thought, when the url matching is done, when does
django get rid of what was matched when it finds an include?! Imagine
if the view is defined as:

def view_function(context,stem, .... ) where stem is the part of the
url that was matched before jumping into the urls.py that has the
called view that would solve my problem (and probably a lot of the
problems that led to the creation of urlresolver.reverse.

I am willing to work on a patch that will implement this. But I also
understand that this will be a huge backwards incompatible change
(since "stem" will need to be added to each view function) So, I am
expecting that people will not be so warm to the idea.

Another alternative, which I don't like as much, but will be backwards
compatible is to add "stem" to the request object (even though I
understand this is not really a request property)

So, I would like to hear what you guys think.

--
Thanks!
Medhat

medhat

unread,
Jan 26, 2007, 1:12:32 PM1/26/07
to Django developers
Here is even a better idea...

in your urls.py when you "from django.conf.urls.defaults import *" this
will import 'stem' in addition to the usual things. In any url pattern,
if you add {'param_name':stem} to the extra options, your view will get
a keyword argumant called 'param_name' and will have the value of the
part of the url that matched *before* going into that urls.py file.

example:

--- urls.py ---

from django.conf.urls.defaults import *

urlpatterns = patterns('',
(r'^categories/', include('apps.tags.urls')),
)

--- apps/tags/urls.py ---

from django.conf.urls.defaults import *

urlpatterns = patterns('apps.tags.views',
(r'^(?P<tag>[^/]*)/$','tag_list',{'stem':stem}),
)

---------------

In that case the url http://www.example.com/tags/x/ will call
apps.tags.views.tag_list and pass it the request object and two keyword
parameters: tag = 'x' and stem = /categories/

I already have a patch that does this. I don't have unit tests or
documentation yet. But if this looks like a patch that would be
accepted, I can definitely work on the tests and documentation.

--
Thanks,
Medhat

medhat

unread,
Jan 26, 2007, 1:18:42 PM1/26/07
to Django developers
the url should be http://www.example.com/categories/x/

--
Medhat

On Jan 26, 12:12 pm, "medhat" <medhat.ass...@gmail.com> wrote:
> Here is even a better idea...
>
> in your urls.py when you "from django.conf.urls.defaults import *" this
> will import 'stem' in addition to the usual things. In any url pattern,
> if you add {'param_name':stem} to the extra options, your view will get
> a keyword argumant called 'param_name' and will have the value of the
> part of the url that matched *before* going into that urls.py file.
>
> example:
>
> --- urls.py ---
>
> from django.conf.urls.defaults import *
>
> urlpatterns = patterns('',
> (r'^categories/', include('apps.tags.urls')),
> )
>
> --- apps/tags/urls.py ---
>
> from django.conf.urls.defaults import *
>
> urlpatterns = patterns('apps.tags.views',
> (r'^(?P<tag>[^/]*)/$','tag_list',{'stem':stem}),
> )
>
> ---------------
>

> In that case the urlhttp://www.example.com/tags/x/will call

SmileyChris

unread,
Jan 26, 2007, 3:04:48 PM1/26/07
to Django developers
I'd suggest doing it as middleware which appends to the request (sorta
like the user middleware). This way it's available in the view as well
as the template context (if you pass it explicitly or use the request
context processor).

Also, what about urls.py deeper than just parent->child? Maybe stem
should be a list of matches, one for each URLResolver?

On Jan 27, 7:12 am, "medhat" <medhat.ass...@gmail.com> wrote:
> Here is even a better idea...
>
> in your urls.py when you "from django.conf.urls.defaults import *" this
> will import 'stem' in addition to the usual things. In any url pattern,
> if you add {'param_name':stem} to the extra options, your view will get
> a keyword argumant called 'param_name' and will have the value of the
> part of the url that matched *before* going into that urls.py file.
>
> example:
>
> --- urls.py ---
>
> from django.conf.urls.defaults import *
>
> urlpatterns = patterns('',
> (r'^categories/', include('apps.tags.urls')),
> )
>
> --- apps/tags/urls.py ---
>
> from django.conf.urls.defaults import *
>
> urlpatterns = patterns('apps.tags.views',
> (r'^(?P<tag>[^/]*)/$','tag_list',{'stem':stem}),
> )
>
> ---------------
>

> In that case the urlhttp://www.example.com/tags/x/will call

medhat

unread,
Jan 26, 2007, 3:26:47 PM1/26/07
to Django developers
In the case of the parent->child->grandchild, the grandchild view will
get the stem for everything before the grandchild urls.py (In my case,
I really did not care how many urls.py it went through, my main problem
is that the same view can be called by multiple urls, and I wanted in
each case to have the stem available to the template)

As for doing it as a middleware, it is probably doable, but it does not
seem right to me... in the case of the user middleware, the user is an
actual property of the request, but the stem does not feel like it
belongs to the request (I know I am being a purist here :-)) the stem
is more an internal thing that depends on how the project is organized
and how urls.py files are including each other.

medhat

unread,
Jan 30, 2007, 11:33:02 AM1/30/07
to Django developers
Well, I went ahead and created a patch for this. It is in ticket 3402
(http://code.djangoproject.com/ticket/3402)

--
Thanks,
Medhat

On Jan 26, 12:18 pm, "medhat" <medhat.ass...@gmail.com> wrote:
> the url should behttp://www.example.com/categories/x/

mur...@gmail.com

unread,
Jan 31, 2007, 9:02:20 AM1/31/07
to Django developers
I actually wrote a patch yesterday to solve the same problem, but for
different reasons (and using a different method). From my
perspective, the larger problem that access to the URL 'stem' can
solve is to decouple apps from projects, making it much easier to
write relocatable, "drop-in" applications.

Currently, if an application wants to construct absolute URL's for
anything, one needs to either hardcode the 'stem' into the
application, and into the project's URLconf. (For most things, of
course, it is at least possible to use relative URLs, but it can
certainly be less convenient sometimes..) With the solution described
above (and in medhat's patch) you still need to know what the
particular application wants the stem to be passed as, so in a lot of
cases, it seems you might be just as well doing this:
(r'^news/', 'myproject.apps.news.urls', {'stem':'news'})
instead of:
(r'^news/', 'myproject.apps.news.urls', {'stem':stem})

The solution I went with was to add the stem transparently to the
request variable as the URLs are resolved. (I agree it's not exactly
a property of the request itself, so this might be suboptimal; it is
at least backwards compatible, though..) This would allow application
authors an easy way to refer to themselves absolutely, and let folks
installing those applications do so at any URL prefix with only a
simple include, and without having to figure out what stem variable
the app wants to be passed.

medhat

unread,
Jan 31, 2007, 10:56:26 AM1/31/07
to Django developers
Well, Matt and Adrian closed the ticket with some similar argument.
But I respectfuly disagree :-)

In muramas example above, (r'^news/', 'myproject.apps.news.urls',
{'stem':'news'}) this does not solve my problem, because in my case
(assuming that this pattern is in an included urls.py) the stem is not
'news' it is whatever was matched in the urls.py that included this
current one.

In the ticket the argument was that in the including urls.py you can
have something like:
(r'^(?P<section>news)/', include('myproject.apps.news.urls')),
and then use section as a kwarg. I see some problems with this:

1. This will only work for one level of includes (granted I only have
one urls.py that is a second level, and probably no one will ever need
more than two levels) Of course you can do something similar for more
levels but it will be very messy.
2. The more you add parameters to the pattern, the more it becomes
complex and harder to read.
3. Doing it this way (?P<section>news) will force me to have a
'section' kwarg for every view in the included urls.py while I only
need it for only one view.
4. In the django documentation, it says the following on the URL
Dispatcher page "Whenever Django encounters include(), it chops off
whatever part of the URL matched up to that point and sends the
remaining string to the included URLconf for further processing." And
that's what sounded weird to me... "it chops off..." why chop off
something that seems to be useful to pass to the view?

In my case, as I mentioned before, adding /tags at the end of almost
any url will display a page with the tags of the item that was viewed.
And on that page when constructing the links for the tags I want it to
include the stem. So for example /galleries/tags will show only the
tags in galleries (and all these tags will be links that will show
gallery items with that tag) and /galleries/2004/tags will do the same
thing but only for galleries of pictures that were taken in 2004. Same
thing can be done for /blog/tags etc...

My solution (the one in the patch in ticket 3402 is fully backward
compatible, will only affect the views that *need* the stem, and is
not affected by how many levels of includes there are. And imho I
think it solves the problem in a simple non-obtrusive way.

So, I would like you guys to reconsider the patch in light of this
more in-depth explanation :-)

--
Thanks,
Medhat

mur...@gmail.com

unread,
Jan 31, 2007, 12:37:14 PM1/31/07
to Django developers
Ah, ok.. I understand a little better now what you are trying to do.
I definitely agree that a solution to this would be a useful addition;
I would only advocate for considering the "request.stem" method
instead. As with your patch, it is completely backwards-compatible
and works with nested includes; I find it slightly more simple/
unintrusive, as you don't need to add anything to the URLconf in order
to use the stem in a view.

I haven't written docs/tests for the request.stem patch yet, but I can
do so and post it if anyone is interested for the sake of comparison.

Reply all
Reply to author
Forward
0 new messages