Skip to content

Commit 8d48eaa

Browse files
committed
Fixed #10061 -- Added namespacing for named URLs - most importantly, for the admin site, where the absence of this facility was causing problems. Thanks to the many people who contributed to and helped review this patch.
This change is backwards incompatible for anyone that is using the named URLs introduced in [9739]. Any usage of the old admin_XXX names need to be modified to use the new namespaced format; in many cases this will be as simple as a search & replace for "admin_" -> "admin:". See the docs for more details on the new URL names, and the namespace resolution strategy. git-svn-id: http://code.djangoproject.com/svn/django/trunk@11250 bcc190cf-cafb-0310-a4f2-bffc1f526a37
1 parent 9fd19c0 commit 8d48eaa

File tree

20 files changed

+545
-120
lines changed

20 files changed

+545
-120
lines changed

django/conf/urls/defaults.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@
66
handler404 = 'django.views.defaults.page_not_found'
77
handler500 = 'django.views.defaults.server_error'
88

9-
include = lambda urlconf_module: [urlconf_module]
9+
def include(arg, namespace=None, app_name=None):
10+
if isinstance(arg, tuple):
11+
# callable returning a namespace hint
12+
if namespace:
13+
raise ImproperlyConfigured('Cannot override the namespace for a dynamic module that provides a namespace')
14+
urlconf_module, app_name, namespace = arg
15+
else:
16+
# No namespace hint - use manually provided namespace
17+
urlconf_module = arg
18+
return (urlconf_module, app_name, namespace)
1019

1120
def patterns(prefix, *args):
1221
pattern_list = []
@@ -19,9 +28,10 @@ def patterns(prefix, *args):
1928
return pattern_list
2029

2130
def url(regex, view, kwargs=None, name=None, prefix=''):
22-
if type(view) == list:
31+
if isinstance(view, (list,tuple)):
2332
# For include(...) processing.
24-
return RegexURLResolver(regex, view[0], kwargs)
33+
urlconf_module, app_name, namespace = view
34+
return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace)
2535
else:
2636
if isinstance(view, basestring):
2737
if not view:

django/contrib/admin/options.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -226,24 +226,24 @@ def wrapper(*args, **kwargs):
226226
return self.admin_site.admin_view(view)(*args, **kwargs)
227227
return update_wrapper(wrapper, view)
228228

229-
info = self.admin_site.name, self.model._meta.app_label, self.model._meta.module_name
229+
info = self.model._meta.app_label, self.model._meta.module_name
230230

231231
urlpatterns = patterns('',
232232
url(r'^$',
233233
wrap(self.changelist_view),
234-
name='%sadmin_%s_%s_changelist' % info),
234+
name='%s_%s_changelist' % info),
235235
url(r'^add/$',
236236
wrap(self.add_view),
237-
name='%sadmin_%s_%s_add' % info),
237+
name='%s_%s_add' % info),
238238
url(r'^(.+)/history/$',
239239
wrap(self.history_view),
240-
name='%sadmin_%s_%s_history' % info),
240+
name='%s_%s_history' % info),
241241
url(r'^(.+)/delete/$',
242242
wrap(self.delete_view),
243-
name='%sadmin_%s_%s_delete' % info),
243+
name='%s_%s_delete' % info),
244244
url(r'^(.+)/$',
245245
wrap(self.change_view),
246-
name='%sadmin_%s_%s_change' % info),
246+
name='%s_%s_change' % info),
247247
)
248248
return urlpatterns
249249

@@ -582,11 +582,12 @@ def render_change_form(self, request, context, add=False, change=False, form_url
582582
'save_on_top': self.save_on_top,
583583
'root_path': self.admin_site.root_path,
584584
})
585+
context_instance = template.RequestContext(request, current_app=self.admin_site.name)
585586
return render_to_response(self.change_form_template or [
586587
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
587588
"admin/%s/change_form.html" % app_label,
588589
"admin/change_form.html"
589-
], context, context_instance=template.RequestContext(request))
590+
], context, context_instance=context_instance)
590591

