Opened 12 years ago

Closed 9 years ago

#17914 closed Bug (wontfix)

Add support for namespaced view references when passing a callable to reverse()

Reported by: Bradley Ayers <bradley.ayers@…> Owned by: nobody
Component: Core (URLs) Version: dev
Severity: Normal Keywords:
Cc: bradley.ayers@…, amirouche.boubekki@…, gwahl@…, Florian Apolloner Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

It's not possible to reverse a URL by passing a function reference to reverse()
that's hooked into the URLconf under a namespace.

Consider the trivial Django project (that doesn't work):

# urls.py
from django.conf.urls.defaults import patterns, include
from django.core.urlresolvers import reverse
from django.http import HttpResponse

def view(request):
    # Return the URL to this view
    return HttpResponse(reverse(view))

level2 = patterns('',
    (r'^2/', view)
)

urlpatterns = patterns('',
    (r'^1/', include(level2, namespace="foo"))
)

Removing , namespace="foo" will make it work.

My understanding of why this happens, is that reverse() traverses RegexURLResolvers
*only* for strings. Function references are passed directly to the root RegexURLResolver.reverse
method.:

# from django/core/urlresolvers.py ~ line 432

if not isinstance(viewname, basestring):
    view = viewname
else:
    # <snip> -- resolver traversal happens here to honor namespaces
    # `resolver` is reassigned to last RegexURLResolver in namespace chain

return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))

Obviously traversing namespaces isn't possible for a function reference,
so what's needed is to add *all* descendent view references into each RegexURLResolver's
reverse_dict dictionary.

Populating reverse_dict is lazy and happens on first access, the work is
performed in RegexURLResolver._populate:

# django/core/urlresolvers.py ~ line 237
def _populate(self):
    # <snip>
    for pattern in reversed(self.url_patterns):
        # <snip>
        if isinstance(pattern, RegexURLResolver):
            if pattern.namespace:
                # <snip> -- tldr `self.reverse_dict` isn't populated, `pattern` (the
                #           RegexURLResolver) is instead stored in `self.namespace_dict`
            else:
                # <snip> -- tldr `self.reverse_dict` *is* populated with the keys
                #           being the name of each URL (or the function reference)
                #           defined in this level. (i.e. only children, not all descendants)
        else:
            # <snip> -- tldr `self.reverse_dict` is again populated

This combination of behaviour by RegexURLResolver._populate and reverse()
leads to the problem.

Attachments (3)

17914-test.patch (2.5 KB ) - added by Bradley Ayers <bradley.ayers@…> 12 years ago.
test to expose bug
17914.patch (8.0 KB ) - added by Bradley Ayers <bradley.ayers@…> 12 years ago.
fix + tests
17914.diff (437 bytes ) - added by Tim Graham 9 years ago.

Download all attachments as: .zip

Change History (26)

by Bradley Ayers <bradley.ayers@…>, 12 years ago

Attachment: 17914-test.patch added

test to expose bug

by Bradley Ayers <bradley.ayers@…>, 12 years ago

Attachment: 17914.patch added

fix + tests

comment:1 by Bradley Ayers <bradley.ayers@…>, 12 years ago

Has patch: set

comment:2 by Bradley Ayers <bradley.ayers@…>, 12 years ago

Cc: bradley.ayers@… added

comment:3 by Bradley Ayers <bradley.ayers@…>, 12 years ago

Version: 1.3SVN

comment:4 by Bradley Ayers <bradley.ayers@…>, 12 years ago

It should be noted that this patch is technically backwards incompatible. After applying this patch, it's no longer possible to use recursive URL patterns, e.g. consider a project named 'foo', and it had a urls.py that contained:

