Code

Ticket #10061: t10061-r11021.v3.diff

File t10061-r11021.v3.diff, 36.9 KB (added by russellm, 5 years ago)

Cleanup of some naming issues and other minor tweaks.

Line 
1diff --git a/django/conf/urls/defaults.py b/django/conf/urls/defaults.py
2index 26cdd3e..3ab8bab 100644
3--- a/django/conf/urls/defaults.py
4+++ b/django/conf/urls/defaults.py
5@@ -6,7 +6,16 @@ __all__ = ['handler404', 'handler500', 'include', 'patterns', 'url']
6 handler404 = 'django.views.defaults.page_not_found'
7 handler500 = 'django.views.defaults.server_error'
8 
9-include = lambda urlconf_module: [urlconf_module]
10+def include(arg, namespace=None, app_name=None):
11+    if isinstance(arg, tuple):
12+        # callable returning a namespace hint
13+        if namespace:
14+            raise ImproperlyConfigured('Cannot override the namespace for a dynamic module that provides a namespace')
15+        urlconf_module, app_name, namespace = arg
16+    else:
17+        # No namespace hint - use manually provided namespace
18+        urlconf_module = arg
19+    return (urlconf_module, app_name, namespace)
20 
21 def patterns(prefix, *args):
22     pattern_list = []
23@@ -19,9 +28,10 @@ def patterns(prefix, *args):
24     return pattern_list
25 
26 def url(regex, view, kwargs=None, name=None, prefix=''):
27-    if type(view) == list:
28+    if isinstance(view, (list,tuple)):
29         # For include(...) processing.
30-        return RegexURLResolver(regex, view[0], kwargs)
31+        urlconf_module, app_name, namespace = view
32+        return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace)
33     else:
34         if isinstance(view, basestring):
35             if not view:
36diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
37index 8297eca..31a28cc 100644
38--- a/django/contrib/admin/options.py
39+++ b/django/contrib/admin/options.py
40@@ -226,24 +226,24 @@ class ModelAdmin(BaseModelAdmin):
41                 return self.admin_site.admin_view(view)(*args, **kwargs)
42             return update_wrapper(wrapper, view)
43 
44-        info = self.admin_site.name, self.model._meta.app_label, self.model._meta.module_name
45+        info = self.model._meta.app_label, self.model._meta.module_name
46 
47         urlpatterns = patterns('',
48             url(r'^$',
49                 wrap(self.changelist_view),
50-                name='%sadmin_%s_%s_changelist' % info),
51+                name='%s_%s_changelist' % info),
52             url(r'^add/$',
53                 wrap(self.add_view),
54-                name='%sadmin_%s_%s_add' % info),
55+                name='%s_%s_add' % info),
56             url(r'^(.+)/history/$',
57                 wrap(self.history_view),
58-                name='%sadmin_%s_%s_history' % info),
59+                name='%s_%s_history' % info),
60             url(r'^(.+)/delete/$',
61                 wrap(self.delete_view),
62-                name='%sadmin_%s_%s_delete' % info),
63+                name='%s_%s_delete' % info),
64             url(r'^(.+)/$',
65                 wrap(self.change_view),
66-                name='%sadmin_%s_%s_change' % info),
67+                name='%s_%s_change' % info),
68         )
69         return urlpatterns
70 
71@@ -582,11 +582,12 @@ class ModelAdmin(BaseModelAdmin):
72             'save_on_top': self.save_on_top,
73             'root_path': self.admin_site.root_path,
74         })
75+        context_instance = template.RequestContext(request, current_app=self.admin_site.name)
76         return render_to_response(self.change_form_template or [
77             "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
78             "admin/%s/change_form.html" % app_label,
79             "admin/change_form.html"
80-        ], context, context_instance=template.RequestContext(request))
81+        ], context, context_instance=context_instance)
82 
83     def response_add(self, request, obj, post_url_continue='../%s/'):
84         """
85@@ -977,11 +978,12 @@ class ModelAdmin(BaseModelAdmin):
86             'actions_on_bottom': self.actions_on_bottom,
87         }
88         context.update(extra_context or {})
89+        context_instance = template.RequestContext(request, current_app=self.admin_site.name)
90         return render_to_response(self.change_list_template or [
91             'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
92             'admin/%s/change_list.html' % app_label,
93             'admin/change_list.html'
94-        ], context, context_instance=template.RequestContext(request))
95+        ], context, context_instance=context_instance)
96 
97     def delete_view(self, request, object_id, extra_context=None):
98         "The 'delete' admin view for this model."
99@@ -1032,11 +1034,12 @@ class ModelAdmin(BaseModelAdmin):
100             "app_label": app_label,
101         }
102         context.update(extra_context or {})
103+        context_instance = template.RequestContext(request, current_app=self.admin_site.name)
104         return render_to_response(self.delete_confirmation_template or [
105             "admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
106             "admin/%s/delete_confirmation.html" % app_label,
107             "admin/delete_confirmation.html"
108-        ], context, context_instance=template.RequestContext(request))
109+        ], context, context_instance=context_instance)
110 
111     def history_view(self, request, object_id, extra_context=None):
112         "The 'history' admin view for this model."
113@@ -1059,11 +1062,12 @@ class ModelAdmin(BaseModelAdmin):
114             'app_label': app_label,
115         }
116         context.update(extra_context or {})
117+        context_instance = template.RequestContext(request, current_app=self.admin_site.name)
118         return render_to_response(self.object_history_template or [
119             "admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()),
120             "admin/%s/object_history.html" % app_label,
121             "admin/object_history.html"
122-        ], context, context_instance=template.RequestContext(request))
123+        ], context, context_instance=context_instance)
124 
125     #
126     # DEPRECATED methods.
127diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
128index 6e9ef11..72f3efe 100644
129--- a/django/contrib/admin/sites.py
130+++ b/django/contrib/admin/sites.py
131@@ -5,6 +5,7 @@ from django.contrib.admin import actions
132 from django.contrib.auth import authenticate, login
133 from django.db.models.base import ModelBase
134 from django.core.exceptions import ImproperlyConfigured
135+from django.core.urlresolvers import reverse
136 from django.shortcuts import render_to_response
137 from django.utils.functional import update_wrapper
138 from django.utils.safestring import mark_safe
139@@ -38,17 +39,14 @@ class AdminSite(object):
140     login_template = None
141     app_index_template = None
142 
143-    def __init__(self, name=None):
144+    def __init__(self, name=None, app_name='admin'):
145         self._registry = {} # model_class class -> admin_class instance
146-        # TODO Root path is used to calculate urls under the old root() method
147-        # in order to maintain backwards compatibility we are leaving that in
148-        # so root_path isn't needed, not sure what to do about this.
149-        self.root_path = 'admin/'
150+        self.root_path = None
151         if name is None:
152-            name = ''
153+            self.name = 'admin'
154         else:
155-            name += '_'
156-        self.name = name
157+            self.name = name
158+        self.app_name = app_name
159         self._actions = {'delete_selected': actions.delete_selected}
160         self._global_actions = self._actions.copy()
161 
162@@ -114,20 +112,20 @@ class AdminSite(object):
163         name = name or action.__name__
164         self._actions[name] = action
165         self._global_actions[name] = action
166-       
167+
168     def disable_action(self, name):
169         """
170         Disable a globally-registered action. Raises KeyError for invalid names.
171         """
172         del self._actions[name]
173-       
174+
175     def get_action(self, name):
176         """
177         Explicitally get a registered global action wheather it's enabled or
178         not. Raises KeyError for invalid names.
179         """
180         return self._global_actions[name]
181-   
182+
183     def actions(self):
184         """
185         Get all the enabled actions as an iterable of (name, func).
186@@ -186,7 +184,6 @@ class AdminSite(object):
187 
188     def get_urls(self):
189         from django.conf.urls.defaults import patterns, url, include
190-
191         def wrap(view):
192             def wrapper(*args, **kwargs):
193                 return self.admin_view(view)(*args, **kwargs)
194@@ -196,24 +193,24 @@ class AdminSite(object):
195         urlpatterns = patterns('',
196             url(r'^$',
197                 wrap(self.index),
198-                name='%sadmin_index' % self.name),
199+                name='index'),
200             url(r'^logout/$',
201                 wrap(self.logout),
202-                name='%sadmin_logout'),
203+                name='logout'),
204             url(r'^password_change/$',
205                 wrap(self.password_change),
206-                name='%sadmin_password_change' % self.name),
207+                name='password_change'),
208             url(r'^password_change/done/$',
209                 wrap(self.password_change_done),
210-                name='%sadmin_password_change_done' % self.name),
211+                name='password_change_done'),
212             url(r'^jsi18n/$',
213                 wrap(self.i18n_javascript),
214-                name='%sadmin_jsi18n' % self.name),
215+                name='jsi18n'),
216             url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
217                 'django.views.defaults.shortcut'),
218             url(r'^(?P<app_label>\w+)/$',
219                 wrap(self.app_index),
220-                name='%sadmin_app_list' % self.name),
221+                name='app_list')
222         )
223 
224         # Add in each model's views.
225@@ -225,7 +222,7 @@ class AdminSite(object):
226         return urlpatterns
227 
228     def urls(self):
229-        return self.get_urls()
230+        return self.get_urls(), self.app_name, self.name
231     urls = property(urls)
232 
233     def password_change(self, request):
234@@ -233,8 +230,11 @@ class AdminSite(object):
235         Handles the "change password" task -- both form display and validation.
236         """
237         from django.contrib.auth.views import password_change
238-        return password_change(request,
239-            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)
245 
246     def password_change_done(self, request):
247         """
248@@ -362,8 +362,9 @@ class AdminSite(object):
249             'root_path': self.root_path,
250         }
251         context.update(extra_context or {})
252+        context_instance = template.RequestContext(request, current_app=self.name)
253         return render_to_response(self.index_template or 'admin/index.html', context,
254-            context_instance=template.RequestContext(request)
255+            context_instance=context_instance
256         )
257     index = never_cache(index)
258 
259@@ -376,8 +377,9 @@ class AdminSite(object):
260             'root_path': self.root_path,
261         }
262         context.update(extra_context or {})
263+        context_instance = template.RequestContext(request, current_app=self.name)
264         return render_to_response(self.login_template or 'admin/login.html', context,
265-            context_instance=template.RequestContext(request)
266+            context_instance=context_instance
267         )
268 
269     def app_index(self, request, app_label, extra_context=None):
270@@ -419,9 +421,10 @@ class AdminSite(object):
271             'root_path': self.root_path,
272         }
273         context.update(extra_context or {})
274+        context_instance = template.RequestContext(request, current_app=self.name)
275         return render_to_response(self.app_index_template or ('admin/%s/app_index.html' % app_label,
276             'admin/app_index.html'), context,
277-            context_instance=template.RequestContext(request)
278+            context_instance=context_instance
279         )
280 
281     def root(self, request, url):
282diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html
283index 8cab439..9525728 100644
284--- a/django/contrib/admin/templates/admin/base.html
285+++ b/django/contrib/admin/templates/admin/base.html
286@@ -23,7 +23,30 @@
287         {% block branding %}{% endblock %}
288         </div>
289         {% if user.is_authenticated and user.is_staff %}
290-        <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>
291+        <div id="user-tools">
292+            {% trans 'Welcome,' %}
293+            <strong>{% firstof user.first_name user.username %}</strong>.
294+            {% block userlinks %}
295+                {% url django-admindocs-docroot as docsroot %}
296+                {% if docsroot %}
297+                    <a href="{{ docsroot }}">{% trans 'Documentation' %}</a> /
298+                {% endif %}
299+                {% url admin:password_change as password_change_url %}
300+                {% if password_change_url %}
301+                    <a href="{{ password_change_url }}">
302+                {% else %}
303+                    <a href="{{ root_path }}password_change/">
304+                {% endif %}
305+                {% trans 'Change password' %}</a> /
306+                {% url admin:logout as logout_url %}
307+                {% if logout_url %}
308+                    <a href="{{ logout_url }}">
309+                {% else %}
310+                    <a href="{{ root_path }}logout/">
311+                {% endif %}
312+                {% trans 'Log out' %}</a>
313+            {% endblock %}
314+        </div>
315         {% endif %}
316         {% block nav-global %}{% endblock %}
317     </div>
318diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
319index 7ae5e64..1a081bc 100644
320--- a/django/contrib/admin/widgets.py
321+++ b/django/contrib/admin/widgets.py
322@@ -125,7 +125,7 @@ class ForeignKeyRawIdWidget(forms.TextInput):
323         if value:
324             output.append(self.label_for_value(value))
325         return mark_safe(u''.join(output))
326-   
327+
328     def base_url_parameters(self):
329         params = {}
330         if self.rel.limit_choices_to:
331@@ -137,14 +137,14 @@ class ForeignKeyRawIdWidget(forms.TextInput):
332                     v = str(v)
333                 items.append((k, v))
334             params.update(dict(items))
335-        return params   
336-   
337+        return params
338+
339     def url_parameters(self):
340         from django.contrib.admin.views.main import TO_FIELD_VAR
341         params = self.base_url_parameters()
342         params.update({TO_FIELD_VAR: self.rel.get_related_field().name})
343         return params
344-           
345+
346     def label_for_value(self, value):
347         key = self.rel.get_related_field().name
348         obj = self.rel.to._default_manager.get(**{key: value})
349@@ -165,10 +165,10 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
350         else:
351             value = ''
352         return super(ManyToManyRawIdWidget, self).render(name, value, attrs)
353-   
354+
355     def url_parameters(self):
356         return self.base_url_parameters()
357-   
358+
359     def label_for_value(self, value):
360         return ''
361 
362@@ -222,8 +222,7 @@ class RelatedFieldWidgetWrapper(forms.Widget):
363         rel_to = self.rel.to
364         info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
365         try:
366-            related_info = (self.admin_site.name,) + info
367-            related_url = reverse('%sadmin_%s_%s_add' % related_info)
368+            related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
369         except NoReverseMatch:
370             related_url = '../../../%s/%s/add/' % info
371         self.widget.choices = self.choices
372diff --git a/django/contrib/admindocs/templates/admin_doc/index.html b/django/contrib/admindocs/templates/admin_doc/index.html
373index 242fc73..a8b21c3 100644
374--- a/django/contrib/admindocs/templates/admin_doc/index.html
375+++ b/django/contrib/admindocs/templates/admin_doc/index.html
376@@ -1,6 +1,6 @@
377 {% extends "admin/base_site.html" %}
378 {% load i18n %}
379-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> &rsaquo; Documentation</div>{% endblock %}
380+{% block breadcrumbs %}<div class="breadcrumbs"><a href="{{ root_path }}">Home</a> &rsaquo; Documentation</div>{% endblock %}
381 {% block title %}Documentation{% endblock %}
382 
383 {% block content %}
384diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py
385index 4f22fe0..571f393 100644
386--- a/django/contrib/admindocs/views.py
387+++ b/django/contrib/admindocs/views.py
388@@ -22,11 +22,14 @@ class GenericSite(object):
389     name = 'my site'
390 
391 def get_root_path():
392-    from django.contrib import admin
393     try:
394-        return urlresolvers.reverse(admin.site.root, args=[''])
395+        return urlresolvers.reverse('admin:index')
396     except urlresolvers.NoReverseMatch:
397-        return getattr(settings, "ADMIN_SITE_ROOT_URL", "/admin/")
398+        from django.contrib import admin
399+        try:
400+            return urlresolvers.reverse(admin.site.root, args=[''])
401+        except urlresolvers.NoReverseMatch:
402+            return getattr(settings, "ADMIN_SITE_ROOT_URL", "/admin/")
403 
404 def doc_index(request):
405     if not utils.docutils_is_available:
406@@ -179,7 +182,7 @@ model_index = staff_member_required(model_index)
407 def model_detail(request, app_label, model_name):
408     if not utils.docutils_is_available:
409         return missing_docutils_page(request)
410-       
411+
412     # Get the model class.
413     try:
414         app_mod = models.get_app(app_label)
415diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
416index 10e97bb..4f9eb98 100644
417--- a/django/core/urlresolvers.py
418+++ b/django/core/urlresolvers.py
419@@ -139,7 +139,7 @@ class RegexURLPattern(object):
420     callback = property(_get_callback)
421 
422 class RegexURLResolver(object):
423-    def __init__(self, regex, urlconf_name, default_kwargs=None):
424+    def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
425         # regex is a string representing a regular expression.
426         # urlconf_name is a string representing the module containing URLconfs.
427         self.regex = re.compile(regex, re.UNICODE)
428@@ -148,19 +148,29 @@ class RegexURLResolver(object):
429             self._urlconf_module = self.urlconf_name
430         self.callback = None
431         self.default_kwargs = default_kwargs or {}
432-        self._reverse_dict = MultiValueDict()
433+        self.namespace = namespace
434+        self.app_name = app_name
435+        self._reverse_dict = None
436+        self._namespace_dict = None
437+        self._app_dict = None
438 
439     def __repr__(self):
440-        return '<%s %s %s>' % (self.__class__.__name__, self.urlconf_name, self.regex.pattern)
441-
442-    def _get_reverse_dict(self):
443-        if not self._reverse_dict:
444-            lookups = MultiValueDict()
445-            for pattern in reversed(self.url_patterns):
446-                p_pattern = pattern.regex.pattern
447-                if p_pattern.startswith('^'):
448-                    p_pattern = p_pattern[1:]
449-                if isinstance(pattern, RegexURLResolver):
450+        return '<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)
451+
452+    def _populate(self):
453+        lookups = MultiValueDict()
454+        namespaces = {}
455+        apps = {}
456+        for pattern in reversed(self.url_patterns):
457+            p_pattern = pattern.regex.pattern
458+            if p_pattern.startswith('^'):
459+                p_pattern = p_pattern[1:]
460+            if isinstance(pattern, RegexURLResolver):
461+                if pattern.namespace:
462+                    namespaces[pattern.namespace] = (p_pattern, pattern)
463+                    if pattern.app_name:
464+                        apps.setdefault(pattern.app_name, []).append(pattern.namespace)
465+                else:
466                     parent = normalize(pattern.regex.pattern)
467                     for name in pattern.reverse_dict:
468                         for matches, pat in pattern.reverse_dict.getlist(name):
469@@ -168,14 +178,36 @@ class RegexURLResolver(object):
470                             for piece, p_args in parent:
471                                 new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches])
472                             lookups.appendlist(name, (new_matches, p_pattern + pat))
473-                else:
474-                    bits = normalize(p_pattern)
475-                    lookups.appendlist(pattern.callback, (bits, p_pattern))
476-                    lookups.appendlist(pattern.name, (bits, p_pattern))
477-            self._reverse_dict = lookups
478+                    for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items():
479+                        namespaces[namespace] = (p_pattern + prefix, sub_pattern)
480+                    for app_name, namespace_list in pattern.app_dict.items():
481+                        apps.setdefault(app_name, []).extend(namespace_list)
482+            else:
483+                bits = normalize(p_pattern)
484+                lookups.appendlist(pattern.callback, (bits, p_pattern))
485+                lookups.appendlist(pattern.name, (bits, p_pattern))
486+        self._reverse_dict = lookups
487+        self._namespace_dict = namespaces
488+        self._app_dict = apps
489+
490+    def _get_reverse_dict(self):
491+        if self._reverse_dict is None:
492+            self._populate()
493         return self._reverse_dict
494     reverse_dict = property(_get_reverse_dict)
495 
496+    def _get_namespace_dict(self):
497+        if self._namespace_dict is None:
498+            self._populate()
499+        return self._namespace_dict
500+    namespace_dict = property(_get_namespace_dict)
501+
502+    def _get_app_dict(self):
503+        if self._app_dict is None:
504+            self._populate()
505+        return self._app_dict
506+    app_dict = property(_get_app_dict)
507+
508     def resolve(self, path):
509         tried = []
510         match = self.regex.search(path)
511@@ -261,12 +293,51 @@ class RegexURLResolver(object):
512 def resolve(path, urlconf=None):
513     return get_resolver(urlconf).resolve(path)
514 
515-def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None):
516+def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None):
517+    resolver = get_resolver(urlconf)
518     args = args or []
519     kwargs = kwargs or {}
520+
521     if prefix is None:
522         prefix = get_script_prefix()
523-    return iri_to_uri(u'%s%s' % (prefix, get_resolver(urlconf).reverse(viewname,
524+
525+    if not isinstance(viewname, basestring):
526+        view = viewname
527+    else:
528+        parts = viewname.split(':')
529+        parts.reverse()
530+        view = parts[0]
531+        path = parts[1:]
532+
533+        resolved_path = []
534+        while path:
535+            ns = path.pop()
536+
537+            # Lookup the name to see if it could be an app identifier
538+            try:
539+                app_list = resolver.app_dict[ns]
540+                # Yes! Path part matches an app in the current Resolver
541+                if current_app and current_app in app_list:
542+                    # If we are reversing for a particular app, use that namespace
543+                    ns = current_app
544+                elif ns not in app_list:
545+                    # The name isn't shared by one of the instances (i.e., the default)
546+                    # so just pick the first instance as the default.
547+                    ns = app_list[0]
548+            except KeyError:
549+                pass
550+
551+            try:
552+                extra, resolver = resolver.namespace_dict[ns]
553+                resolved_path.append(ns)
554+                prefix = prefix + extra
555+            except KeyError, key:
556+                if resolved_path:
557+                    raise NoReverseMatch("%s is not a registered namespace inside '%s'" % (key, ':'.join(resolved_path)))
558+                else:
559+                    raise NoReverseMatch("%s is not a registered namespace" % key)
560+
561+    return iri_to_uri(u'%s%s' % (prefix, resolver.reverse(view,
562             *args, **kwargs)))
563 
564 def clear_url_caches():
565diff --git a/django/template/context.py b/django/template/context.py
566index 0ccb5fa..1c43387 100644
567--- a/django/template/context.py
568+++ b/django/template/context.py
569@@ -9,10 +9,11 @@ class ContextPopException(Exception):
570 
571 class Context(object):
572     "A stack container for variable context"
573-    def __init__(self, dict_=None, autoescape=True):
574+    def __init__(self, dict_=None, autoescape=True, current_app=None):
575         dict_ = dict_ or {}
576         self.dicts = [dict_]
577         self.autoescape = autoescape
578+        self.current_app = current_app
579 
580     def __repr__(self):
581         return repr(self.dicts)
582@@ -96,8 +97,8 @@ class RequestContext(Context):
583     Additional processors can be specified as a list of callables
584     using the "processors" keyword argument.
585     """
586-    def __init__(self, request, dict=None, processors=None):
587-        Context.__init__(self, dict)
588+    def __init__(self, request, dict=None, processors=None, current_app=None):
589+        Context.__init__(self, dict, current_app=current_app)
590         if processors is None:
591             processors = ()
592         else:
593diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
594index 7d91cd6..de74699 100644
595--- a/django/template/defaulttags.py
596+++ b/django/template/defaulttags.py
597@@ -367,17 +367,17 @@ class URLNode(Node):
598         # {% url ... as var %} construct in which cause return nothing.
599         url = ''
600         try:
601-            url = reverse(self.view_name, args=args, kwargs=kwargs)
602+            url = reverse(self.view_name, args=args, kwargs=kwargs, current_app=context.current_app)
603         except NoReverseMatch, e:
604             if settings.SETTINGS_MODULE:
605                 project_name = settings.SETTINGS_MODULE.split('.')[0]
606                 try:
607                     url = reverse(project_name + '.' + self.view_name,
608-                              args=args, kwargs=kwargs)
609+                              args=args, kwargs=kwargs, current_app=context.current_app)
610                 except NoReverseMatch:
611                     if self.asvar is None:
612                         # Re-raise the original exception, not the one with
613-                        # the path relative to the project. This makes a
614+                        # the path relative to the project. This makes a
615                         # better error message.
616                         raise e
617             else:
618diff --git a/tests/regressiontests/admin_views/customadmin.py b/tests/regressiontests/admin_views/customadmin.py
619index 70e87eb..80570ea 100644
620--- a/tests/regressiontests/admin_views/customadmin.py
621+++ b/tests/regressiontests/admin_views/customadmin.py
622@@ -10,19 +10,19 @@ import models
623 class Admin2(admin.AdminSite):
624     login_template = 'custom_admin/login.html'
625     index_template = 'custom_admin/index.html'
626-   
627+
628     # A custom index view.
629     def index(self, request, extra_context=None):
630         return super(Admin2, self).index(request, {'foo': '*bar*'})
631-   
632+
633     def get_urls(self):
634         return patterns('',
635             (r'^my_view/$', self.admin_view(self.my_view)),
636         ) + super(Admin2, self).get_urls()
637-   
638+
639     def my_view(self, request):
640         return HttpResponse("Django is a magical pony!")
641-   
642+
643 site = Admin2(name="admin2")
644 
645 site.register(models.Article, models.ArticleAdmin)
646diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
647index 99168fd..3fc145b 100644
648--- a/tests/regressiontests/admin_views/tests.py
649+++ b/tests/regressiontests/admin_views/tests.py
650@@ -204,6 +204,11 @@ class AdminViewBasicTest(TestCase):
651         response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'})
652         self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
653 
654+    def testLogoutAndPasswordChangeURLs(self):
655+        response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit)
656+        self.failIf('<a href="/test_admin/%s/logout/">' % self.urlbit not in response.content)
657+        self.failIf('<a href="/test_admin/%s/password_change/">' % self.urlbit not in response.content)
658+
659     def testNamedGroupFieldChoicesChangeList(self):
660         """
661         Ensures the admin changelist shows correct values in the relevant column
662diff --git a/tests/regressiontests/admin_widgets/widgetadmin.py b/tests/regressiontests/admin_widgets/widgetadmin.py
663index bd68954..9257c30 100644
664--- a/tests/regressiontests/admin_widgets/widgetadmin.py
665+++ b/tests/regressiontests/admin_widgets/widgetadmin.py
666@@ -19,7 +19,7 @@ class CarTireAdmin(admin.ModelAdmin):
667             return db_field.formfield(**kwargs)
668         return super(CarTireAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
669 
670-site = WidgetAdmin()
671+site = WidgetAdmin(name='widget-admin')
672 
673 site.register(models.User)
674 site.register(models.Car, CarAdmin)
675diff --git a/tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py b/tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py
676new file mode 100644
677index 0000000..0731906
678--- /dev/null
679+++ b/tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py
680@@ -0,0 +1,13 @@
681+from django.conf.urls.defaults import *
682+from namespace_urls import URLObject
683+
684+testobj3 = URLObject('testapp', 'test-ns3')
685+
686+urlpatterns = patterns('regressiontests.urlpatterns_reverse.views',
687+    url(r'^normal/$', 'empty_view', name='inc-normal-view'),
688+    url(r'^normal/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='inc-normal-view'),
689+
690+    (r'^test3/', include(testobj3.urls)),
691+    (r'^ns-included3/', include('regressiontests.urlpatterns_reverse.included_urls', namespace='inc-ns3')),
692+)
693+
694diff --git a/tests/regressiontests/urlpatterns_reverse/namespace_urls.py b/tests/regressiontests/urlpatterns_reverse/namespace_urls.py
695new file mode 100644
696index 0000000..27cc7f7
697--- /dev/null
698+++ b/tests/regressiontests/urlpatterns_reverse/namespace_urls.py
699@@ -0,0 +1,38 @@
700+from django.conf.urls.defaults import *
701+
702+class URLObject(object):
703+    def __init__(self, app_name, namespace):
704+        self.app_name = app_name
705+        self.namespace = namespace
706+
707+    def urls(self):
708+        return patterns('',
709+            url(r'^inner/$', 'empty_view', name='urlobject-view'),
710+            url(r'^inner/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='urlobject-view'),
711+        ), self.app_name, self.namespace
712+    urls = property(urls)
713+
714+testobj1 = URLObject('testapp', 'test-ns1')
715+testobj2 = URLObject('testapp', 'test-ns2')
716+default_testobj = URLObject('testapp', 'testapp')
717+
718+otherobj1 = URLObject('nodefault', 'other-ns1')
719+otherobj2 = URLObject('nodefault', 'other-ns2')
720+
721+urlpatterns = patterns('regressiontests.urlpatterns_reverse.views',
722+    url(r'^normal/$', 'empty_view', name='normal-view'),
723+    url(r'^normal/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='normal-view'),
724+
725+    (r'^test1/', include(testobj1.urls)),
726+    (r'^test2/', include(testobj2.urls)),
727+    (r'^default/', include(default_testobj.urls)),
728+
729+    (r'^other1/', include(otherobj1.urls)),
730+    (r'^other2/', include(otherobj2.urls)),
731+
732+    (r'^ns-included1/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')),
733+    (r'^ns-included2/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')),
734+
735+    (r'^included/', include('regressiontests.urlpatterns_reverse.included_namespace_urls')),
736+
737+)
738diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py
739index 9def6b2..d4f281b 100644
740--- a/tests/regressiontests/urlpatterns_reverse/tests.py
741+++ b/tests/regressiontests/urlpatterns_reverse/tests.py
742@@ -158,4 +158,84 @@ class ReverseShortcutTests(TestCase):
743         res = redirect('/foo/')
744         self.assertEqual(res['Location'], '/foo/')
745         res = redirect('http://example.com/')
746-        self.assertEqual(res['Location'], 'http://example.com/')
747\ No newline at end of file
748+        self.assertEqual(res['Location'], 'http://example.com/')
749+
750+
751+class NamespaceTests(TestCase):
752+    urls = 'regressiontests.urlpatterns_reverse.namespace_urls'
753+
754+    def test_ambiguous_object(self):
755+        "Names deployed via dynamic URL objects that require namespaces can't be resolved"
756+        self.assertRaises(NoReverseMatch, reverse, 'urlobject-view')
757+        self.assertRaises(NoReverseMatch, reverse, 'urlobject-view', args=[37,42])
758+        self.assertRaises(NoReverseMatch, reverse, 'urlobject-view', kwargs={'arg1':42, 'arg2':37})
759+
760+    def test_ambiguous_urlpattern(self):
761+        "Names deployed via dynamic URL objects that require namespaces can't be resolved"
762+        self.assertRaises(NoReverseMatch, reverse, 'inner-nothing')
763+        self.assertRaises(NoReverseMatch, reverse, 'inner-nothing', args=[37,42])
764+        self.assertRaises(NoReverseMatch, reverse, 'inner-nothing', kwargs={'arg1':42, 'arg2':37})
765+
766+    def test_non_existent_namespace(self):
767+        "Non-existent namespaces raise errors"
768+        self.assertRaises(NoReverseMatch, reverse, 'blahblah:urlobject-view')
769+        self.assertRaises(NoReverseMatch, reverse, 'test-ns1:blahblah:urlobject-view')
770+
771+    def test_normal_name(self):
772+        "Normal lookups work as expected"
773+        self.assertEquals('/normal/', reverse('normal-view'))
774+        self.assertEquals('/normal/37/42/', reverse('normal-view', args=[37,42]))
775+        self.assertEquals('/normal/42/37/', reverse('normal-view', kwargs={'arg1':42, 'arg2':37}))
776+
777+    def test_simple_included_name(self):
778+        "Normal lookups work on names included from other patterns"
779+        self.assertEquals('/included/normal/', reverse('inc-normal-view'))
780+        self.assertEquals('/included/normal/37/42/', reverse('inc-normal-view', args=[37,42]))
781+        self.assertEquals('/included/normal/42/37/', reverse('inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
782+
783+    def test_namespace_object(self):
784+        "Dynamic URL objects can be found using a namespace"
785+        self.assertEquals('/test1/inner/', reverse('test-ns1:urlobject-view'))
786+        self.assertEquals('/test1/inner/37/42/', reverse('test-ns1:urlobject-view', args=[37,42]))
787+        self.assertEquals('/test1/inner/42/37/', reverse('test-ns1:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
788+
789+    def test_embedded_namespace_object(self):
790+        "Namespaces can be installed anywhere in the URL pattern tree"
791+        self.assertEquals('/included/test3/inner/', reverse('test-ns3:urlobject-view'))
792+        self.assertEquals('/included/test3/inner/37/42/', reverse('test-ns3:urlobject-view', args=[37,42]))
793+        self.assertEquals('/included/test3/inner/42/37/', reverse('test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
794+
795+    def test_namespace_pattern(self):
796+        "Namespaces can be applied to include()'d urlpatterns"
797+        self.assertEquals('/ns-included1/normal/', reverse('inc-ns1:inc-normal-view'))
798+        self.assertEquals('/ns-included1/normal/37/42/', reverse('inc-ns1:inc-normal-view', args=[37,42]))
799+        self.assertEquals('/ns-included1/normal/42/37/', reverse('inc-ns1:inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
800+
801+    def test_multiple_namespace_pattern(self):
802+        "Namespaces can be embedded"
803+        self.assertEquals('/ns-included1/test3/inner/', reverse('inc-ns1:test-ns3:urlobject-view'))
804+        self.assertEquals('/ns-included1/test3/inner/37/42/', reverse('inc-ns1:test-ns3:urlobject-view', args=[37,42]))
805+        self.assertEquals('/ns-included1/test3/inner/42/37/', reverse('inc-ns1:test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
806+
807+    def test_app_lookup_object(self):
808+        "A default application namespace can be used for lookup"
809+        self.assertEquals('/default/inner/', reverse('testapp:urlobject-view'))
810+        self.assertEquals('/default/inner/37/42/', reverse('testapp:urlobject-view', args=[37,42]))
811+        self.assertEquals('/default/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
812+
813+    def test_app_lookup_object_with_default(self):
814+        "A default application namespace is sensitive to the 'current' app can be used for lookup"
815+        self.assertEquals('/included/test3/inner/', reverse('testapp:urlobject-view', current_app='test-ns3'))
816+        self.assertEquals('/included/test3/inner/37/42/', reverse('testapp:urlobject-view', args=[37,42], current_app='test-ns3'))
817+        self.assertEquals('/included/test3/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='test-ns3'))
818+
819+    def test_app_lookup_object_without_default(self):
820+        "An application namespace without a default is sensitive to the 'current' app can be used for lookup"
821+        self.assertEquals('/other2/inner/', reverse('nodefault:urlobject-view'))
822+        self.assertEquals('/other2/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42]))
823+        self.assertEquals('/other2/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
824+
825+        self.assertEquals('/other1/inner/', reverse('nodefault:urlobject-view', current_app='other-ns1'))
826+        self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1'))
827+        self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1'))
828+