591592
def response_add(self, request, obj, post_url_continue='../%s/'):
592593
"""
@@ -977,11 +978,12 @@ def changelist_view(self, request, extra_context=None):
977978
'actions_on_bottom': self.actions_on_bottom,
978979
}
979980
context.update(extra_context or {})
981+
context_instance = template.RequestContext(request, current_app=self.admin_site.name)
980982
return render_to_response(self.change_list_template or [
981983
'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
982984
'admin/%s/change_list.html' % app_label,
983985
'admin/change_list.html'
984-
], context, context_instance=template.RequestContext(request))
986+
], context, context_instance=context_instance)
985987

986988
def delete_view(self, request, object_id, extra_context=None):
987989
"The 'delete' admin view for this model."
@@ -1032,11 +1034,12 @@ def delete_view(self, request, object_id, extra_context=None):
10321034
"app_label": app_label,
10331035
}
10341036
context.update(extra_context or {})
1037+
context_instance = template.RequestContext(request, current_app=self.admin_site.name)
10351038
return render_to_response(self.delete_confirmation_template or [
10361039
"admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
10371040
"admin/%s/delete_confirmation.html" % app_label,
10381041
"admin/delete_confirmation.html"
1039-
], context, context_instance=template.RequestContext(request))
1042+
], context, context_instance=context_instance)
10401043

10411044
def history_view(self, request, object_id, extra_context=None):
10421045
"The 'history' admin view for this model."
@@ -1059,11 +1062,12 @@ def history_view(self, request, object_id, extra_context=None):
10591062
'app_label': app_label,
10601063
}
10611064
context.update(extra_context or {})
1065+
context_instance = template.RequestContext(request, current_app=self.admin_site.name)
10621066
return render_to_response(self.object_history_template or [
10631067
"admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()),
10641068
"admin/%s/object_history.html" % app_label,
10651069
"admin/object_history.html"
1066-
], context, context_instance=template.RequestContext(request))
1070+
], context, context_instance=context_instance)
10671071

10681072
#
10691073
# DEPRECATED methods.

django/contrib/admin/sites.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.contrib.auth import authenticate, login
66
from django.db.models.base import ModelBase
77
from django.core.exceptions import ImproperlyConfigured
8+
from django.core.urlresolvers import reverse
89
from django.shortcuts import render_to_response
910
from django.utils.functional import update_wrapper
1011
from django.utils.safestring import mark_safe
@@ -38,17 +39,14 @@ class AdminSite(object):
3839
login_template = None
3940
app_index_template = None
4041

41-
def __init__(self, name=None):
42+
def __init__(self, name=None, app_name='admin'):
4243
self._registry = {} # model_class class -> admin_class instance
43-
# TODO Root path is used to calculate urls under the old root() method
44-
# in order to maintain backwards compatibility we are leaving that in
45-
# so root_path isn't needed, not sure what to do about this.
46-
self.root_path = 'admin/'
44+
self.root_path = None
4745
if name is None:
48-
name = ''
46+
self.name = 'admin'
4947
else:
50-
name += '_'
51-
self.name = name
48+
self.name = name
49+
self.app_name = app_name
5250
self._actions = {'delete_selected': actions.delete_selected}
5351
self._global_actions = self._actions.copy()
5452

@@ -202,24 +200,24 @@ def wrapper(*args, **kwargs):
202200
urlpatterns = patterns('',
203201
url(r'^$',
204202
wrap(self.index),
205-
name='%sadmin_index' % self.name),
203+
name='index'),
206204
url(r'^logout/$',
207205
wrap(self.logout),
208-
name='%sadmin_logout'),
206+
name='logout'),
209207
url(r'^password_change/$',
210208
wrap(self.password_change, cacheable=True),
211-
name='%sadmin_password_change' % self.name),
209+
name='password_change'),
212210
url(r'^password_change/done/$',
213211
wrap(self.password_change_done, cacheable=True),
214-
name='%sadmin_password_change_done' % self.name),
212+
name='password_change_done'),
215213
url(r'^jsi18n/$',
216214
wrap(self.i18n_javascript, cacheable=True),
217-
name='%sadmin_jsi18n' % self.name),
215+
name='jsi18n'),
218216
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
219217
'django.views.defaults.shortcut'),
220218
url(r'^(?P<app_label>\w+)/$',
221219
wrap(self.app_index),
222-
name='%sadmin_app_list' % self.name),
220+
name='app_list')
223221
)
224222

225223
# Add in each model's views.
@@ -231,16 +229,19 @@ def wrapper(*args, **kwargs):
231229
return urlpatterns
232230

233231
def urls(self):
234-
return self.get_urls()
232+
return self.get_urls(), self.app_name, self.name
235233
urls = property(urls)
236234

237235
def password_change(self, request):
238236
"""
239237
Handles the "change password" task -- both form display and validation.
240238
"""
241239
from django.contrib.auth.views import password_change
242-
return password_change(request,
243-
post_change_redirect='%spassword_change/done/' % self.root_path)
240+
if self.root_path is not None:
241+
url = '%spassword_change/done/' % self.root_path
242+
else:
243+
url = reverse('admin:password_change_done', current_app=self.name)
244+
return password_change(request, post_change_redirect=url)
244245

245246
def password_change_done(self, request):
246247
"""
@@ -368,8 +369,9 @@ def index(self, request, extra_context=None):
368369
'root_path': self.root_path,
369370
}
370371
context.update(extra_context or {})
372+
context_instance = template.RequestContext(request, current_app=self.name)
371373
return render_to_response(self.index_template or 'admin/index.html', context,
372-
context_instance=template.RequestContext(request)
374+
context_instance=context_instance
373375
)
374376
index = never_cache(index)
375377

