mirror of https://github.com/django/django.git
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
This commit is contained in:
parent
9fd19c0161
commit
8d48eaa064
|
@ -6,7 +6,16 @@ __all__ = ['handler404', 'handler500', 'include', 'patterns', 'url']
|
|||
handler404 = 'django.views.defaults.page_not_found'
|
||||
handler500 = 'django.views.defaults.server_error'
|
||||
|
||||
include = lambda urlconf_module: [urlconf_module]
|
||||
def include(arg, namespace=None, app_name=None):
|
||||
if isinstance(arg, tuple):
|
||||
# callable returning a namespace hint
|
||||
if namespace:
|
||||
raise ImproperlyConfigured('Cannot override the namespace for a dynamic module that provides a namespace')
|
||||
urlconf_module, app_name, namespace = arg
|
||||
else:
|
||||
# No namespace hint - use manually provided namespace
|
||||
urlconf_module = arg
|
||||
return (urlconf_module, app_name, namespace)
|
||||
|
||||
def patterns(prefix, *args):
|
||||
pattern_list = []
|
||||
|
@ -19,9 +28,10 @@ def patterns(prefix, *args):
|
|||
return pattern_list
|
||||
|
||||
def url(regex, view, kwargs=None, name=None, prefix=''):
|
||||
if type(view) == list:
|
||||
if isinstance(view, (list,tuple)):
|
||||
# For include(...) processing.
|
||||
return RegexURLResolver(regex, view[0], kwargs)
|
||||
urlconf_module, app_name, namespace = view
|
||||
return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace)
|
||||
else:
|
||||
if isinstance(view, basestring):
|
||||
if not view:
|
||||
|
|
|
@ -226,24 +226,24 @@ class ModelAdmin(BaseModelAdmin):
|
|||
return self.admin_site.admin_view(view)(*args, **kwargs)
|
||||
return update_wrapper(wrapper, view)
|
||||
|
||||
info = self.admin_site.name, self.model._meta.app_label, self.model._meta.module_name
|
||||
info = self.model._meta.app_label, self.model._meta.module_name
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$',
|
||||
wrap(self.changelist_view),
|
||||
name='%sadmin_%s_%s_changelist' % info),
|
||||
name='%s_%s_changelist' % info),
|
||||
url(r'^add/$',
|
||||
wrap(self.add_view),
|
||||
name='%sadmin_%s_%s_add' % info),
|
||||
name='%s_%s_add' % info),
|
||||
url(r'^(.+)/history/$',
|
||||
wrap(self.history_view),
|
||||
name='%sadmin_%s_%s_history' % info),
|
||||
name='%s_%s_history' % info),
|
||||
url(r'^(.+)/delete/$',
|
||||
wrap(self.delete_view),
|
||||
name='%sadmin_%s_%s_delete' % info),
|
||||
name='%s_%s_delete' % info),
|
||||
url(r'^(.+)/$',
|
||||
wrap(self.change_view),
|
||||
name='%sadmin_%s_%s_change' % info),
|
||||
name='%s_%s_change' % info),
|
||||
)
|
||||
return urlpatterns
|
||||
|
||||
|
@ -582,11 +582,12 @@ class ModelAdmin(BaseModelAdmin):
|
|||
'save_on_top': self.save_on_top,
|
||||
'root_path': self.admin_site.root_path,
|
||||
})
|
||||
context_instance = template.RequestContext(request, current_app=self.admin_site.name)
|
||||
return render_to_response(self.change_form_template or [
|
||||
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
|
||||
"admin/%s/change_form.html" % app_label,
|
||||
"admin/change_form.html"
|
||||
], context, context_instance=template.RequestContext(request))
|
||||
], context, context_instance=context_instance)
|
||||
|
||||
def response_add(self, request, obj, post_url_continue='../%s/'):
|
||||
"""
|
||||
|
@ -977,11 +978,12 @@ class ModelAdmin(BaseModelAdmin):
|
|||
'actions_on_bottom': self.actions_on_bottom,
|
||||
}
|
||||
context.update(extra_context or {})
|
||||
context_instance = template.RequestContext(request, current_app=self.admin_site.name)
|
||||
return render_to_response(self.change_list_template or [
|
||||
'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
|
||||
'admin/%s/change_list.html' % app_label,
|
||||
'admin/change_list.html'
|
||||
], context, context_instance=template.RequestContext(request))
|
||||
], context, context_instance=context_instance)
|
||||
|
||||
def delete_view(self, request, object_id, extra_context=None):
|
||||
"The 'delete' admin view for this model."
|
||||
|
@ -1032,11 +1034,12 @@ class ModelAdmin(BaseModelAdmin):
|
|||
"app_label": app_label,
|
||||
}
|
||||
context.update(extra_context or {})
|
||||
context_instance = template.RequestContext(request, current_app=self.admin_site.name)
|
||||
return render_to_response(self.delete_confirmation_template or [
|
||||
"admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
|
||||
"admin/%s/delete_confirmation.html" % app_label,
|
||||
"admin/delete_confirmation.html"
|
||||
], context, context_instance=template.RequestContext(request))
|
||||
], context, context_instance=context_instance)
|
||||
|
||||
def history_view(self, request, object_id, extra_context=None):
|
||||
"The 'history' admin view for this model."
|
||||
|
@ -1059,11 +1062,12 @@ class ModelAdmin(BaseModelAdmin):
|
|||
'app_label': app_label,
|
||||
}
|
||||
context.update(extra_context or {})
|
||||
context_instance = template.RequestContext(request, current_app=self.admin_site.name)
|
||||
return render_to_response(self.object_history_template or [
|
||||
"admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()),
|
||||
"admin/%s/object_history.html" % app_label,
|
||||
"admin/object_history.html"
|
||||
], context, context_instance=template.RequestContext(request))
|
||||
], context, context_instance=context_instance)
|
||||
|
||||
#
|
||||
# DEPRECATED methods.
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.contrib.admin import actions
|
|||
from django.contrib.auth import authenticate, login
|
||||
from django.db.models.base import ModelBase
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import render_to_response
|
||||
from django.utils.functional import update_wrapper
|
||||
from django.utils.safestring import mark_safe
|
||||
|
@ -38,17 +39,14 @@ class AdminSite(object):
|
|||
login_template = None
|
||||
app_index_template = None
|
||||
|
||||
def __init__(self, name=None):
|
||||
def __init__(self, name=None, app_name='admin'):
|
||||
self._registry = {} # model_class class -> admin_class instance
|
||||
# TODO Root path is used to calculate urls under the old root() method
|
||||
# in order to maintain backwards compatibility we are leaving that in
|
||||
# so root_path isn't needed, not sure what to do about this.
|
||||
self.root_path = 'admin/'
|
||||
self.root_path = None
|
||||
if name is None:
|
||||
name = ''
|
||||
self.name = 'admin'
|
||||
else:
|
||||
name += '_'
|
||||
self.name = name
|
||||
self.name = name
|
||||
self.app_name = app_name
|
||||
self._actions = {'delete_selected': actions.delete_selected}
|
||||
self._global_actions = self._actions.copy()
|
||||
|
||||
|
@ -202,24 +200,24 @@ class AdminSite(object):
|
|||
urlpatterns = patterns('',
|
||||
url(r'^$',
|
||||
wrap(self.index),
|
||||
name='%sadmin_index' % self.name),
|
||||
name='index'),
|
||||
url(r'^logout/$',
|
||||
wrap(self.logout),
|
||||
name='%sadmin_logout'),
|
||||
name='logout'),
|
||||
url(r'^password_change/$',
|
||||
wrap(self.password_change, cacheable=True),
|
||||
name='%sadmin_password_change' % self.name),
|
||||
name='password_change'),
|
||||
url(r'^password_change/done/$',
|
||||
wrap(self.password_change_done, cacheable=True),
|
||||
name='%sadmin_password_change_done' % self.name),
|
||||
name='password_change_done'),
|
||||
url(r'^jsi18n/$',
|
||||
wrap(self.i18n_javascript, cacheable=True),
|
||||
name='%sadmin_jsi18n' % self.name),
|
||||
name='jsi18n'),
|
||||
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
|
||||
'django.views.defaults.shortcut'),
|
||||
url(r'^(?P<app_label>\w+)/$',
|
||||
wrap(self.app_index),
|
||||
name='%sadmin_app_list' % self.name),
|
||||
name='app_list')
|
||||
)
|
||||
|
||||
# Add in each model's views.
|
||||
|
@ -231,7 +229,7 @@ class AdminSite(object):
|
|||
return urlpatterns
|
||||
|
||||
def urls(self):
|
||||
return self.get_urls()
|
||||
return self.get_urls(), self.app_name, self.name
|
||||
urls = property(urls)
|
||||
|
||||
def password_change(self, request):
|
||||
|
@ -239,8 +237,11 @@ class AdminSite(object):
|
|||
Handles the "change password" task -- both form display and validation.
|
||||
"""
|
||||
from django.contrib.auth.views import password_change
|
||||
return password_change(request,
|
||||
post_change_redirect='%spassword_change/done/' % self.root_path)
|
||||
if self.root_path is not None:
|
||||
url = '%spassword_change/done/' % self.root_path
|
||||
else:
|
||||
url = reverse('admin:password_change_done', current_app=self.name)
|
||||
return password_change(request, post_change_redirect=url)
|
||||
|
||||
def password_change_done(self, request):
|
||||
"""
|
||||
|
@ -368,8 +369,9 @@ class AdminSite(object):
|
|||
'root_path': self.root_path,
|
||||
}
|
||||
context.update(extra_context or {})
|
||||
context_instance = template.RequestContext(request, current_app=self.name)
|
||||
return render_to_response(self.index_template or 'admin/index.html', context,
|
||||
context_instance=template.RequestContext(request)
|
||||
context_instance=context_instance
|
||||
)
|
||||
index = never_cache(index)
|
||||
|
||||
|
@ -382,8 +384,9 @@ class AdminSite(object):
|
|||
'root_path': self.root_path,
|
||||
}
|
||||
context.update(extra_context or {})
|
||||
context_instance = template.RequestContext(request, current_app=self.name)
|
||||
return render_to_response(self.login_template or 'admin/login.html', context,
|
||||
context_instance=template.RequestContext(request)
|
||||
context_instance=context_instance
|
||||
)
|
||||
|
||||
def app_index(self, request, app_label, extra_context=None):
|
||||
|
@ -425,9 +428,10 @@ class AdminSite(object):
|
|||
'root_path': self.root_path,
|
||||
}
|
||||
context.update(extra_context or {})
|
||||
context_instance = template.RequestContext(request, current_app=self.name)
|
||||
return render_to_response(self.app_index_template or ('admin/%s/app_index.html' % app_label,
|
||||
'admin/app_index.html'), context,
|
||||
context_instance=template.RequestContext(request)
|
||||
context_instance=context_instance
|
||||
)
|
||||
|
||||
def root(self, request, url):
|
||||
|
|
|
@ -23,7 +23,30 @@
|
|||
{% block branding %}{% endblock %}
|
||||
</div>
|
||||
{% if user.is_authenticated and user.is_staff %}
|
||||
<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>
|
||||
<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 %}
|
||||
{% url admin:password_change as password_change_url %}
|
||||
{% if password_change_url %}
|
||||
<a href="{{ password_change_url }}">
|
||||
{% else %}
|
||||
<a href="{{ root_path }}password_change/">
|
||||
{% endif %}
|
||||
{% trans 'Change password' %}</a> /
|
||||
{% url admin:logout as logout_url %}
|
||||
{% if logout_url %}
|
||||
<a href="{{ logout_url }}">
|
||||
{% else %}
|
||||
<a href="{{ root_path }}logout/">
|
||||
{% endif %}
|
||||
{% trans 'Log out' %}</a>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block nav-global %}{% endblock %}
|
||||
</div>
|
||||
|
|
|
@ -125,7 +125,7 @@ class ForeignKeyRawIdWidget(forms.TextInput):
|
|||
if value:
|
||||
output.append(self.label_for_value(value))
|
||||
return mark_safe(u''.join(output))
|
||||
|
||||
|
||||
def base_url_parameters(self):
|
||||
params = {}
|
||||
if self.rel.limit_choices_to:
|
||||
|
@ -137,14 +137,14 @@ class ForeignKeyRawIdWidget(forms.TextInput):
|
|||
v = str(v)
|
||||
items.append((k, v))
|
||||
params.update(dict(items))
|
||||
return params
|
||||
|
||||
return params
|
||||
|
||||
def url_parameters(self):
|
||||
from django.contrib.admin.views.main import TO_FIELD_VAR
|
||||
params = self.base_url_parameters()
|
||||
params.update({TO_FIELD_VAR: self.rel.get_related_field().name})
|
||||
return params
|
||||
|
||||
|
||||
def label_for_value(self, value):
|
||||
key = self.rel.get_related_field().name
|
||||
obj = self.rel.to._default_manager.get(**{key: value})
|
||||
|
@ -165,10 +165,10 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
|
|||
else:
|
||||
value = ''
|
||||
return super(ManyToManyRawIdWidget, self).render(name, value, attrs)
|
||||
|
||||
|
||||
def url_parameters(self):
|
||||
return self.base_url_parameters()
|
||||
|
||||
|
||||
def label_for_value(self, value):
|
||||
return ''
|
||||
|
||||
|
@ -222,8 +222,7 @@ class RelatedFieldWidgetWrapper(forms.Widget):
|
|||
rel_to = self.rel.to
|
||||
info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
|
||||
try:
|
||||
related_info = (self.admin_site.name,) + info
|
||||
related_url = reverse('%sadmin_%s_%s_add' % related_info)
|
||||
related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
|
||||
except NoReverseMatch:
|
||||
related_url = '../../../%s/%s/add/' % info
|
||||
self.widget.choices = self.choices
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> › Documentation</div>{% endblock %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="{{ root_path }}">Home</a> › Documentation</div>{% endblock %}
|
||||
{% block title %}Documentation{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
|
|
@ -22,11 +22,14 @@ class GenericSite(object):
|
|||
name = 'my site'
|
||||
|
||||
def get_root_path():
|
||||
from django.contrib import admin
|
||||
try:
|
||||
return urlresolvers.reverse(admin.site.root, args=[''])
|
||||
return urlresolvers.reverse('admin:index')
|
||||
except urlresolvers.NoReverseMatch:
|
||||
return getattr(settings, "ADMIN_SITE_ROOT_URL", "/admin/")
|
||||
from django.contrib import admin
|
||||
try:
|
||||
return urlresolvers.reverse(admin.site.root, args=[''])
|
||||
except urlresolvers.NoReverseMatch:
|
||||
return getattr(settings, "ADMIN_SITE_ROOT_URL", "/admin/")
|
||||
|
||||
def doc_index(request):
|
||||
if not utils.docutils_is_available:
|
||||
|
@ -179,7 +182,7 @@ model_index = staff_member_required(model_index)
|
|||
def model_detail(request, app_label, model_name):
|
||||
if not utils.docutils_is_available:
|
||||
return missing_docutils_page(request)
|
||||
|
||||
|
||||
# Get the model class.
|
||||
try:
|
||||
app_mod = models.get_app(app_label)
|
||||
|
|
|
@ -139,7 +139,7 @@ class RegexURLPattern(object):
|
|||
callback = property(_get_callback)
|
||||
|
||||
class RegexURLResolver(object):
|
||||
def __init__(self, regex, urlconf_name, default_kwargs=None):
|
||||
def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
|
||||
# regex is a string representing a regular expression.
|
||||
# urlconf_name is a string representing the module containing URLconfs.
|
||||
self.regex = re.compile(regex, re.UNICODE)
|
||||
|
@ -148,19 +148,29 @@ class RegexURLResolver(object):
|
|||
self._urlconf_module = self.urlconf_name
|
||||
self.callback = None
|
||||
self.default_kwargs = default_kwargs or {}
|
||||
self._reverse_dict = MultiValueDict()
|
||||
self.namespace = namespace
|
||||
self.app_name = app_name
|
||||
self._reverse_dict = None
|
||||
self._namespace_dict = None
|
||||
self._app_dict = None
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %s %s>' % (self.__class__.__name__, self.urlconf_name, self.regex.pattern)
|
||||
return '<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)
|
||||
|
||||
def _get_reverse_dict(self):
|
||||
if not self._reverse_dict:
|
||||
lookups = MultiValueDict()
|
||||
for pattern in reversed(self.url_patterns):
|
||||
p_pattern = pattern.regex.pattern
|
||||
if p_pattern.startswith('^'):
|
||||
p_pattern = p_pattern[1:]
|
||||
if isinstance(pattern, RegexURLResolver):
|
||||
def _populate(self):
|
||||
lookups = MultiValueDict()
|
||||
namespaces = {}
|
||||
apps = {}
|
||||
for pattern in reversed(self.url_patterns):
|
||||
p_pattern = pattern.regex.pattern
|
||||
if p_pattern.startswith('^'):
|
||||
p_pattern = p_pattern[1:]
|
||||
if isinstance(pattern, RegexURLResolver):
|
||||
if pattern.namespace:
|
||||
namespaces[pattern.namespace] = (p_pattern, pattern)
|
||||
if pattern.app_name:
|
||||
apps.setdefault(pattern.app_name, []).append(pattern.namespace)
|
||||
else:
|
||||
parent = normalize(pattern.regex.pattern)
|
||||
for name in pattern.reverse_dict:
|
||||
for matches, pat in pattern.reverse_dict.getlist(name):
|
||||
|
@ -168,14 +178,36 @@ class RegexURLResolver(object):
|
|||
for piece, p_args in parent:
|
||||
new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches])
|
||||
lookups.appendlist(name, (new_matches, p_pattern + pat))
|
||||
else:
|
||||
bits = normalize(p_pattern)
|
||||
lookups.appendlist(pattern.callback, (bits, p_pattern))
|
||||
lookups.appendlist(pattern.name, (bits, p_pattern))
|
||||
self._reverse_dict = lookups
|
||||
for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items():
|
||||
namespaces[namespace] = (p_pattern + prefix, sub_pattern)
|
||||
for app_name, namespace_list in pattern.app_dict.items():
|
||||
apps.setdefault(app_name, []).extend(namespace_list)
|
||||
else:
|
||||
bits = normalize(p_pattern)
|
||||
lookups.appendlist(pattern.callback, (bits, p_pattern))
|
||||
lookups.appendlist(pattern.name, (bits, p_pattern))
|
||||
self._reverse_dict = lookups
|
||||
self._namespace_dict = namespaces
|
||||
self._app_dict = apps
|
||||
|
||||
def _get_reverse_dict(self):
|
||||
if self._reverse_dict is None:
|
||||
self._populate()
|
||||
return self._reverse_dict
|
||||
reverse_dict = property(_get_reverse_dict)
|
||||
|
||||
def _get_namespace_dict(self):
|
||||
if self._namespace_dict is None:
|
||||
self._populate()
|
||||
return self._namespace_dict
|
||||
namespace_dict = property(_get_namespace_dict)
|
||||
|
||||
def _get_app_dict(self):
|
||||
if self._app_dict is None:
|
||||
self._populate()
|
||||
return self._app_dict
|
||||
app_dict = property(_get_app_dict)
|
||||
|
||||
def resolve(self, path):
|
||||
tried = []
|
||||
match = self.regex.search(path)
|
||||
|
@ -261,12 +293,51 @@ class RegexURLResolver(object):
|
|||
def resolve(path, urlconf=None):
|
||||
return get_resolver(urlconf).resolve(path)
|
||||
|
||||
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None):
|
||||
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None):
|
||||
resolver = get_resolver(urlconf)
|
||||
args = args or []
|
||||
kwargs = kwargs or {}
|
||||
|
||||
if prefix is None:
|
||||
prefix = get_script_prefix()
|
||||
return iri_to_uri(u'%s%s' % (prefix, get_resolver(urlconf).reverse(viewname,
|
||||
|
||||
if not isinstance(viewname, basestring):
|
||||
view = viewname
|
||||
else:
|
||||
parts = viewname.split(':')
|
||||
parts.reverse()
|
||||
view = parts[0]
|
||||
path = parts[1:]
|
||||
|
||||
resolved_path = []
|
||||
while path:
|
||||
ns = path.pop()
|
||||
|
||||
# Lookup the name to see if it could be an app identifier
|
||||
try:
|
||||
app_list = resolver.app_dict[ns]
|
||||
# Yes! Path part matches an app in the current Resolver
|
||||
if current_app and current_app in app_list:
|
||||
# If we are reversing for a particular app, use that namespace
|
||||
ns = current_app
|
||||
elif ns not in app_list:
|
||||
# The name isn't shared by one of the instances (i.e., the default)
|
||||
# so just pick the first instance as the default.
|
||||
ns = app_list[0]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
extra, resolver = resolver.namespace_dict[ns]
|
||||
resolved_path.append(ns)
|
||||
prefix = prefix + extra
|
||||
except KeyError, key:
|
||||
if resolved_path:
|
||||
raise NoReverseMatch("%s is not a registered namespace inside '%s'" % (key, ':'.join(resolved_path)))
|
||||
else:
|
||||
raise NoReverseMatch("%s is not a registered namespace" % key)
|
||||
|
||||
return iri_to_uri(u'%s%s' % (prefix, resolver.reverse(view,
|
||||
*args, **kwargs)))
|
||||
|
||||
def clear_url_caches():
|
||||
|
|
|
@ -9,10 +9,11 @@ class ContextPopException(Exception):
|
|||
|
||||
class Context(object):
|
||||
"A stack container for variable context"
|
||||
def __init__(self, dict_=None, autoescape=True):
|
||||
def __init__(self, dict_=None, autoescape=True, current_app=None):
|
||||
dict_ = dict_ or {}
|
||||
self.dicts = [dict_]
|
||||
self.autoescape = autoescape
|
||||
self.current_app = current_app
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.dicts)
|
||||
|
@ -96,8 +97,8 @@ class RequestContext(Context):
|
|||
Additional processors can be specified as a list of callables
|
||||
using the "processors" keyword argument.
|
||||
"""
|
||||
def __init__(self, request, dict=None, processors=None):
|
||||
Context.__init__(self, dict)
|
||||
def __init__(self, request, dict=None, processors=None, current_app=None):
|
||||
Context.__init__(self, dict, current_app=current_app)
|
||||
if processors is None:
|
||||
processors = ()
|
||||
else:
|
||||
|
|
|
@ -367,17 +367,17 @@ class URLNode(Node):
|
|||
# {% url ... as var %} construct in which cause return nothing.
|
||||
url = ''
|
||||
try:
|
||||
url = reverse(self.view_name, args=args, kwargs=kwargs)
|
||||
url = reverse(self.view_name, args=args, kwargs=kwargs, current_app=context.current_app)
|
||||
except NoReverseMatch, e:
|
||||
if settings.SETTINGS_MODULE:
|
||||
project_name = settings.SETTINGS_MODULE.split('.')[0]
|
||||
try:
|
||||
url = reverse(project_name + '.' + self.view_name,
|
||||
args=args, kwargs=kwargs)
|
||||
args=args, kwargs=kwargs, current_app=context.current_app)
|
||||
except NoReverseMatch:
|
||||
if self.asvar is None:
|
||||
# Re-raise the original exception, not the one with
|
||||
# the path relative to the project. This makes a
|
||||
# the path relative to the project. This makes a
|
||||
# better error message.
|
||||
raise e
|
||||
else:
|
||||
|
|
|
@ -1242,7 +1242,7 @@ or :attr:`AdminSite.login_template` properties.
|
|||
``AdminSite`` objects
|
||||
=====================
|
||||
|
||||
.. class:: AdminSite
|
||||
.. class:: AdminSite(name=None)
|
||||
|
||||
A Django administrative site is represented by an instance of
|
||||
``django.contrib.admin.sites.AdminSite``; by default, an instance of
|
||||
|
@ -1256,6 +1256,14 @@ or add anything you like. Then, simply create an instance of your
|
|||
Python class), and register your models and ``ModelAdmin`` subclasses
|
||||
with it instead of using the default.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
When constructing an instance of an ``AdminSite``, you are able to provide
|
||||
a unique instance name using the ``name`` argument to the constructor. This
|
||||
instance name is used to identify the instance, especially when
|
||||
:ref:`reversing admin URLs <admin-reverse-urls>`. If no instance name is
|
||||
provided, a default instance name of ``admin`` will be used.
|
||||
|
||||
``AdminSite`` attributes
|
||||
------------------------
|
||||
|
||||
|
@ -1353,10 +1361,10 @@ a pattern for your new view.
|
|||
|
||||
.. note::
|
||||
Any view you render that uses the admin templates, or extends the base
|
||||
admin template, should include in it's context a variable named
|
||||
``admin_site`` that contains the name of the :class:`AdminSite` instance. For
|
||||
:class:`AdminSite` instances, this means ``self.name``; for :class:`ModelAdmin`
|
||||
instances, this means ``self.admin_site.name``.
|
||||
admin template, should provide the ``current_app`` argument to
|
||||
``RequestContext`` or ``Context`` when rendering the template. It should
|
||||
be set to either ``self.name`` if your view is on an ``AdminSite`` or
|
||||
``self.admin_site.name`` if your view is on a ``ModelAdmin``.
|
||||
|
||||
.. _admin-reverse-urls:
|
||||
|
||||
|
@ -1370,37 +1378,31 @@ accessible using Django's :ref:`URL reversing system <naming-url-patterns>`.
|
|||
|
||||
The :class:`AdminSite` provides the following named URL patterns:
|
||||
|
||||
====================== =============================== =============
|
||||
Page URL name Parameters
|
||||
====================== =============================== =============
|
||||
Index ``admin_index``
|
||||
Logout ``admin_logout``
|
||||
Password change ``admin_password_change``
|
||||
Password change done ``admin_password_change_done``
|
||||
i18n javascript ``admin_jsi18n``
|
||||
Application index page ``admin_app_list`` ``app_label``
|
||||
====================== =============================== =============
|
||||
|
||||
These names will be prefixed with the name of the :class:`AdminSite` instance,
|
||||
plus an underscore. For example, if your :class:`AdminSite` was named
|
||||
``custom``, then the Logout view would be served using a URL with the name
|
||||
``custom_admin_logout``. The default :class:`AdminSite` doesn't use a prefix
|
||||
in it's URL names.
|
||||
====================== ======================== =============
|
||||
Page URL name Parameters
|
||||
====================== ======================== =============
|
||||
Index ``index``
|
||||
Logout ``logout``
|
||||
Password change ``password_change``
|
||||
Password change done ``password_change_done``
|
||||
i18n javascript ``jsi18n``
|
||||
Application index page ``app_list`` ``app_label``
|
||||
====================== ======================== =============
|
||||
|
||||
Each :class:`ModelAdmin` instance provides an additional set of named URLs:
|
||||
|
||||
====================== ===================================================== =============
|
||||
Page URL name Parameters
|
||||
====================== ===================================================== =============
|
||||
Changelist ``admin_{{ app_label }}_{{ model_name }}_changelist``
|
||||
Add ``admin_{{ app_label }}_{{ model_name }}_add``
|
||||
History ``admin_{{ app_label }}_{{ model_name }}_history`` ``object_id``
|
||||
Delete ``admin_{{ app_label }}_{{ model_name }}_delete`` ``object_id``
|
||||
Change ``admin_{{ app_label }}_{{ model_name }}_change`` ``object_id``
|
||||
====================== ===================================================== =============
|
||||
====================== =============================================== =============
|
||||
Page URL name Parameters
|
||||
====================== =============================================== =============
|
||||
Changelist ``{{ app_label }}_{{ model_name }}_changelist``
|
||||
Add ``{{ app_label }}_{{ model_name }}_add``
|
||||
History ``{{ app_label }}_{{ model_name }}_history`` ``object_id``
|
||||
Delete ``{{ app_label }}_{{ model_name }}_delete`` ``object_id``
|
||||
Change ``{{ app_label }}_{{ model_name }}_change`` ``object_id``
|
||||
====================== =============================================== =============
|
||||
|
||||
Again, these names will be prefixed by the name of the :class:`AdminSite` in
|
||||
which they are deployed.
|
||||
These named URLs are registered with the application namespace ``admin``, and
|
||||
with an instance namespace corresponding to the name of the Site instance.
|
||||
|
||||
So - if you wanted to get a reference to the Change view for a particular
|
||||
``Choice`` object (from the polls application) in the default admin, you would
|
||||
|
@ -1408,8 +1410,16 @@ call::
|
|||
|
||||
>>> from django.core import urlresolvers
|
||||
>>> c = Choice.objects.get(...)
|
||||
>>> change_url = urlresolvers.reverse('admin_polls_choice_change', args=(c.id,))
|
||||
>>> change_url = urlresolvers.reverse('admin:polls_choice_change', args=(c.id,))
|
||||
|
||||
However, if the admin instance was named ``custom``, you would need to call::
|
||||
This will find the first registered instance of the admin application (whatever the instance
|
||||
name), and resolve to the view for changing ``poll.Choice`` instances in that instance.
|
||||
|
||||
>>> change_url = urlresolvers.reverse('custom_admin_polls_choice_change', args=(c.id,))
|
||||
If you want to find a URL in a specific admin instance, provide the name of that instance
|
||||
as a ``current_app`` hint to the reverse call. For example, if you specifically wanted
|
||||
the admin view from the admin instance named ``custom``, you would need to call::
|
||||
|
||||
>>> change_url = urlresolvers.reverse('custom:polls_choice_change', args=(c.id,))
|
||||
|
||||
For more details, see the documentation on :ref:`reversing namespaced URLs
|
||||
<topics-http-reversing-url-namespaces>`.
|
||||
|
|
|
@ -86,9 +86,16 @@ Rendering a context
|
|||
|
||||
Once you have a compiled ``Template`` object, you can render a context -- or
|
||||
multiple contexts -- with it. The ``Context`` class lives at
|
||||
``django.template.Context``, and the constructor takes one (optional)
|
||||
argument: a dictionary mapping variable names to variable values. Call the
|
||||
``Template`` object's ``render()`` method with the context to "fill" the
|
||||
``django.template.Context``, and the constructor takes two (optional)
|
||||
arguments:
|
||||
|
||||
* A dictionary mapping variable names to variable values.
|
||||
|
||||
* The name of the current application. This application name is used
|
||||
to help :ref:`resolve namespaced URLs<topics-http-reversing-url-namespaces>`.
|
||||
If you're not using namespaced URLs, you can ignore this argument.
|
||||
|
||||
Call the ``Template`` object's ``render()`` method with the context to "fill" the
|
||||
template::
|
||||
|
||||
>>> from django.template import Context, Template
|
||||
|
@ -549,13 +556,13 @@ Here are the template loaders that come with Django:
|
|||
Note that the loader performs an optimization when it is first imported: It
|
||||
caches a list of which :setting:`INSTALLED_APPS` packages have a
|
||||
``templates`` subdirectory.
|
||||
|
||||
|
||||
This loader is enabled by default.
|
||||
|
||||
``django.template.loaders.eggs.load_template_source``
|
||||
Just like ``app_directories`` above, but it loads templates from Python
|
||||
eggs rather than from the filesystem.
|
||||
|
||||
|
||||
This loader is disabled by default.
|
||||
|
||||
Django uses the template loaders in order according to the
|
||||
|
|
|
@ -795,6 +795,16 @@ missing. In practice you'll use this to link to views that are optional::
|
|||
<a href="{{ the_url }}">Link to optional stuff</a>
|
||||
{% endif %}
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
If you'd like to retrieve a namespaced URL, specify the fully qualified name::
|
||||
|
||||
{% url myapp:view-name %}
|
||||
|
||||
This will follow the normal :ref:`namespaced URL resolution strategy
|
||||
<topics-http-reversing-url-namespaces>`, including using any hints provided
|
||||
by the context as to the current application.
|
||||
|
||||
.. templatetag:: widthratio
|
||||
|
||||
widthratio
|
||||
|
|
|
@ -400,7 +400,7 @@ further processing.
|
|||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
Another posibility is to include additional URL patterns not by specifying the
|
||||
Another possibility is to include additional URL patterns not by specifying the
|
||||
URLconf Python module defining them as the `include`_ argument but by using
|
||||
directly the pattern list as returned by `patterns`_ instead. For example::
|
||||
|
||||
|
@ -417,6 +417,13 @@ directly the pattern list as returned by `patterns`_ instead. For example::
|
|||
(r'^credit/', include(extra_patterns)),
|
||||
)
|
||||
|
||||
This approach can be seen in use when you deploy an instance of the Django
|
||||
Admin application. The Django Admin is deployed as instances of a
|
||||
:class:`AdminSite`; each :class:`AdminSite` instance has an attribute
|
||||
``urls`` that returns the url patterns available to that instance. It is this
|
||||
attribute that you ``included()`` into your projects ``urlpatterns`` when you
|
||||
deploy the admin instance.
|
||||
|
||||
.. _`Django Web site`: http://www.djangoproject.com/
|
||||
|
||||
Captured parameters
|
||||
|
@ -439,6 +446,58 @@ the following example is valid::
|
|||
In the above example, the captured ``"username"`` variable is passed to the
|
||||
included URLconf, as expected.
|
||||
|
||||
.. _topics-http-defining-url-namespaces:
|
||||
|
||||
Defining URL Namespaces
|
||||
-----------------------
|
||||
|
||||
When you need to deploying multiple instances of a single application, it can
|
||||
be helpful to be able to differentiate between instances. This is especially
|
||||
important when using _`named URL patterns <naming-url-patterns>`, since
|
||||
multiple instances of a single application will share named URLs. Namespaces
|
||||
provide a way to tell these named URLs apart.
|
||||
|
||||
A URL namespace comes in two parts, both of which are strings:
|
||||
|
||||
* An **application namespace**. This describes the name of the application
|
||||
that is being deployed. Every instance of a single application will have
|
||||
the same application namespace. For example, Django's admin application
|
||||
has the somewhat predictable application namespace of ``admin``.
|
||||
|
||||
* An **instance namespace**. This identifies a specific instance of an
|
||||
application. Instance namespaces should be unique across your entire
|
||||
project. However, and instance namespace can be the same as the
|
||||
application namespace. This is used to specify a default instance of an
|
||||
application. For example, the default Django Admin instance has an
|
||||
instance namespace of ``admin``.
|
||||
|
||||
URL Namespaces can be specified in two ways.
|
||||
|
||||
Firstly, you can provide the applicaiton and instance namespace as arguments
|
||||
to the ``include()`` when you construct your URL patterns. For example,::
|
||||
|
||||
(r'^help/', include('apps.help.urls', namespace='foo', app_name='bar')),
|
||||
|
||||
This will include the URLs defined in ``apps.help.urls`` into the application
|
||||
namespace ``bar``, with the instance namespace ``foo``.
|
||||
|
||||
Secondly, you can include an object that contains embedded namespace data. If
|
||||
you ``include()`` a ``patterns`` object, that object will be added to the
|
||||
global namespace. However, you can also ``include()`` an object that contains
|
||||
a 3-tuple containing::
|
||||
|
||||
(<patterns object>, <application namespace>, <instance namespace>)
|
||||
|
||||
This will include the nominated URL patterns into the given application and
|
||||
instance namespace. For example, the ``urls`` attribute of Django's
|
||||
:class:`AdminSite` object returns a 3-tuple that contains all the patterns in
|
||||
an admin site, plus the name of the admin instance, and the application
|
||||
namespace ``admin``.
|
||||
|
||||
Once you have defined namespace URLs, you can reverse them. For details on
|
||||
reversing namespaced urls, see the documentation on :ref:`reversing namespaced
|
||||
URLs <topics-http-reversing-url-namespaces>`.
|
||||
|
||||
Passing extra options to view functions
|
||||
=======================================
|
||||
|
||||
|
@ -613,6 +672,86 @@ not restricted to valid Python names.
|
|||
name, will decrease the chances of collision. We recommend something like
|
||||
``myapp-comment`` instead of ``comment``.
|
||||
|
||||
.. _topics-http-reversing-url-namespaces:
|
||||
|
||||
URL namespaces
|
||||
--------------
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
Namespaced URLs are specified using the `:` operator. For example, the main index
|
||||
page of the admin application is referenced using ``admin:index``. This indicates
|
||||
a namespace of ``admin``, and a named URL of ``index``.
|
||||
|
||||
Namespaces can also be nested. The named URL ``foo:bar:whiz`` would look for
|
||||
a pattern named ``whiz`` in the namespace ``bar`` that is itself defined within
|
||||
the top-level namespace ``foo``.
|
||||
|
||||
When given a namespaced URL (e.g.,, `myapp:index`) to resolve, Django splits
|
||||
the fully qualified name into parts, and then tries the following lookup:
|
||||
|
||||
1. Django then looks for a matching application namespace (in this
|
||||
example, ``myapp``). This will yield a list of instances of that
|
||||
application.
|
||||
|
||||
2. If there is a ``current`` application defined, Django finds and returns
|
||||
the URL resolver for that instance. The ``current`` can be specified
|
||||
as an attribute on the template context - applications that expect to
|
||||
have multiple deployments should set the ``current_app`` attribute on
|
||||
any ``Context`` or ``RequestContext`` that is used to render a
|
||||
template.
|
||||
|
||||
The current application can also be specified manually as an argument
|
||||
to the :method:``reverse()`` function.
|
||||
|
||||
3. If there is no current application. Django looks for a default
|
||||
application instance. The default application instance is the instance
|
||||
that has an instance namespace matching the application namespace (in
|
||||
this example, an instance of the ``myapp`` called ``myapp``)
|
||||
|
||||
4. If there is no default application instance, Django will pick the first
|
||||
deployed instance of the application, whatever it's instance name may be.
|
||||
|
||||
5. If the provided namespace doesn't match an application namespace in
|
||||
step 2, Django will attempt a direct lookup of the namespace as an
|
||||
instance namespace.
|
||||
|
||||
If there are nested namespaces, these steps are repeated for each part of the
|
||||
namespace until only the view name is unresolved. The view name will then be
|
||||
resolved into a URL in the namespace that has been found.
|
||||
|
||||
To show this resolution strategy in action, consider an example of two instances
|
||||
of ``myapp``: one called ``foo``, and one called ``bar``. ``myapp`` has a main
|
||||
index page with a URL named `index`. Using this setup, the following lookups are
|
||||
possible:
|
||||
|
||||
* If one of the instances is current - say, if we were rendering a utility page
|
||||
in the instance ``bar`` - ``myapp:index`` will resolve to the index page of
|
||||
the instance ``bar``.
|
||||
|
||||
* If there is no current instance - say, if we were rendering a page
|
||||
somewhere else on the site - ``myapp:index`` will resolve to the first
|
||||
registered instance of ``myapp``. Since there is no default instance,
|
||||
the first instance of ``myapp`` that is registered will be used. This could
|
||||
be ``foo`` or ``bar``, depending on the order they are introduced into the
|
||||
urlpatterns of the project.
|
||||
|
||||
* ``foo:index`` will always resolve to the index page of the instance ``foo``.
|
||||
|
||||
If there was also a default instance - i.e., an instance named `myapp` - the
|
||||
following would happen:
|
||||
|
||||
* If one of the instances is current - say, if we were rendering a utility page
|
||||
in the instance ``bar`` - ``myapp:index`` will resolve to the index page of
|
||||
the instance ``bar``.
|
||||
|
||||
* If there is no current instance - say, if we were rendering a page somewhere
|
||||
else on the site - ``myapp:index`` will resolve to the index page of the
|
||||
default instance.
|
||||
|
||||
* ``foo:index`` will again resolve to the index page of the instance ``foo``.
|
||||
|
||||
|
||||
Utility methods
|
||||
===============
|
||||
|
||||
|
@ -624,7 +763,7 @@ your code, Django provides the following method (in the
|
|||
``django.core.urlresolvers`` module):
|
||||
|
||||
.. currentmodule:: django.core.urlresolvers
|
||||
.. function:: reverse(viewname, urlconf=None, args=None, kwargs=None)
|
||||
.. function:: reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)
|
||||
|
||||
``viewname`` is either the function name (either a function reference, or the
|
||||
string version of the name, if you used that form in ``urlpatterns``) or the
|
||||
|
@ -646,6 +785,14 @@ vertical bar (``"|"``) character. You can quite happily use such patterns for
|
|||
matching against incoming URLs and sending them off to views, but you cannot
|
||||
reverse such patterns.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
The ``current_app`` argument allows you to provide a hint to the resolver
|
||||
indicating the application to which the currently executing view belongs.
|
||||
This ``current_app`` argument is used as a hint to resolve application
|
||||
namespaces into URLs on specific application instances, according to the
|
||||
:ref:`namespaced URL resolution strategy <topics-http-reversing-url-namespaces>`.
|
||||
|
||||
.. admonition:: Make sure your views are all correct
|
||||
|
||||
As part of working out which URL names map to which patterns, the
|
||||
|
|
|
@ -10,19 +10,19 @@ import models
|
|||
class Admin2(admin.AdminSite):
|
||||
login_template = 'custom_admin/login.html'
|
||||
index_template = 'custom_admin/index.html'
|
||||
|
||||
|
||||
# A custom index view.
|
||||
def index(self, request, extra_context=None):
|
||||
return super(Admin2, self).index(request, {'foo': '*bar*'})
|
||||
|
||||
|
||||
def get_urls(self):
|
||||
return patterns('',
|
||||
(r'^my_view/$', self.admin_view(self.my_view)),
|
||||
) + super(Admin2, self).get_urls()
|
||||
|
||||
|
||||
def my_view(self, request):
|
||||
return HttpResponse("Django is a magical pony!")
|
||||
|
||||
|
||||
site = Admin2(name="admin2")
|
||||
|
||||
site.register(models.Article, models.ArticleAdmin)
|
||||
|
|
|
@ -205,6 +205,11 @@ class AdminViewBasicTest(TestCase):
|
|||
response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'})
|
||||
self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
|
||||
|
||||
def testLogoutAndPasswordChangeURLs(self):
|
||||
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit)
|
||||
self.failIf('<a href="/test_admin/%s/logout/">' % self.urlbit not in response.content)
|
||||
self.failIf('<a href="/test_admin/%s/password_change/">' % self.urlbit not in response.content)
|
||||
|
||||
def testNamedGroupFieldChoicesChangeList(self):
|
||||
"""
|
||||
Ensures the admin changelist shows correct values in the relevant column
|
||||
|
|
|
@ -19,7 +19,7 @@ class CarTireAdmin(admin.ModelAdmin):
|
|||
return db_field.formfield(**kwargs)
|
||||
return super(CarTireAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
|
||||
|
||||
site = WidgetAdmin()
|
||||
site = WidgetAdmin(name='widget-admin')
|
||||
|
||||
site.register(models.User)
|
||||
site.register(models.Car, CarAdmin)
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
from django.conf.urls.defaults import *
|
||||
from namespace_urls import URLObject
|
||||
|
||||
testobj3 = URLObject('testapp', 'test-ns3')
|
||||
|
||||
urlpatterns = patterns('regressiontests.urlpatterns_reverse.views',
|
||||
url(r'^normal/$', 'empty_view', name='inc-normal-view'),
|
||||
url(r'^normal/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='inc-normal-view'),
|
||||
|
||||
(r'^test3/', include(testobj3.urls)),
|
||||
(r'^ns-included3/', include('regressiontests.urlpatterns_reverse.included_urls', namespace='inc-ns3')),
|
||||
)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
from django.conf.urls.defaults import *
|
||||
|
||||
class URLObject(object):
|
||||
def __init__(self, app_name, namespace):
|
||||
self.app_name = app_name
|
||||
self.namespace = namespace
|
||||
|
||||
def urls(self):
|
||||
return patterns('',
|
||||
url(r'^inner/$', 'empty_view', name='urlobject-view'),
|
||||
url(r'^inner/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='urlobject-view'),
|
||||
), self.app_name, self.namespace
|
||||
urls = property(urls)
|
||||
|
||||
testobj1 = URLObject('testapp', 'test-ns1')
|
||||
testobj2 = URLObject('testapp', 'test-ns2')
|
||||
default_testobj = URLObject('testapp', 'testapp')
|
||||
|
||||
otherobj1 = URLObject('nodefault', 'other-ns1')
|
||||
otherobj2 = URLObject('nodefault', 'other-ns2')
|
||||
|
||||
urlpatterns = patterns('regressiontests.urlpatterns_reverse.views',
|
||||
url(r'^normal/$', 'empty_view', name='normal-view'),
|
||||
url(r'^normal/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='normal-view'),
|
||||
|
||||
(r'^test1/', include(testobj1.urls)),
|
||||
(r'^test2/', include(testobj2.urls)),
|
||||
(r'^default/', include(default_testobj.urls)),
|
||||
|
||||
(r'^other1/', include(otherobj1.urls)),
|
||||
(r'^other2/', include(otherobj2.urls)),
|
||||
|
||||
(r'^ns-included1/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')),
|
||||
(r'^ns-included2/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')),
|
||||
|
||||
(r'^included/', include('regressiontests.urlpatterns_reverse.included_namespace_urls')),
|
||||
|
||||
)
|
|
@ -158,4 +158,84 @@ class ReverseShortcutTests(TestCase):
|
|||
res = redirect('/foo/')
|
||||
self.assertEqual(res['Location'], '/foo/')
|
||||
res = redirect('http://example.com/')
|
||||
self.assertEqual(res['Location'], 'http://example.com/')
|
||||
self.assertEqual(res['Location'], 'http://example.com/')
|
||||
|
||||
|
||||
class NamespaceTests(TestCase):
|
||||
urls = 'regressiontests.urlpatterns_reverse.namespace_urls'
|
||||
|
||||
def test_ambiguous_object(self):
|
||||
"Names deployed via dynamic URL objects that require namespaces can't be resolved"
|
||||
self.assertRaises(NoReverseMatch, reverse, 'urlobject-view')
|
||||
self.assertRaises(NoReverseMatch, reverse, 'urlobject-view', args=[37,42])
|
||||
self.assertRaises(NoReverseMatch, reverse, 'urlobject-view', kwargs={'arg1':42, 'arg2':37})
|
||||
|
||||
def test_ambiguous_urlpattern(self):
|
||||
"Names deployed via dynamic URL objects that require namespaces can't be resolved"
|
||||
self.assertRaises(NoReverseMatch, reverse, 'inner-nothing')
|
||||
self.assertRaises(NoReverseMatch, reverse, 'inner-nothing', args=[37,42])
|
||||
self.assertRaises(NoReverseMatch, reverse, 'inner-nothing', kwargs={'arg1':42, 'arg2':37})
|
||||
|
||||
def test_non_existent_namespace(self):
|
||||
"Non-existent namespaces raise errors"
|
||||
self.assertRaises(NoReverseMatch, reverse, 'blahblah:urlobject-view')
|
||||
self.assertRaises(NoReverseMatch, reverse, 'test-ns1:blahblah:urlobject-view')
|
||||
|
||||
def test_normal_name(self):
|
||||
"Normal lookups work as expected"
|
||||
self.assertEquals('/normal/', reverse('normal-view'))
|
||||
self.assertEquals('/normal/37/42/', reverse('normal-view', args=[37,42]))
|
||||
self.assertEquals('/normal/42/37/', reverse('normal-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
def test_simple_included_name(self):
|
||||
"Normal lookups work on names included from other patterns"
|
||||
self.assertEquals('/included/normal/', reverse('inc-normal-view'))
|
||||
self.assertEquals('/included/normal/37/42/', reverse('inc-normal-view', args=[37,42]))
|
||||
self.assertEquals('/included/normal/42/37/', reverse('inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
def test_namespace_object(self):
|
||||
"Dynamic URL objects can be found using a namespace"
|
||||
self.assertEquals('/test1/inner/', reverse('test-ns1:urlobject-view'))
|
||||
self.assertEquals('/test1/inner/37/42/', reverse('test-ns1:urlobject-view', args=[37,42]))
|
||||
self.assertEquals('/test1/inner/42/37/', reverse('test-ns1:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
def test_embedded_namespace_object(self):
|
||||
"Namespaces can be installed anywhere in the URL pattern tree"
|
||||
self.assertEquals('/included/test3/inner/', reverse('test-ns3:urlobject-view'))
|
||||
self.assertEquals('/included/test3/inner/37/42/', reverse('test-ns3:urlobject-view', args=[37,42]))
|
||||
self.assertEquals('/included/test3/inner/42/37/', reverse('test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
def test_namespace_pattern(self):
|
||||
"Namespaces can be applied to include()'d urlpatterns"
|
||||
self.assertEquals('/ns-included1/normal/', reverse('inc-ns1:inc-normal-view'))
|
||||
self.assertEquals('/ns-included1/normal/37/42/', reverse('inc-ns1:inc-normal-view', args=[37,42]))
|
||||
self.assertEquals('/ns-included1/normal/42/37/', reverse('inc-ns1:inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
def test_multiple_namespace_pattern(self):
|
||||
"Namespaces can be embedded"
|
||||
self.assertEquals('/ns-included1/test3/inner/', reverse('inc-ns1:test-ns3:urlobject-view'))
|
||||
self.assertEquals('/ns-included1/test3/inner/37/42/', reverse('inc-ns1:test-ns3:urlobject-view', args=[37,42]))
|
||||
self.assertEquals('/ns-included1/test3/inner/42/37/', reverse('inc-ns1:test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
def test_app_lookup_object(self):
|
||||
"A default application namespace can be used for lookup"
|
||||
self.assertEquals('/default/inner/', reverse('testapp:urlobject-view'))
|
||||
self.assertEquals('/default/inner/37/42/', reverse('testapp:urlobject-view', args=[37,42]))
|
||||
self.assertEquals('/default/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
def test_app_lookup_object_with_default(self):
|
||||
"A default application namespace is sensitive to the 'current' app can be used for lookup"
|
||||
self.assertEquals('/included/test3/inner/', reverse('testapp:urlobject-view', current_app='test-ns3'))
|
||||
self.assertEquals('/included/test3/inner/37/42/', reverse('testapp:urlobject-view', args=[37,42], current_app='test-ns3'))
|
||||
self.assertEquals('/included/test3/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='test-ns3'))
|
||||
|
||||
def test_app_lookup_object_without_default(self):
|
||||
"An application namespace without a default is sensitive to the 'current' app can be used for lookup"
|
||||
self.assertEquals('/other2/inner/', reverse('nodefault:urlobject-view'))
|
||||
self.assertEquals('/other2/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42]))
|
||||
self.assertEquals('/other2/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
self.assertEquals('/other1/inner/', reverse('nodefault:urlobject-view', current_app='other-ns1'))
|
||||
self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1'))
|
||||
self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1'))
|
||||
|
||||
|
|
Loading…
Reference in New Issue