10

Background

Let's say I have a url pattern with parameters that will link me to a view in django:

url(
    r'^things/(?P<thing_name>\w+)/features/(?P<feature_name>\w+)$',
    views.thingFeature,
    name='thing_feature'
),

And lets say I have a thing and a feature:

thing = Thing.objects.get(.....)
feature = thing.feature_set.first()

t_name = thing.name
f_name = feature.name

Now Django gives me the awesome ability to get a url that brings me to a page dedicated to a specific feature of a specific thing. I can do that like so:

from django.core.urlresolvers import reverse
url = reverse('thing_feature', thing_name=t_name, feature_name=f_name)
# url == '/things/thing2/features/left-arm'

Question

Now I've stumbled into a situation that I need to specifically address. I'm not looking for a workaround - I'm looking to solve the following problem:

Given a url's name, how do I get the list of kwarg argument names needed to reverse that url?

I am looking for the function get_kwarg_names_for_url. It behaves like so:

url_kwarg_names = get_kwarg_names_for_url('thing_feature')
# url_kwarg_names == ['thing_name', 'feature_name']

url_kwarg_names is now the list of every keyword I need to supply to Django's reverse function in order to reverse the url named "thing_feature".

Any help is appreciated!

Solution

Based on knbk's answer I was able to come up with the following solution:

def get_kwarg_names_for_url(url_name):
    resolver = get_resolver(get_urlconf())
    reverse_data = resolver.reverse_dict[url_name]

    pattern_list = reverse_data[0]

    '''
    Need to specify the 1st pattern because url regexes can
    potentially have multiple kwarg arrangments - this function does
    not take this possibility into account.
    ''' 
    first_pattern = pattern_list[0]

    '''
    `first_pattern` is now of the form `(url_string, kwarg_list)` -
    all we are interested in is the 2nd value.
    '''
    return first_pattern[1]
Gershom Maes
  • 7,358
  • 2
  • 35
  • 55
  • I just went down a rabbit hole of: reverse the url, match the url to a urlpattern, compile the urlpattern regex, and inspecting the `groupindex` of the regex... I've got to be missing something here. – Kyle Pittman Oct 07 '15 at 21:37
  • 1
    An issue is that you can't reverse the url before you already know the required url parameter names - django will raise an error for `reverse('thing_feature')` because it's missing the needed kwargs! – Gershom Maes Oct 07 '15 at 21:42
  • You can try this http://stackoverflow.com/a/1275601 I tried myself here, and using `.items()` instead, it returns a nice list of tuples that has the kwargs. You have to go deeper seems to be a good start, unfortunately I'm going home now and can't go deeper. – Tiago Oct 07 '15 at 21:47
  • I just want to add that you don't need the kwarg names themselves to reverse the pattern. Even patterns with named groups accept positional arguments. – knbk Oct 07 '15 at 22:05

2 Answers2

2

I'll start with a fair warning: this isn't possible using the public API. On top of that, I'm actively working to rewrite the URL dispatcher for 1.10, so this method will most likely break by that time.

First, you need to get the right RegexURLResolver. If the view is not in a namespace, you can use the reverse_dict to get a list of possibilities, and extract the kwargs:

def get_kwargs(view_name):
    resolver = urlresolvers.get_resolver()
    patterns = resolver.reverse_dict.getlist(view_name)
    kwargs = []
    for possibility, pattern, defaults in patterns:
        for result, params in possibility:
            kwargs.append(params)
    return kwargs

Since a view name can have multiple patterns with different kwargs (though you'd want to avoid that for your own sanity), this will return a list of each set of possible kwargs. Usually the different sets would be the required kwargs on one side and required + optional kwargs on the other side.

I haven't tested this, but if it doesn't work you can dig around in resolver.reverse_dict for a bit to find out the exact specifics. It wasn't exactly designed with usability in mind.

knbk
  • 52,111
  • 9
  • 124
  • 122
  • You sir are a savage. Your code doesn't quite work directly, but it gave me more than enough information to make it functional. I'm also not worried about url regexes resulting in multiple kwarg arrangements. Updated my question to reflect what worked in the end. – Gershom Maes Oct 07 '15 at 22:21
  • p.s. It sounds like you're one of the django authors? – Gershom Maes Oct 07 '15 at 22:35
  • @GershomMaes I'm a contributor, not part of the core team though ;) – knbk Oct 07 '15 at 22:36
  • Oh, coolcool, anyhow thanks for the great answer! It'll serve me well :) – Gershom Maes Oct 07 '15 at 22:37
1

You should be able to do this with a resolve()

From the Docs:

A ResolverMatch object can then be interrogated to provide information about the URL pattern that matches a URL:

func, args, kwargs = resolve('/some/path/')

Specific to your example code:

url = reverse('thing_feature')
func, args, kwargs = resolve(url)
# args == ['thing_name', 'feature_name']
Community
  • 1
  • 1
Dan O'Boyle
  • 3,676
  • 5
  • 28
  • 44