@@ -382,8 +384,9 @@ def display_login_form(self, request, error_message='', extra_context=None):
382384
'root_path': self.root_path,
383385
}
384386
context.update(extra_context or {})
387+
context_instance = template.RequestContext(request, current_app=self.name)
385388
return render_to_response(self.login_template or 'admin/login.html', context,
386-
context_instance=template.RequestContext(request)
389+
context_instance=context_instance
387390
)
388391

389392
def app_index(self, request, app_label, extra_context=None):
@@ -425,9 +428,10 @@ def app_index(self, request, app_label, extra_context=None):
425428
'root_path': self.root_path,
426429
}
427430
context.update(extra_context or {})
431+
context_instance = template.RequestContext(request, current_app=self.name)
428432
return render_to_response(self.app_index_template or ('admin/%s/app_index.html' % app_label,
429433
'admin/app_index.html'), context,
430-
context_instance=template.RequestContext(request)
434+
context_instance=context_instance
431435
)
432436

433437
def root(self, request, url):

django/contrib/admin/templates/admin/base.html

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,30 @@
2323
{% block branding %}{% endblock %}
2424
</div>
2525
{% if user.is_authenticated and user.is_staff %}
26-
<div id="user-tools">{% trans 'Welcome,' %} <strong>{% firstof user.first_name user.username %}</strong>. {% block userlinks %}{% url django-admindocs-docroot as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> / {% endif %}<a href="{{ root_path }}password_change/">{% trans 'Change password' %}</a> / <a href="{{ root_path }}logout/">{% trans 'Log out' %}</a>{% endblock %}</div>
26+
<div id="user-tools">
27+
{% trans 'Welcome,' %}
28+
<strong>{% firstof user.first_name user.username %}</strong>.
29+
{% block userlinks %}
30+
{% url django-admindocs-docroot as docsroot %}
31+
{% if docsroot %}
32+
<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> /
33+
{% endif %}
34+
{% url admin:password_change as password_change_url %}
35+
{% if password_change_url %}
36+
<a href="{{ password_change_url }}">
37+
{% else %}
38+
<a href="{{ root_path }}password_change/">
39+
{% endif %}
40+
{% trans 'Change password' %}</a> /
41+
{% url admin:logout as logout_url %}
42+
{% if logout_url %}
43+
<a href="{{ logout_url }}">
44+
{% else %}
45+
<a href="{{ root_path }}logout/">
46+
{% endif %}
47+
{% trans 'Log out' %}</a>
48+
{% endblock %}
49+
</div>
2750
{% endif %}
2851
{% block nav-global %}{% endblock %}
2952
</div>

django/contrib/admin/widgets.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def render(self, name, value, attrs=None):
125125
if value:
126126
output.append(self.label_for_value(value))
127127
return mark_safe(u''.join(output))
128-
128+
129129
def base_url_parameters(self):
130130
params = {}
131131
if self.rel.limit_choices_to:
@@ -137,14 +137,14 @@ def base_url_parameters(self):
137137
v = str(v)
138138
items.append((k, v))
139139
params.update(dict(items))
140-
return params
141-
140+
return params
141+
142142
def url_parameters(self):
143143
from django.contrib.admin.views.main import TO_FIELD_VAR
144144
params = self.base_url_parameters()
145145
params.update({TO_FIELD_VAR: self.rel.get_related_field().name})
146146
return params
147-
147+
148148
def label_for_value(self, value):
149149
key = self.rel.get_related_field().name
150150
obj = self.rel.to._default_manager.get(**{key: value})
@@ -165,10 +165,10 @@ def render(self, name, value, attrs=None):
165165
else:
166166
value = ''
167167
return super(ManyToManyRawIdWidget, self).render(name, value, attrs)
168-
168+
169169
def url_parameters(self):
170170
return self.base_url_parameters()
171-
171+
172172
def label_for_value(self, value):
173173
return ''
174174

@@ -222,8 +222,7 @@ def render(self, name, value, *args, **kwargs):
222222
rel_to = self.rel.to
223223
info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
224224
try:
225-
related_info = (self.admin_site.name,) + info
226-
related_url = reverse('%sadmin_%s_%s_add' % related_info)
225+
related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
227226
except NoReverseMatch:
228227
related_url = '../../../%s/%s/add/' % info
229228
self.widget.choices = self.choices

django/contrib/admindocs/templates/admin_doc/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{% extends "admin/base_site.html" %}
22
{% load i18n %}
3-
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> &rsaquo; Documentation</div>{% endblock %}
3+
{% block breadcrumbs %}<div class="breadcrumbs"><a href="{{ root_path }}">Home</a> &rsaquo; Documentation</div>{% endblock %}
44
{% block title %}Documentation{% endblock %}
55

66
{% block content %}

django/contrib/admindocs/views.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@ class GenericSite(object):
2222
name = 'my site'
2323

2424
def get_root_path():
25-
from django.contrib import admin
2625
try:
27-
return urlresolvers.reverse(admin.site.root, args=[''])
26+
return urlresolvers.reverse('admin:index')
2827
except urlresolvers.NoReverseMatch:
29-
return getattr(settings, "ADMIN_SITE_ROOT_URL", "/admin/")
28+
from django.contrib import admin
29+
try:
30+
return urlresolvers.reverse(admin.site.root, args=[''])
31+
except urlresolvers.NoReverseMatch:
32+
return getattr(settings, "ADMIN_SITE_ROOT_URL", "/admin/")
3033

3134
def doc_index(request):
3235
if not utils.docutils_is_available:
@@ -179,7 +182,7 @@ def model_index(request):
179182
def model_detail(request, app_label, model_name):
180183
if not utils.docutils_is_available:
181184
return missing_docutils_page(request)
182-
185+
183186
# Get the model class.
184187
try:
185188
app_mod = models.get_app(app_label)

0 commit comments

Comments
 (0)