URL path expression is correct, URL tag can't find it.

The url pattern:

path("<str:space_name>/exhibitions/", views.exhibition_list, name="exhibition_list"),

The url template tag:

<a class="page_menu_item" href="{% url 'exhibition_list' space.name 'exhibitions' %}"> Exhibitions </a>

The error:

Reverse for 'exhibition_list' with arguments '('', 'exhibitions')' not found. 1 pattern(s) tried: ['artwork/(?P<space_name>[^/]+)/exhibitions/\\Z']

Why?

Note that directly entering “http://127.0.0.1:8000/artwork/public/exhibitions/” in my browser does work, so the pattern is correct.

The only thing different from my other url tags is that this one has a literal string in it. None of the various web discussions about literals in urls were useful (and don’t get me started on the ridiculous ‘load from future’ tag). So my hack fix to this is to pass in the word “exhibitions” through the context param. Irritating.

Django version 5.0.7

Is your template tag split across lines in your real template? If so, that’s a problem. Django tags are not allowed to be split.

However, the issue here is that you’re trying to render a url with two parameters when your url definition only allows for one.

It’s not split across two lines.

As for ‘two parameters’: are you saying that this would work?

path("<str:space_name>/<str:exhibitions>/", views.exhibition_list, name="exhibition_list"),

That would cause a conflict with other paths with two parameters.

That would be a valid use of a url defined that way.

However, I’d look at it from the other direction.

Why are you passing 'exhibitions' as a second parameter?

Side note: Your error also shows that you don’t have a value for space.name in the context being used to render this template. You need to ensure that your tag is referencing the proper variable.

Aha… I wasn’t understanding the function of the pattern name — that’s why I putting ‘exhibitions’ in the path, for disambiguation. So now I have this, which works:

path("<str:space_name>/", views.exhibition_list, name="exhibition_list")

<a class="page_menu_item" href="{% url 'exhibition_list' space.name %}"> Exhibitions </a>

Re space_name, it’s populated via the context param passed into the template.

That’s fine to have the string ‘exhibitions’ in the path. Using:

Is 100% valid and correct.

However, in both cases (with and without ‘exhibitions’), there’s only one parameter.

You would properly render this url with:
{% url 'exhibition_list' space.name %}

The problem with your previous attempt was with trying to use that path with:
{% url 'exhibition_list' space.name 'exhibitions' %}

If you’re populating space_name in the context, that’s the wrong context variable, because you’re specifying space.name as the parameter, which would be the name attribute of an object or dict named space.

That’s fine to have the string ‘exhibitions’ in the path.

No need to.

If you’re populating space_name in the context, that’s the wrong context variable

No, it’s passed in as ‘space’:

space = Space.objects.get(name=space_name)
context = { exhibitions: exhibitions, space: space }

Ok, I was momentarily convinced that the ‘name’ param in a path expression was a definitive identifier, but apparently not:

path("<str:arg1>/<str:arg2>/", views.path_test_a, name="path_test_a"),
path("<str:arg1>/<str:arg2>/", views.path_test_b, name="path_test_b"),

when referenced by:

{% url 'path_test_a' 'foo' 'bar' %}

invokes the view method:

path_test_a(request, arg1, arg2)

So far so good.

But if the two path expressions are flipped (i.e., path_test_b comes before path_test_a), then:

{% url 'path_test_a' 'foo' 'bar' %}

invokes

path_test_b(request, arg1, arg2)

What am I missing here? Does the path (i.e. str:arg1/str:arg2/ ) take precedence or override the path name?

The path takes precedence. Django uses the first matching entry in your URLconf, the path name (or rather view name) is mostly only used for reversing URLs.

The name parameter is an empty string. That’s no good. I would guess there’s a template error being swallowed. I recommend installing django-fastdev which turns these errors into hard errors in the template where the error is.

It is a definitive identifier within Django. When you are reversing a URL, you are creating a url based upon the name given.

However, the names are meaningless for URLs being requested. Your browser doesn’t submit a name, it submits a url. The url being submitted is compared to each of the defined paths in the order that they are defined, and will call the first view where the url matches the path.

See URL dispatcher | Django documentation | Django for more details on this.

Ok, thanks for the help, I worked it out.

I have to say, though Django’s documentation is generally very good, for this feature it’s rather poor (of course, it’s hard to write good documentation of a badly-designed scheme — but I digress).

H

I, controversially, tell people not to use reverse()/{%url%} and just write out the url. It’s error prone, and slow, and what do you get for that work? It becomes easier to change urls later, which is so rare that it doesn’t matter.

(For library authors you need it though, and for i18n urls)

There’s another situation where it’s important too, and that’s the case where you’re needing to deploy multiple projects, or multiple instances of the same project on one site, and you need to segregate those projects below different base URLs.

In our situation, that’s far from being “so rare that it doesn’t matter”.

As a side note, we also find that using it reduces errors in that fewer mistakes are made by being able to reference a single app:name for a long url and passing parameters by name rather than needing to remember that full path and the order in which parameters are being provided.

1 Like

As long as you use reverse() then sure. It’s just that the combination of template silent errors creating empty strings and then failing silently after that that is a problem.

It’s also an issue of the above, plus the poor error message (it could list all valid names for example!), that makes many beginners struggle needlessly imo. It’s just not worth it for people who are already struggling with basics like HTML, CSS, and Python.