Ticket #10061: t10061-r11201.v2.diff

File t10061-r11201.v2.diff, 34.0 KB (added by russellm, 6 years ago)

Updated patch including suggestions from Alex and Rob

  • django/conf/urls/defaults.py

    diff --git a/django/conf/urls/defaults.py b/django/conf/urls/defaults.py
    index 26cdd3e..572a7b0 100644
    a b __all__ = ['handler404', 'handler500', 'include', 'patterns', 'url'] 
    66handler404 = 'django.views.defaults.page_not_found'
    77handler500 = 'django.views.defaults.server_error'
    88
    9 include = lambda urlconf_module: [urlconf_module]
     9def include(arg, namespace=None, app_name=None):
     10    if type(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
    1120def patterns(prefix, *args):
    1221    pattern_list = []
    def patterns(prefix, *args): 
    1928    return pattern_list
    2029
    2130def url(regex, view, kwargs=None, name=None, prefix=''):
    22     if type(view) == list:
     31    if type(view) == 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

    diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
    index 8297eca..0545409 100644
    a b class ModelAdmin(BaseModelAdmin): 
    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
    class ModelAdmin(BaseModelAdmin): 
    582582            'save_on_top': self.save_on_top,
    583583            'root_path': self.admin_site.root_path,
    584584        })
     585        context_instance = template.RequestContext(request, app_name=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        """
    class ModelAdmin(BaseModelAdmin): 
    977978            'actions_on_bottom': self.actions_on_bottom,
    978979        }
    979980        context.update(extra_context or {})
     981        context_instance = template.RequestContext(request, app_name=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."
    class ModelAdmin(BaseModelAdmin): 
    10321034            "app_label": app_label,
    10331035        }
    10341036        context.update(extra_context or {})
     1037        context_instance = template.RequestContext(request, app_name=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."
    class ModelAdmin(BaseModelAdmin): 
    10591062            'app_label': app_label,
    10601063        }
    10611064        context.update(extra_context or {})
     1065        context_instance = template.RequestContext(request, app_name=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

    diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
    index 6e9ef11..e161516 100644
    a b from django.contrib.admin import actions 
    55from django.contrib.auth import authenticate, login
    66from django.db.models.base import ModelBase
    77from django.core.exceptions import ImproperlyConfigured
     8from django.core.urlresolvers import reverse
    89from django.shortcuts import render_to_response
    910from django.utils.functional import update_wrapper
    1011from django.utils.safestring import mark_safe
    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
    class AdminSite(object): 
    114112        name = name or action.__name__
    115113        self._actions[name] = action
    116114        self._global_actions[name] = action
    117        
     115
    118116    def disable_action(self, name):
    119117        """
    120118        Disable a globally-registered action. Raises KeyError for invalid names.
    121119        """
    122120        del self._actions[name]
    123        
     121
    124122    def get_action(self, name):
    125123        """
    126124        Explicitally get a registered global action wheather it's enabled or
    127125        not. Raises KeyError for invalid names.
    128126        """
    129127        return self._global_actions[name]
    130    
     128
    131129    def actions(self):
    132130        """
    133131        Get all the enabled actions as an iterable of (name, func).
    class AdminSite(object): 
    186184
    187185    def get_urls(self):
    188186        from django.conf.urls.defaults import patterns, url, include
    189 
    190187        def wrap(view):
    191188            def wrapper(*args, **kwargs):
    192189                return self.admin_view(view)(*args, **kwargs)
    class AdminSite(object): 
    196193        urlpatterns = patterns('',
    197194            url(r'^$',
    198195                wrap(self.index),
    199                 name='%sadmin_index' % self.name),
     196                name='index'),
    200197            url(r'^logout/$',
    201198                wrap(self.logout),
    202                 name='%sadmin_logout'),
     199                name='logout'),
    203200            url(r'^password_change/$',
    204201                wrap(self.password_change),
    205                 name='%sadmin_password_change' % self.name),
     202                name='password_change'),
    206203            url(r'^password_change/done/$',
    207204                wrap(self.password_change_done),
    208                 name='%sadmin_password_change_done' % self.name),
     205                name='password_change_done'),
    209206            url(r'^jsi18n/$',
    210207                wrap(self.i18n_javascript),
    211                 name='%sadmin_jsi18n' % self.name),
     208                name='jsi18n'),
    212209            url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
    213210                'django.views.defaults.shortcut'),
    214211            url(r'^(?P<app_label>\w+)/$',
    215212                wrap(self.app_index),
    216                 name='%sadmin_app_list' % self.name),
     213                name='app_list')
    217214        )
    218215
    219216        # Add in each model's views.
    class AdminSite(object): 
    222219                url(r'^%s/%s/' % (model._meta.app_label, model._meta.module_name),
    223220                    include(model_admin.urls))
    224221            )
    225         return urlpatterns
     222        return urlpatterns, self.app_name, self.name
    226223
    227224    def urls(self):
    228225        return self.get_urls()
    class AdminSite(object): 
    233230        Handles the "change password" task -- both form display and validation.
    234231        """
    235232        from django.contrib.auth.views import password_change
    236         return password_change(request,
    237             post_change_redirect='%spassword_change/done/' % self.root_path)
     233        if self.root_path is not None:
     234            url = '%spassword_change/done/' % self.root_path
     235        else:
     236            url = reverse('%s:password_change_done' % self.name)
     237        return password_change(request, post_change_redirect=url)
    238238
    239239    def password_change_done(self, request):
    240240        """
    class AdminSite(object): 
    362362            'root_path': self.root_path,
    363363        }
    364364        context.update(extra_context or {})
     365        context_instance = template.RequestContext(request, app_name=self.name)
    365366        return render_to_response(self.index_template or 'admin/index.html', context,
    366             context_instance=template.RequestContext(request)
     367            context_instance=context_instance
    367368        )
    368369    index = never_cache(index)
    369370
    class AdminSite(object): 
    376377            'root_path': self.root_path,
    377378        }
    378379        context.update(extra_context or {})
     380        context_instance = template.RequestContext(request, app_name=self.name)
    379381        return render_to_response(self.login_template or 'admin/login.html', context,
    380             context_instance=template.RequestContext(request)
     382            context_instance=context_instance
    381383        )
    382384
    383385    def app_index(self, request, app_label, extra_context=None):
    class AdminSite(object): 
    419421            'root_path': self.root_path,
    420422        }
    421423        context.update(extra_context or {})
     424        context_instance = template.RequestContext(request, app_name=self.name)
    422425        return render_to_response(self.app_index_template or ('admin/%s/app_index.html' % app_label,
    423426            'admin/app_index.html'), context,
    424             context_instance=template.RequestContext(request)
     427            context_instance=context_instance
    425428        )
    426429
    427430    def root(self, request, url):
  • django/contrib/admin/templates/admin/base.html

    diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html
    index 8cab439..9525728 100644
    a b  
    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/admindocs/templates/admin_doc/index.html

    diff --git a/django/contrib/admindocs/templates/admin_doc/index.html b/django/contrib/admindocs/templates/admin_doc/index.html
    index 242fc73..a8b21c3 100644
    a b  
    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

    diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py
    index 4f22fe0..063aac9 100644
    a b class GenericSite(object): 
    2222    name = 'my site'
    2323
    2424def get_root_path():
    25     from django.contrib import admin
    26     try:
    27         return urlresolvers.reverse(admin.site.root, args=[''])
    28     except urlresolvers.NoReverseMatch:
    29         return getattr(settings, "ADMIN_SITE_ROOT_URL", "/admin/")
     25    return urlresolvers.reverse('admin:index')
    3026
    3127def doc_index(request):
    3228    if not utils.docutils_is_available:
    model_index = staff_member_required(model_index) 
    179175def model_detail(request, app_label, model_name):
    180176    if not utils.docutils_is_available:
    181177        return missing_docutils_page(request)
    182        
     178
    183179    # Get the model class.
    184180    try:
    185181        app_mod = models.get_app(app_label)
  • django/core/urlresolvers.py

    diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
    index 10e97bb..d056d04 100644
    a b class RegexURLPattern(object): 
    139139    callback = property(_get_callback)
    140140
    141141class RegexURLResolver(object):
    142     def __init__(self, regex, urlconf_name, default_kwargs=None):
     142    def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
    143143        # regex is a string representing a regular expression.
    144144        # urlconf_name is a string representing the module containing URLconfs.
    145145        self.regex = re.compile(regex, re.UNICODE)
    class RegexURLResolver(object): 
    148148            self._urlconf_module = self.urlconf_name
    149149        self.callback = None
    150150        self.default_kwargs = default_kwargs or {}
    151         self._reverse_dict = MultiValueDict()
     151        self.namespace = namespace
     152        self.app_name = app_name
     153        self._reverse_dict = None
     154        self._namespace_dict = None
     155        self._app_dict = None
    152156
    153157    def __repr__(self):
    154         return '<%s %s %s>' % (self.__class__.__name__, self.urlconf_name, self.regex.pattern)
    155 
    156     def _get_reverse_dict(self):
    157         if not self._reverse_dict:
    158             lookups = MultiValueDict()
    159             for pattern in reversed(self.url_patterns):
    160                 p_pattern = pattern.regex.pattern
    161                 if p_pattern.startswith('^'):
    162                     p_pattern = p_pattern[1:]
    163                 if isinstance(pattern, RegexURLResolver):
     158        return '<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)
     159
     160    def _populate(self):
     161        lookups = MultiValueDict()
     162        namespaces = {}
     163        apps = {}
     164        for pattern in reversed(self.url_patterns):
     165            p_pattern = pattern.regex.pattern
     166            if p_pattern.startswith('^'):
     167                p_pattern = p_pattern[1:]
     168            if isinstance(pattern, RegexURLResolver):
     169                if pattern.namespace:
     170                    namespaces[pattern.namespace] = (p_pattern, pattern)
     171                    if pattern.app_name:
     172                        apps.setdefault(pattern.app_name, []).append(pattern.namespace)
     173                else:
    164174                    parent = normalize(pattern.regex.pattern)
    165175                    for name in pattern.reverse_dict:
    166176                        for matches, pat in pattern.reverse_dict.getlist(name):
    class RegexURLResolver(object): 
    168178                            for piece, p_args in parent:
    169179                                new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches])
    170180                            lookups.appendlist(name, (new_matches, p_pattern + pat))
    171                 else:
    172                     bits = normalize(p_pattern)
    173                     lookups.appendlist(pattern.callback, (bits, p_pattern))
    174                     lookups.appendlist(pattern.name, (bits, p_pattern))
    175             self._reverse_dict = lookups
     181                    for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items():
     182                        namespaces[namespace] = (p_pattern + prefix, sub_pattern)
     183                    for app_name, namespace_list in pattern.app_dict.items():
     184                        apps.setdefault(app_name, []).extend(namespace_list)
     185            else:
     186                bits = normalize(p_pattern)
     187                lookups.appendlist(pattern.callback, (bits, p_pattern))
     188                lookups.appendlist(pattern.name, (bits, p_pattern))
     189        self._reverse_dict = lookups
     190        self._namespace_dict = namespaces
     191        self._app_dict = apps
     192
     193    def _get_reverse_dict(self):
     194        if self._reverse_dict is None:
     195            self._populate()
    176196        return self._reverse_dict
    177197    reverse_dict = property(_get_reverse_dict)
    178198
     199    def _get_namespace_dict(self):
     200        if self._namespace_dict is None:
     201            self._populate()
     202        return self._namespace_dict
     203    namespace_dict = property(_get_namespace_dict)
     204
     205    def _get_app_dict(self):
     206        if self._app_dict is None:
     207            self._populate()
     208        return self._app_dict
     209    app_dict = property(_get_app_dict)
     210
    179211    def resolve(self, path):
    180212        tried = []
    181213        match = self.regex.search(path)
    class RegexURLResolver(object): 
    261293def resolve(path, urlconf=None):
    262294    return get_resolver(urlconf).resolve(path)
    263295
    264 def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None):
     296def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, app_name=None):
     297    parts = viewname.split(':')
     298    parts.reverse()
     299    view = parts[0]
     300    path = parts[1:]
     301
    265302    args = args or []
    266303    kwargs = kwargs or {}
    267304    if prefix is None:
    268305        prefix = get_script_prefix()
    269     return iri_to_uri(u'%s%s' % (prefix, get_resolver(urlconf).reverse(viewname,
     306
     307    resolver = get_resolver(urlconf)
     308    resolved_path = []
     309    while path:
     310        ns = path.pop()
     311
     312        # Lookup the name to see if it could be an app identifier
     313        try:
     314            app_list = resolver.app_dict[ns]
     315            # Yes! Path part matches an app in the current Resolver
     316            if app_name and app_name in app_list:
     317                # If we are reversing for a particular app, use that namespace
     318                ns = app_name
     319            elif ns not in app_list:
     320                # The name isn't shared by one of the instances (i.e., the default)
     321                # so just pick the first instance as the default.
     322                ns = app_list[0]
     323        except KeyError:
     324            pass
     325
     326        try:
     327            extra, resolver = resolver.namespace_dict[ns]
     328            resolved_path.append(ns)
     329            prefix = prefix + extra
     330        except KeyError, key:
     331            if resolved_path:
     332                raise NoReverseMatch("%s is not a registered namespace inside '%s'" % (key, ':'.join(resolved_path)))
     333            else:
     334                raise NoReverseMatch("%s is not a registered namespace" % key)
     335
     336    return iri_to_uri(u'%s%s' % (prefix, resolver.reverse(view,
    270337            *args, **kwargs)))
    271338
    272339def clear_url_caches():
  • django/template/context.py

    diff --git a/django/template/context.py b/django/template/context.py
    index 0ccb5fa..e79af4d 100644
    a b class ContextPopException(Exception): 
    99
    1010class Context(object):
    1111    "A stack container for variable context"
    12     def __init__(self, dict_=None, autoescape=True):
     12    def __init__(self, dict_=None, autoescape=True, app_name=None):
    1313        dict_ = dict_ or {}
    1414        self.dicts = [dict_]
    1515        self.autoescape = autoescape
     16        self.app_name = app_name
    1617
    1718    def __repr__(self):
    1819        return repr(self.dicts)
    class RequestContext(Context): 
    9697    Additional processors can be specified as a list of callables
    9798    using the "processors" keyword argument.
    9899    """
    99     def __init__(self, request, dict=None, processors=None):
    100         Context.__init__(self, dict)
     100    def __init__(self, request, dict=None, processors=None, app_name=None):
     101        Context.__init__(self, dict, app_name=app_name)
    101102        if processors is None:
    102103            processors = ()
    103104        else:
  • django/template/defaulttags.py

    diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
    index 7d91cd6..a61e965 100644
    a b class URLNode(Node): 
    367367        # {% url ... as var %} construct in which cause return nothing.
    368368        url = ''
    369369        try:
    370             url = reverse(self.view_name, args=args, kwargs=kwargs)
     370            url = reverse(self.view_name, args=args, kwargs=kwargs, app_name=context.app_name)
    371371        except NoReverseMatch, e:
    372372            if settings.SETTINGS_MODULE:
    373373                project_name = settings.SETTINGS_MODULE.split('.')[0]
    374374                try:
    375375                    url = reverse(project_name + '.' + self.view_name,
    376                               args=args, kwargs=kwargs)
     376                              args=args, kwargs=kwargs, app_name=context.app_name)
    377377                except NoReverseMatch:
    378378                    if self.asvar is None:
    379379                        # Re-raise the original exception, not the one with
    380                         # the path relative to the project. This makes a 
     380                        # the path relative to the project. This makes a
    381381                        # better error message.
    382382                        raise e
    383383            else:
  • tests/regressiontests/admin_views/customadmin.py

    diff --git a/tests/regressiontests/admin_views/customadmin.py b/tests/regressiontests/admin_views/customadmin.py
    index 70e87eb..4aa781e 100644
    a b import models 
    1010class Admin2(admin.AdminSite):
    1111    login_template = 'custom_admin/login.html'
    1212    index_template = 'custom_admin/index.html'
    13    
     13
    1414    # A custom index view.
    1515    def index(self, request, extra_context=None):
    1616        return super(Admin2, self).index(request, {'foo': '*bar*'})
    17    
     17
    1818    def get_urls(self):
     19        base_patterns, app_name, name = super(Admin2, self).get_urls()
    1920        return patterns('',
    2021            (r'^my_view/$', self.admin_view(self.my_view)),
    21         ) + super(Admin2, self).get_urls()
    22    
     22        ) + base_patterns, app_name, name
     23
    2324    def my_view(self, request):
    2425        return HttpResponse("Django is a magical pony!")
    25    
     26
    2627site = Admin2(name="admin2")
    2728
    2829site.register(models.Article, models.ArticleAdmin)
  • tests/regressiontests/admin_views/tests.py

    diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
    index 99168fd..3fc145b 100644
    a b class AdminViewBasicTest(TestCase): 
    204204        response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'})
    205205        self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
    206206
     207    def testLogoutAndPasswordChangeURLs(self):
     208        response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit)
     209        self.failIf('<a href="/test_admin/%s/logout/">' % self.urlbit not in response.content)
     210        self.failIf('<a href="/test_admin/%s/password_change/">' % self.urlbit not in response.content)
     211
    207212    def testNamedGroupFieldChoicesChangeList(self):
    208213        """
    209214        Ensures the admin changelist shows correct values in the relevant column
  • new file tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py

    diff --git a/tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py b/tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py
    new file mode 100644
    index 0000000..0731906
    - +  
     1from django.conf.urls.defaults import *
     2from namespace_urls import URLObject
     3
     4testobj3 = URLObject('testapp', 'test-ns3')
     5
     6urlpatterns = patterns('regressiontests.urlpatterns_reverse.views',
     7    url(r'^normal/$', 'empty_view', name='inc-normal-view'),
     8    url(r'^normal/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='inc-normal-view'),
     9
     10    (r'^test3/', include(testobj3.urls)),
     11    (r'^ns-included3/', include('regressiontests.urlpatterns_reverse.included_urls', namespace='inc-ns3')),
     12)
     13
  • new file tests/regressiontests/urlpatterns_reverse/namespace_urls.py

    diff --git a/tests/regressiontests/urlpatterns_reverse/namespace_urls.py b/tests/regressiontests/urlpatterns_reverse/namespace_urls.py
    new file mode 100644
    index 0000000..27cc7f7
    - +  
     1from django.conf.urls.defaults import *
     2
     3class URLObject(object):
     4    def __init__(self, app_name, namespace):
     5        self.app_name = app_name
     6        self.namespace = namespace
     7
     8    def urls(self):
     9        return patterns('',
     10            url(r'^inner/$', 'empty_view', name='urlobject-view'),
     11            url(r'^inner/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='urlobject-view'),
     12        ), self.app_name, self.namespace
     13    urls = property(urls)
     14
     15testobj1 = URLObject('testapp', 'test-ns1')
     16testobj2 = URLObject('testapp', 'test-ns2')
     17default_testobj = URLObject('testapp', 'testapp')
     18
     19otherobj1 = URLObject('nodefault', 'other-ns1')
     20otherobj2 = URLObject('nodefault', 'other-ns2')
     21
     22urlpatterns = patterns('regressiontests.urlpatterns_reverse.views',
     23    url(r'^normal/$', 'empty_view', name='normal-view'),
     24    url(r'^normal/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='normal-view'),
     25
     26    (r'^test1/', include(testobj1.urls)),
     27    (r'^test2/', include(testobj2.urls)),
     28    (r'^default/', include(default_testobj.urls)),
     29
     30    (r'^other1/', include(otherobj1.urls)),
     31    (r'^other2/', include(otherobj2.urls)),
     32
     33    (r'^ns-included1/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')),
     34    (r'^ns-included2/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')),
     35
     36    (r'^included/', include('regressiontests.urlpatterns_reverse.included_namespace_urls')),
     37
     38)
  • tests/regressiontests/urlpatterns_reverse/tests.py

    diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py
    index 9def6b2..68e34eb 100644
    a b class ReverseShortcutTests(TestCase): 
    158158        res = redirect('/foo/')
    159159        self.assertEqual(res['Location'], '/foo/')
    160160        res = redirect('http://example.com/')
    161         self.assertEqual(res['Location'], 'http://example.com/')
    162  No newline at end of file
     161        self.assertEqual(res['Location'], 'http://example.com/')
     162
     163
     164class NamespaceTests(TestCase):
     165    urls = 'regressiontests.urlpatterns_reverse.namespace_urls'
     166
     167    def test_ambiguous_object(self):
     168        "Names deployed via dynamic URL objects that require namespaces can't be resolved"
     169        self.assertRaises(NoReverseMatch, reverse, 'urlobject-view')
     170        self.assertRaises(NoReverseMatch, reverse, 'urlobject-view', args=[37,42])
     171        self.assertRaises(NoReverseMatch, reverse, 'urlobject-view', kwargs={'arg1':42, 'arg2':37})
     172
     173    def test_ambiguous_urlpattern(self):
     174        "Names deployed via dynamic URL objects that require namespaces can't be resolved"
     175        self.assertRaises(NoReverseMatch, reverse, 'inner-nothing')
     176        self.assertRaises(NoReverseMatch, reverse, 'inner-nothing', args=[37,42])
     177        self.assertRaises(NoReverseMatch, reverse, 'inner-nothing', kwargs={'arg1':42, 'arg2':37})
     178
     179    def test_non_existent_namespace(self):
     180        "Non-existent namespaces raise errors"
     181        self.assertRaises(NoReverseMatch, reverse, 'blahblah:urlobject-view')
     182        self.assertRaises(NoReverseMatch, reverse, 'test-ns1:blahblah:urlobject-view')
     183
     184    def test_normal_name(self):
     185        "Normal lookups work as expected"
     186        self.assertEquals('/normal/', reverse('normal-view'))
     187        self.assertEquals('/normal/37/42/', reverse('normal-view', args=[37,42]))
     188        self.assertEquals('/normal/42/37/', reverse('normal-view', kwargs={'arg1':42, 'arg2':37}))
     189
     190    def test_simple_included_name(self):
     191        "Normal lookups work on names included from other patterns"
     192        self.assertEquals('/included/normal/', reverse('inc-normal-view'))
     193        self.assertEquals('/included/normal/37/42/', reverse('inc-normal-view', args=[37,42]))
     194        self.assertEquals('/included/normal/42/37/', reverse('inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
     195
     196    def test_namespace_object(self):
     197        "Dynamic URL objects can be found using a namespace"
     198        self.assertEquals('/test1/inner/', reverse('test-ns1:urlobject-view'))
     199        self.assertEquals('/test1/inner/37/42/', reverse('test-ns1:urlobject-view', args=[37,42]))
     200        self.assertEquals('/test1/inner/42/37/', reverse('test-ns1:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
     201
     202    def test_embedded_namespace_object(self):
     203        "Namespaces can be installed anywhere in the URL pattern tree"
     204        self.assertEquals('/included/test3/inner/', reverse('test-ns3:urlobject-view'))
     205        self.assertEquals('/included/test3/inner/37/42/', reverse('test-ns3:urlobject-view', args=[37,42]))
     206        self.assertEquals('/included/test3/inner/42/37/', reverse('test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
     207
     208    def test_namespace_pattern(self):
     209        "Namespaces can be applied to include()'d urlpatterns"
     210        self.assertEquals('/ns-included1/normal/', reverse('inc-ns1:inc-normal-view'))
     211        self.assertEquals('/ns-included1/normal/37/42/', reverse('inc-ns1:inc-normal-view', args=[37,42]))
     212        self.assertEquals('/ns-included1/normal/42/37/', reverse('inc-ns1:inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
     213
     214    def test_multiple_namespace_pattern(self):
     215        "Namespaces can be embedded"
     216        self.assertEquals('/ns-included1/test3/inner/', reverse('inc-ns1:test-ns3:urlobject-view'))
     217        self.assertEquals('/ns-included1/test3/inner/37/42/', reverse('inc-ns1:test-ns3:urlobject-view', args=[37,42]))
     218        self.assertEquals('/ns-included1/test3/inner/42/37/', reverse('inc-ns1:test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
     219
     220    def test_app_lookup_object(self):
     221        "A default application namespace can be used for lookup"
     222        self.assertEquals('/default/inner/', reverse('testapp:urlobject-view'))
     223        self.assertEquals('/default/inner/37/42/', reverse('testapp:urlobject-view', args=[37,42]))
     224        self.assertEquals('/default/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
     225
     226    def test_app_lookup_object_with_default(self):
     227        "A default application namespace is sensitive to the 'current' app can be used for lookup"
     228        self.assertEquals('/included/test3/inner/', reverse('testapp:urlobject-view', app_name='test-ns3'))
     229        self.assertEquals('/included/test3/inner/37/42/', reverse('testapp:urlobject-view', args=[37,42], app_name='test-ns3'))
     230        self.assertEquals('/included/test3/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1':42, 'arg2':37}, app_name='test-ns3'))
     231
     232    def test_app_lookup_object_without_default(self):
     233        "An application namespace without a default is sensitive to the 'current' app can be used for lookup"
     234        self.assertEquals('/other2/inner/', reverse('nodefault:urlobject-view'))
     235        self.assertEquals('/other2/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42]))
     236        self.assertEquals('/other2/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
     237
     238        self.assertEquals('/other1/inner/', reverse('nodefault:urlobject-view', app_name='other-ns1'))
     239        self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], app_name='other-ns1'))
     240        self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, app_name='other-ns1'))
     241
Back to Top