Unfortunately, I haven't found the time and motivation to actually work on
these improvements since then. I just mentioned them in #20734.
I'm including below a slightly edited version of my notes. Not everything
is clear, but that's all I have, and I don't remember anything else.
----
'''Use cases'''
URL namespaces only exist for the purpose of reversing. They're "names for
groups of URLs".
(They're analogous to XML namespaces in this regard.)
- Apps need to be able to reverse their own URLs, even if there are
several instances installed.
- It must be possible to find the default instance of an app.
- It must be possible to find a specific instance of an app.
'''Application vs. instance namespaces'''
An application namespace = app_name
- There can be only one in a given project.
- The only use case for not using the application label is name conflicts.
- Shouldn't it be eventually moved to app._meta? ''(not sure about what I
meant there)''
An instance namespace = namespace
- It differentiate instances of the same application.
'''Next steps'''
1) Clarify documentation
2) Make it possible to reverse without specifying the namespace and
document this pattern:
{{{
urlpatterns = (
url(r'^foo/', include('foo.urls', namespace='foo')),
)
}}}
This requires a way to specifiy the default namespace. It would supersede
#11642.
--
Ticket URL: <https://code.djangoproject.com/ticket/21927>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
Comment (by alanwj):
I use the following in most of my projects, which makes namespaces work
more or less the way people probably expect them to. It allows you to
define an app_name (and optionally a namespace) in your urlconf, and falls
back to the current behavior if you don't.
{{{
from django.conf.urls import include as django_include
from django.utils.importlib import import_module
from django.utils import six
def include(urlconf_module, namespace=None, app_name=None):
"""Namespace aware replacement for Django's include."""
if isinstance(urlconf_module, six.string_types):
urlconf_module = import_module(urlconf_module)
app_name = app_name or getattr(urlconf_module, 'app_name', None)
namespace = namespace or getattr(urlconf_module, 'namespace', None)
# app_name doesn't work unless namespace is also defined
return django_include(urlconf_module, namespace or app_name, app_name)
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:1>
Comment (by bendavis78):
A major problem i see with url namespaces is that if the user of an app
specifies a namespace, it will break url reversals within the app itself
(even when using current_app). If I want my app to support namespacing,
and I don't want my url reversals to break within my app, I have to
provide a function that accepts a namespace argument and returns a triple
and instruct the user to use that instead of specifying a namespace in in
include(). This makes the namespace argument to include() fairly useless,
IMHO. It also forces a non-standard API for namespacing urls.
If what this ticket proposes can fix that, I'm all for it.
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:2>
Comment (by mrmachine):
Indeed, an app author ideally shouldn't need to do anything special (hard
coding the app name or namespace) when reversing the app's own URLs
because it is at the project level, outside the control of the app author,
where app URLs are installed into the root URLconf and optionally with a
namespace, either to avoid conflict with another installed app or to
install one app more than once at different URLs.
This means that app authors must provide instructions to the effect that
"my app must be installed within a namespace with an app_name and at least
once with a namespace of FOO" or some other work-around like the one you
have mentioned.
Django should automatically provide a default "current app" hint to
`reverse()` and `{% url %}` when URLs are reversed within the context of a
request/response cycle where the requested URL is installed into the root
URLconf with a namespace. Django can get this information from
`request.resolver_match`early in the request/response cycle and make it
available to `reverse()` via thread local storage. The use of thread local
storage is just one idea, I would be open to any others.
Apps should not themselves need to know anything about the namespace that
they are installed into (if any). App authors should reverse URLs using
their name only, as defined in the app's URLconf, and project developers
should be able to install that URLconf with any arbitrary namespace as
they see fit. Django should know when a URL is being reversed within the
context of that app, and provide the current app hint automatically based
on the configuration provided by the project developer.
See #22203 which proposes a default current app via thread local storage
but was closed as wontfix, and the corresponding google groups thread:
https://groups.google.com/forum/#!topic/django-developers/mPtWJHz2870
Also see #11642 which proposes to allow app authors to define a default
app name/namespace, similar to the earlier comment above by alanwj.
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:3>
* cc: real.human@… (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:4>
Comment (by bendavis78):
I always get confused when revisiting URL namespaces, and I feel like I
have to re-learn how they work each time. That tells me there's something
wrong with the API here. I'm commenting here mostly just so that I can
come back next time I get confused. Maybe this will help explain the issue
to others.
Whether or not you're using namespaces, ''current'' best practice dictates
that a url name should always prefixed in some way to keep it from
clashing with other names in the app:
{{{
#!python
# usefulapp/urls.py
urlpatterns = [
url('^one-thing/$', views.one_thing, name='usefulapp_one_thing'),
url('^another-thing/$', views.another_thing,
name='usefulapp_another_thing')
]
}}}
This in itself is a manner of namespacing, but has nothing to do with
Django's url namespaces. The purpose of url namespaces is not to keep one
app's urls clashing with another's, but to allow an app's urlconf module
to be used multiple times in a project via `include()`:
{{{
#!python
#some_big_project/root_urlconf.py
import usefulapp.urls
urlpatterns = [
url('^$', my_project_app.views.home),
url('^random_page', my_project_app.views.random_page),
url('^some-things', include(usefulapp.urls, namespace='somethings')),
url('^other-things', include(usefulapp.urls, namespace='otherthings'))
]
}}}
As an **app developer**, I don't explicitly define any namespace. If I
want my app to support multiple inclusions of its urlconf, I must reverse
my urls using the "application namespace" (which is always my app_label):
{{{
#!python
# usefulapp/utils.py
from django.core.urlresolvers import reverse
def get_thing_url(current_app):
# The ‘instance namespace’ ╮
# ╭────┴────╮
return reverse("usefulapp:usefulapp_one_thing",
current_app=current_app)
# ╰────┬────╯
# ╰ the ‘application namespace’ (defaults to
"usefulapp")
}}}
**Problem # 1:** If I reverse urls in my app without using the application
namespace, my app will be broken for those who wish to use namespacing.
A **project developer** can then reverse urls as needed using the
namespace:
{{{
#!html
<div class="code"><pre style="background: white; color:black; padding:
10px">
<span style="color:green">In [1]:</span> from django.core.urlresolvers
import reverse
<span style="color:green">In [2]:</span>
reverse('somethings:usefullapp_one_thing')
<span style="color:red">Out[2]:</span> '/some-things/one-thing/'
<span style="color:green">In [3]:</span> from usefulapp import
get_thing_url
<span style="color:green">In [4]:</span>
get_thing_url(current_app='somethings')
<span style="color:red">Out[2]:</span> '/some-things/one-thing/'
</pre></div>
}}}
Ok, that's all fine and dandy. **But**, what about when another project
developer comes along and wants to use my app, and doesn't really care
about namespacing?
**Problem # 2:** This is how the vast majority of Django include an app in
their urls:
{{{
#!python
#my_project/root_urlconf.py
import usefulapp.urls
urlpatterns = [
url('^$', my_project_app.views.home),
url('^random_page', my_project_app.views.random_page),
url('^some-things', include(usefulapp.urls)),
]
}}}
{{{
#!html
<div class="code"><pre style="background: white; color:black; padding:
10px">
<span style="color:green">In [1]:</span> from django.core.urlresolvers
import reverse
<span style="color:green">In [2]:</span> reverse('usefullapp_one_thing')
...
<span style="color:red; font-weight: bold">NoReverseMatch</span>: Reverse
for 'usefullapp_one_thing' with arguments '()' and keyword arguments '{}'
not found.
</pre>
}}}
“Hmm, that's odd. Oh, right this app uses namespaces, like with the admin
and "admin:index"... Seems like this should work...”
{{{
#!html
<div class="code"><pre style="background: white; color:black; padding:
10px">
<span style="color:green">In [2]:</span>
reverse('usefulapp:usefullapp_one_thing')
...
<span style="color:red; font-weight: bold">NoReverseMatch</span>:
u'passreset' is not a registered namespace
</pre>
}}}
{{{
#!html
“What the... How the heck do I register a namespace? The <a
href="https://docs.djangoproject.com/en/dev/topics/http/urls/"
target="_blank">docs</a> never said anything about registering
namespaces.”
}}}
(they really don't)
TLDR; The problem is, by supporting namespacing in the app, I'm forcing
all other users to explicitly declare an instance namepsace even if they
don't need one. The implementation of the API may be simple, but the usage
is complicated and confusing. Ideally, an app developer shouldn't have to
worry about whether or not someone uses a namespace with their app. A
project developer shouldn't have to use the namespace arg if it isn't
necessary.
It's been said in previous tickets that a "default namespace" will
encourage developers to create "poor url patters" like `url(...,
name=post)`. As for me, I don't see this as a poor url pattern; it's
simple, clean, and easy to read. It's no surprise a developer would want
to do this. The admin does it, why can't anyone else?
It all comes down to how the app is deployed. As an app developer I can
solve the above problems by using the following patterns, which are
simplified variants on what `contrib.admin` is doing:
{{{
#!python
# usefulapp/__init__.py
app_label = __name__.split('.')[-1]
default_ns = app_label # our default *instance* namespace
def urls_ns(namespace=default_ns):
"""
Returns a 3-tuple of url patterns registered under the given namespace
for use with include().
"""
# In for to use reverse() in our views, we need to provide them
`current_app`
kwargs = {'current_app': default_ns}
urlpatterns = [
url('^one-thing/$', views.one_thing, name='one_thing',
kwargs=kwargs),
url('^another-thing/$', views.another_thing, name='another_thing',
kwargs=kwargs)
]
return (urpatterns, app_label, namespace)
# Allow the use of `include(usefulapp.urls)`.
# Note that we don't have a urls.py in the top-level package.
urls = urls_ns()
}}}
Our views must accept the current_app kwarg so that we can use reverse()
{{{
#!python
# usefulapp/views.py
def one_thing(request, current_app=None):
context = {
'current_app': current_app
'another_url': reverse('usefulapp:another_thing',
current_app=current_app)
}
return render(request, 'usefulapp/one_thing.html', context)
def another_thing(request, current_app=None):
context = {'current_app': current_app}
return render(request, 'usefulapp/another_thing.html', context)
}}}
Since our context now has `current_app` in it, the {% url %} tag will work
without it:
{{{
#!django
<a href="{% url "another_thing" %}">Well isn't that special...</a>
}}}
Now, an app developer can deploy our app without having to worry about
registering a namespace (the registration occurs by including the
3-tuple):
{{{
#!python
# my_project/root_urlconf.py
import usefulapp
urlpatterns = [
#...
url('^some-things', include(usefulapp.urls))
]
}}}
*But* if they want to use namespacing, they can't use the `namespace`
kwarg with our `urls` tuple. The `include()` function forbids re-
namespacing a 3-tuple, though I'm not sure why. The solution is to
instruct them to use of our `urls_ns()` function instead:
{{{
#!python
#some_big_project/urls.py
urlpatterns = [
#...
url('^some-things', include(usefulapp.urls_ns('somethings')),
url('^other-things', include(usefulapp.urls_ns('otherthings'))
]
}}}
I think a the solution for this would involve a more robust API for
defining urlpatterns, possibly involving AppConfig. It would be nice if we
didn't have to do so much passing around of `current_app`, but I'm not
sure how we'd make that more transparent.
Also, I think the `app_name` argument is pretty pointless -- I can't
imagine a use case of changing `app_name` that wouldn't be covered by just
creating another namespace. Unless there's a legitimate use case for
changing it, it should not be part of the public API (let me know if I'm
wrong here).
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:5>
Comment (by mrmachine):
One weird thing about the examples above (which I completely agree with),
why is the app name AND a `current_app` hint required for `reverse` but
not for `{% url %}`? E.g. `reverse('usefulapp:another_thing',
current_app=current_app)` vs `{% url "another_thing" %}` (with the
`current_app` hint set on the context)?
I really think that the current state of namespacing is broken. Yes, it
was intended to allow multiple deployments of the same app, but there is
an obvious and legitimate desire for app authors to name their URLs
uniquely *within their app*, and allow project authors to specify a
namespace to avoid conflicts between apps that know nothing about each
other.
Currently, this requires explicit additional repetitive work by app
authors during development *and* explicit installation instructions to
project authors.
Django knows which URL matches the request path. It knows the namespace
that a project author might have optionally specified for that URLconf.
Django should use that automatically when using `reverse()` and `{% url
%}`. App authors can then write their apps without any worry about
additional code or explicit installation instructions. They can name their
URLs without worrying about conflicts with other apps, as long as they are
unique within *their* app. Then project authors can install any app with
any namespace they like.
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:6>
Comment (by bendavis78):
@mrmachine, `current_app` isn't necessarily available in every template
context. It has to be explicitly set. For example, `contrib.admin` sets
current app as a property on AdminSite, and explicitly adds it to the
context. `contrib.auth` passes around current_app in its view kwargs, and
explicitly sets it when rendering templates.
I don't think "keeping an apps url's from conflicting with each other" is
a reason to fix namespaces, as you can already do that with urlname
prefixes. The reason namespaces need fixing is to improve the API for
deploying multiple instances of the same apps at different urls. It just
so happens it has the added side effect/benefit of allowing devs to avoid
prefixing urlnames.
IMO the API needs to be changed so that **all** urls are put into a
namespace, the default of which is the application's app_label. That would
maket things much easier for app users and app devs alike.
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:7>
* cc: bendavis78 (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:8>
Comment (by mrmachine):
@bendavis78, I agree that "keeping an app's URLs from conflicting with
each other" alone is not reason enough for significant change. But it is a
nice side effect, and not one that should deter us from making significant
change.
On `current_app`, Django could set a default hint for it in request
middleware (or before middleware runs) in thread local storage. Then
Django could use that hint (again, as a default only) any time `reverse()`
or `{% url %}` is called.
There is a problem with forcing ALL included app URLs into a namespace. It
makes it impossible for project developers to override the location of a
particular URL provided by a particular app. For example, generic app
`foo` is installed at `/foo/`. It provides a URL named `bar` at
`/foo/bar/`. If a project developer wants to shorten just that URL to
`/bar/`, he can't if it has been installed in a namespace.
If this problem could be worked around, e.g. by allowing a namespace to be
given to a single URL included in the root URLconf (not only when one
URLconf is included into another), and have that URL be detected first and
therefore overriding the version from the included URLconf, then I would
wholeheartedly support your proposal of giving ALL included URLs a default
namespace that matches their app label.
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:9>
* cc: dries@… (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:10>
Comment (by aaugustin):
According to the summary, "the only use case for not using the application
label [as the application namespace] is name conflicts".
If that's correct, we can drop application namespaces because unicity of
application labels is enforced since Django 1.7.
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:11>
* cc: apollo13 (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:12>
* cc: info+coding@… (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:13>
* cc: marten.knbk@… (added)
* owner: nobody => knbk
* status: new => assigned
Comment:
New proposal at https://groups.google.com/forum/#!topic/django-
developers/B68wBMpGX20.
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:14>
* has_patch: 0 => 1
Comment:
PR: https://github.com/django/django/pull/4724
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:15>
* needs_better_patch: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:16>
* needs_better_patch: 1 => 0
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:17>
* status: assigned => closed
* resolution: => fixed
Comment:
In [changeset:"1e82094f1b6690018228e688303295f83e1c3d9a" 1e82094]:
{{{
#!CommitTicketReference repository=""
revision="1e82094f1b6690018228e688303295f83e1c3d9a"
Fixed #21927 -- Made application and instance namespaces more distinct.
Made URL application namespaces be set in the included URLconf and
instance namespaces in the call to include(). Deprecated other ways
to set application and instance namespaces.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:18>
Comment (by Tim Graham <timograham@…>):
In [changeset:"0e723ead5289f64d6fd648104c2aa741394060de" 0e723ead]:
{{{
#!CommitTicketReference repository=""
revision="0e723ead5289f64d6fd648104c2aa741394060de"
Refs #21927 -- Added examples to urls.include() changes in 1.9 release
notes.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:19>
Comment (by Tim Graham <timograham@…>):
In [changeset:"6687f4dcbbfeca81b76233609fca90f30ce4dd25" 6687f4d]:
{{{
#!CommitTicketReference repository=""
revision="6687f4dcbbfeca81b76233609fca90f30ce4dd25"
Refs #21927 -- Added note about include() to tutorial.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:20>
Comment (by Tim Graham <timograham@…>):
In [changeset:"b37cb0b95878c9356693f129334778f00f44a1bc" b37cb0b9]:
{{{
#!CommitTicketReference repository=""
revision="b37cb0b95878c9356693f129334778f00f44a1bc"
[1.9.x] Refs #21927 -- Added note about include() to tutorial.
Backport of 6687f4dcbbfeca81b76233609fca90f30ce4dd25 from master
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:21>
Comment (by Tim Graham <timograham@…>):
In [changeset:"ad393beeb71e8774e4bf9ad842b97022e50f1231" ad393bee]:
{{{
#!CommitTicketReference repository=""
revision="ad393beeb71e8774e4bf9ad842b97022e50f1231"
Refs #21927 -- Removed include()'s app_name argument per deprecation
timeline.
Also removed support for passing a 3-tuple to include() and support for
setting an instance namespace without an application namespace.
Thanks Marten Kenbeek for completing the patch.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:22>
Comment (by Tim Graham <timograham@…>):
In [changeset:"6c98c5abdfb8572936992b5395aab001aeb245f2" 6c98c5ab]:
{{{
#!CommitTicketReference repository=""
revision="6c98c5abdfb8572936992b5395aab001aeb245f2"
Refs #21927 -- Removed docs for include()'s old instance_namespace
parameter.
Follow up to ad393beeb71e8774e4bf9ad842b97022e50f1231.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:23>
Comment (by Tim Graham <timograham@…>):
In [changeset:"791f7aa110346d3e05da8325e403a37a5beb589a" 791f7aa]:
{{{
#!CommitTicketReference repository=""
revision="791f7aa110346d3e05da8325e403a37a5beb589a"
[2.0.x] Refs #21927 -- Removed docs for include()'s old instance_namespace
parameter.
Follow up to ad393beeb71e8774e4bf9ad842b97022e50f1231.
Backport of 6c98c5abdfb8572936992b5395aab001aeb245f2 from master
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/21927#comment:24>