urlpatterns = patterns('', (r'^abc/', include('foo.urls'))

comment:5 by Chris Beaven, 12 years ago

Patch needs improvement: set
Triage Stage: UnreviewedAccepted

Accepting.

Like we talked about on IRC, let's just avoid circular imports when doing your traversal rather than dropping that functionality.

comment:6 by Bradley Ayers <bradley.ayers@…>, 12 years ago

I've updated the patch to maintain support for recursive urlconfs. https://github.com/django/django/pull/174

comment:7 by Malcolm Tredinnick, 12 years ago

Doing a quick scan of URL reversing during a sprint, the last git code looks reasonable. Not ready to commit yet -- I haven't fully digested it, but my intuition is that it's the right kind of thing.

Except(!) that using function objects in reverse() situations is kind of a "don't do that" kind of situation. We permitted it initially for smooth integration, but it's kind of a lucky happenstance. My point being, that a patch of this size is a reasonable to fix this ticket. If it grows to twice this size, we should maybe just add a "don't do that" note to the documentation -- the extra complexity isn't worth it.

For now, charge ahead!

comment:8 by Amirouche, 11 years ago

Cc: amirouche.boubekki@… added

comment:9 by gwahl@…, 11 years ago

Cc: gwahl@… added

comment:10 by Bas Peschier, 10 years ago

Closed #21899 as a duplicate

comment:11 by Tonnzor, 10 years ago

Any news here?

Since reverse now accepts app_name -- as well as include -- we could easily find corresponding namespace. E.g.:

from django.conf.urls import *

urlpatterns = patterns('',
    url(r'^payment/', include('payment.urls', namespace='payment', app_name='payment')),
)

reverse(view, current_app='payment') # try with current_app given

As alternative solution -- we could require passing namespace when using with function. The we could convert function to its name, add namespace+":" and use existing machinery.

comment:12 by Tai Lee, 10 years ago

#21899 was closed a duplicate of this ticket, but I'm not sure they are the same thing. Will fixing this ticket also fix #21899? This ticket seems to be about reversing raw callable view functions, for which we have no way to specify a namespace. The other ticket was about reversing a dotted path reference to a view function, specified as a string with a namespace.

comment:13 by Florian Apolloner, 9 years ago

Cc: Florian Apolloner added

Can someone present a usecase for using function references in reverse? IMO this should just get closed as wontfix and names should be used throughout instead.

comment:14 by Bas Peschier, 9 years ago

This feels like using args and kwargs on reverses. Using either function reverses or namespaces is fine, just don't mix them.

Maybe we should enforce it a bit more like we do with reversing with args and kwargs?

in reply to:  13 ; comment:15 by Thomas Güttler, 9 years ago

Replying to apollo13:

Can someone present a usecase for using function references in reverse? IMO this should just get closed as wontfix and names should be used throughout instead.

For me the view-name is redundant. We try to avoid it. Reversing by function reference works fine (except this bug). Jumping through the code with an IDE is much more fun if you use function references.

It would be nice if this could be solved. For our team this means: don't use namespaced URLs :-(

in reply to:  15 comment:16 by Florian Apolloner, 9 years ago

Replying to guettli:

For me the view-name is redundant. We try to avoid it. Reversing by function reference works fine (except this bug). Jumping through the code with an IDE is much more fun if you use function references.

If I remember correctly we've been over the redundancy point on the ML already, you can always just write your own url function which sets the path of the function as name, not really redundant imo. Also reversing by function reference is something which is ugly in templates etc imo… And redundant is relative anyways, "app:view_name" is imo way nicer than "app.views.view_name" -- especially if you have nested imports.

comment:17 by Tim Graham, 9 years ago

I'm in favor of a "won't fix" of this issue and promoting reversing by URL name as Florian suggested. We could deprecate passing callables to reverse, but this will probably just needlessly annoy people who are happy with it. Instead, we could just remove it from the docs like we did with the @permalink decorator.

comment:18 by Tim Graham, 9 years ago

Summary: reverse() does not support namespaced view referencesreverse() does not support namespaced view references when passed a callable

by Tim Graham, 9 years ago

Attachment: 17914.diff added

comment:19 by Tim Graham, 9 years ago

Patch needs improvement: unset

Added a documentation patch to discourage reversing by callable view.

comment:20 by Florian Apolloner, 9 years ago

LGTM

comment:21 by Tim Graham <timograham@…>, 9 years ago

In a6acfc31:

Refs #17914 -- Discouraged using reverese() with callables.

comment:22 by Tim Graham <timograham@…>, 9 years ago

In f32bb3a:

[1.8.x] Refs #17914 -- Discouraged using reverese() with callables.

Backport of a6acfc31837fd7a9e0e387320d995b2c85cfcfce from master

comment:23 by Tim Graham, 9 years ago

Resolution: wontfix
Status: newclosed
Summary: reverse() does not support namespaced view references when passed a callableAdd support for namespaced view references when passing a callable to reverse()
Note: See TracTickets for help on using tickets.
Back to Top