PageRenderTime 1319ms CodeModel.GetById 95ms app.highlight 907ms RepoModel.GetById 58ms app.codeStats 2ms

/tests/regressiontests/urlpatterns_reverse/tests.py

https://code.google.com/p/mango-py/
Python | 451 lines | 444 code | 2 blank | 5 comment | 3 complexity | 0461dc3f70e71c3dd4109989026bb905 MD5 | raw file
  1"""
  2Unit tests for reverse URL lookups.
  3"""
  4from django.conf import settings
  5from django.core.exceptions import ImproperlyConfigured
  6from django.core.urlresolvers import reverse, resolve, NoReverseMatch,\
  7                                     Resolver404, ResolverMatch,\
  8                                     RegexURLResolver, RegexURLPattern
  9from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
 10from django.shortcuts import redirect
 11from django.test import TestCase
 12from django.utils import unittest
 13
 14import urlconf_outer
 15import urlconf_inner
 16import middleware
 17import views
 18
 19resolve_test_data = (
 20    # These entries are in the format: (path, url_name, app_name, namespace, view_func, args, kwargs)
 21    # Simple case
 22    ('/normal/42/37/', 'normal-view', None, '', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}),
 23    ('/view_class/42/37/', 'view-class', None, '', views.view_class_instance, tuple(), {'arg1': '42', 'arg2': '37'}),
 24    ('/included/normal/42/37/', 'inc-normal-view', None, '', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}),
 25    ('/included/view_class/42/37/', 'inc-view-class', None, '', views.view_class_instance, tuple(), {'arg1': '42', 'arg2': '37'}),
 26
 27    # Unnamed args are dropped if you have *any* kwargs in a pattern
 28    ('/mixed_args/42/37/', 'mixed-args', None, '', views.empty_view, tuple(), {'arg2': '37'}),
 29    ('/included/mixed_args/42/37/', 'inc-mixed-args', None, '', views.empty_view, tuple(), {'arg2': '37'}),
 30
 31    # Unnamed views will be resolved to the function/class name
 32    ('/unnamed/normal/42/37/', 'regressiontests.urlpatterns_reverse.views.empty_view', None, '', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}),
 33    ('/unnamed/view_class/42/37/', 'regressiontests.urlpatterns_reverse.views.ViewClass', None, '', views.view_class_instance, tuple(), {'arg1': '42', 'arg2': '37'}),
 34
 35    # If you have no kwargs, you get an args list.
 36    ('/no_kwargs/42/37/', 'no-kwargs', None, '', views.empty_view, ('42','37'), {}),
 37    ('/included/no_kwargs/42/37/', 'inc-no-kwargs', None, '', views.empty_view, ('42','37'), {}),
 38
 39    # Namespaces
 40    ('/test1/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns1', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}),
 41    ('/included/test3/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns3', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}),
 42    ('/ns-included1/normal/42/37/', 'inc-normal-view', None, 'inc-ns1', views.empty_view, tuple(), {'arg1': '42', 'arg2': '37'}),
 43    ('/included/test3/inner/42/37/', 'urlobject-view', 'testapp', 'test-ns3', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}),
 44    ('/default/inner/42/37/', 'urlobject-view', 'testapp', 'testapp', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}),
 45    ('/other2/inner/42/37/', 'urlobject-view', 'nodefault', 'other-ns2', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}),
 46    ('/other1/inner/42/37/', 'urlobject-view', 'nodefault', 'other-ns1', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}),
 47
 48    # Nested namespaces
 49    ('/ns-included1/test3/inner/42/37/', 'urlobject-view', 'testapp', 'inc-ns1:test-ns3', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}),
 50    ('/ns-included1/ns-included4/ns-included2/test3/inner/42/37/', 'urlobject-view', 'testapp', 'inc-ns1:inc-ns4:inc-ns2:test-ns3', 'empty_view', tuple(), {'arg1': '42', 'arg2': '37'}),
 51)
 52
 53test_data = (
 54    ('places', '/places/3/', [3], {}),
 55    ('places', '/places/3/', ['3'], {}),
 56    ('places', NoReverseMatch, ['a'], {}),
 57    ('places', NoReverseMatch, [], {}),
 58    ('places?', '/place/', [], {}),
 59    ('places+', '/places/', [], {}),
 60    ('places*', '/place/', [], {}),
 61    ('places2?', '/', [], {}),
 62    ('places2+', '/places/', [], {}),
 63    ('places2*', '/', [], {}),
 64    ('places3', '/places/4/', [4], {}),
 65    ('places3', '/places/harlem/', ['harlem'], {}),
 66    ('places3', NoReverseMatch, ['harlem64'], {}),
 67    ('places4', '/places/3/', [], {'id': 3}),
 68    ('people', NoReverseMatch, [], {}),
 69    ('people', '/people/adrian/', ['adrian'], {}),
 70    ('people', '/people/adrian/', [], {'name': 'adrian'}),
 71    ('people', NoReverseMatch, ['name with spaces'], {}),
 72    ('people', NoReverseMatch, [], {'name': 'name with spaces'}),
 73    ('people2', '/people/name/', [], {}),
 74    ('people2a', '/people/name/fred/', ['fred'], {}),
 75    ('optional', '/optional/fred/', [], {'name': 'fred'}),
 76    ('optional', '/optional/fred/', ['fred'], {}),
 77    ('hardcoded', '/hardcoded/', [], {}),
 78    ('hardcoded2', '/hardcoded/doc.pdf', [], {}),
 79    ('people3', '/people/il/adrian/', [], {'state': 'il', 'name': 'adrian'}),
 80    ('people3', NoReverseMatch, [], {'state': 'il'}),
 81    ('people3', NoReverseMatch, [], {'name': 'adrian'}),
 82    ('people4', NoReverseMatch, [], {'state': 'il', 'name': 'adrian'}),
 83    ('people6', '/people/il/test/adrian/', ['il/test', 'adrian'], {}),
 84    ('people6', '/people//adrian/', ['adrian'], {}),
 85    ('range', '/character_set/a/', [], {}),
 86    ('range2', '/character_set/x/', [], {}),
 87    ('price', '/price/$10/', ['10'], {}),
 88    ('price2', '/price/$10/', ['10'], {}),
 89    ('price3', '/price/$10/', ['10'], {}),
 90    ('product', '/product/chocolate+($2.00)/', [], {'price': '2.00', 'product': 'chocolate'}),
 91    ('headlines', '/headlines/2007.5.21/', [], dict(year=2007, month=5, day=21)),
 92    ('windows', r'/windows_path/C:%5CDocuments%20and%20Settings%5Cspam/', [], dict(drive_name='C', path=r'Documents and Settings\spam')),
 93    ('special', r'/special_chars/+%5C$*/', [r'+\$*'], {}),
 94    ('special', NoReverseMatch, [''], {}),
 95    ('mixed', '/john/0/', [], {'name': 'john'}),
 96    ('repeats', '/repeats/a/', [], {}),
 97    ('repeats2', '/repeats/aa/', [], {}),
 98    ('repeats3', '/repeats/aa/', [], {}),
 99    ('insensitive', '/CaseInsensitive/fred', ['fred'], {}),
100    ('test', '/test/1', [], {}),
101    ('test2', '/test/2', [], {}),
102    ('inner-nothing', '/outer/42/', [], {'outer': '42'}),
103    ('inner-nothing', '/outer/42/', ['42'], {}),
104    ('inner-nothing', NoReverseMatch, ['foo'], {}),
105    ('inner-extra', '/outer/42/extra/inner/', [], {'extra': 'inner', 'outer': '42'}),
106    ('inner-extra', '/outer/42/extra/inner/', ['42', 'inner'], {}),
107    ('inner-extra', NoReverseMatch, ['fred', 'inner'], {}),
108    ('disjunction', NoReverseMatch, ['foo'], {}),
109    ('inner-disjunction', NoReverseMatch, ['10', '11'], {}),
110    ('extra-places', '/e-places/10/', ['10'], {}),
111    ('extra-people', '/e-people/fred/', ['fred'], {}),
112    ('extra-people', '/e-people/fred/', [], {'name': 'fred'}),
113    ('part', '/part/one/', [], {'value': 'one'}),
114    ('part', '/prefix/xx/part/one/', [], {'value': 'one', 'prefix': 'xx'}),
115    ('part2', '/part2/one/', [], {'value': 'one'}),
116    ('part2', '/part2/', [], {}),
117    ('part2', '/prefix/xx/part2/one/', [], {'value': 'one', 'prefix': 'xx'}),
118    ('part2', '/prefix/xx/part2/', [], {'prefix': 'xx'}),
119
120    # Regression for #9038
121    # These views are resolved by method name. Each method is deployed twice -
122    # once with an explicit argument, and once using the default value on
123    # the method. This is potentially ambiguous, as you have to pick the
124    # correct view for the arguments provided.
125    ('kwargs_view', '/arg_view/', [], {}),
126    ('kwargs_view', '/arg_view/10/', [], {'arg1':10}),
127    ('regressiontests.urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/', [], {}),
128    ('regressiontests.urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/10/', [], {'arg1':10}),
129    ('non_path_include', '/includes/non_path_include/', [], {})
130
131)
132
133class NoURLPatternsTests(TestCase):
134    urls = 'regressiontests.urlpatterns_reverse.no_urls'
135
136    def assertRaisesErrorWithMessage(self, error, message, callable,
137        *args, **kwargs):
138        self.assertRaises(error, callable, *args, **kwargs)
139        try:
140            callable(*args, **kwargs)
141        except error, e:
142            self.assertEqual(message, str(e))
143
144    def test_no_urls_exception(self):
145        """
146        RegexURLResolver should raise an exception when no urlpatterns exist.
147        """
148        resolver = RegexURLResolver(r'^$', self.urls)
149
150        self.assertRaisesErrorWithMessage(ImproperlyConfigured,
151            "The included urlconf regressiontests.urlpatterns_reverse.no_urls "\
152            "doesn't have any patterns in it", getattr, resolver, 'url_patterns')
153
154class URLPatternReverse(TestCase):
155    urls = 'regressiontests.urlpatterns_reverse.urls'
156
157    def test_urlpattern_reverse(self):
158        for name, expected, args, kwargs in test_data:
159            try:
160                got = reverse(name, args=args, kwargs=kwargs)
161            except NoReverseMatch, e:
162                self.assertEqual(expected, NoReverseMatch)
163            else:
164                self.assertEqual(got, expected)
165
166    def test_reverse_none(self):
167        # Reversing None should raise an error, not return the last un-named view.
168        self.assertRaises(NoReverseMatch, reverse, None)
169
170class ResolverTests(unittest.TestCase):
171    def test_non_regex(self):
172        """
173        Verifies that we raise a Resolver404 if what we are resolving doesn't
174        meet the basic requirements of a path to match - i.e., at the very
175        least, it matches the root pattern '^/'. We must never return None
176        from resolve, or we will get a TypeError further down the line.
177
178        Regression for #10834.
179        """
180        self.assertRaises(Resolver404, resolve, '')
181        self.assertRaises(Resolver404, resolve, 'a')
182        self.assertRaises(Resolver404, resolve, '\\')
183        self.assertRaises(Resolver404, resolve, '.')
184
185    def test_404_tried_urls_have_names(self):
186        """
187        Verifies that the list of URLs that come back from a Resolver404
188        exception contains a list in the right format for printing out in
189        the DEBUG 404 page with both the patterns and URL names, if available.
190        """
191        urls = 'regressiontests.urlpatterns_reverse.named_urls'
192        # this list matches the expected URL types and names returned when
193        # you try to resolve a non-existent URL in the first level of included
194        # URLs in named_urls.py (e.g., '/included/non-existent-url')
195        url_types_names = [
196            [{'type': RegexURLPattern, 'name': 'named-url1'}],
197            [{'type': RegexURLPattern, 'name': 'named-url2'}],
198            [{'type': RegexURLPattern, 'name': None}],
199            [{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': 'named-url3'}],
200            [{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': 'named-url4'}],
201            [{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': None}],
202            [{'type': RegexURLResolver}, {'type': RegexURLResolver}],
203        ]
204        try:
205            resolve('/included/non-existent-url', urlconf=urls)
206            self.fail('resolve did not raise a 404')
207        except Resolver404, e:
208            # make sure we at least matched the root ('/') url resolver:
209            self.assertTrue('tried' in e.args[0])
210            tried = e.args[0]['tried']
211            self.assertEqual(len(e.args[0]['tried']), len(url_types_names), 'Wrong number of tried URLs returned.  Expected %s, got %s.' % (len(url_types_names), len(e.args[0]['tried'])))
212            for tried, expected in zip(e.args[0]['tried'], url_types_names):
213                for t, e in zip(tried, expected):
214                    self.assertTrue(isinstance(t, e['type']), '%s is not an instance of %s' % (t, e['type']))
215                    if 'name' in e:
216                        if not e['name']:
217                            self.assertTrue(t.name is None, 'Expected no URL name but found %s.' % t.name)
218                        else:
219                            self.assertEqual(t.name, e['name'], 'Wrong URL name.  Expected "%s", got "%s".' % (e['name'], t.name))
220
221class ReverseShortcutTests(TestCase):
222    urls = 'regressiontests.urlpatterns_reverse.urls'
223
224    def test_redirect_to_object(self):
225        # We don't really need a model; just something with a get_absolute_url
226        class FakeObj(object):
227            def get_absolute_url(self):
228                return "/hi-there/"
229
230        res = redirect(FakeObj())
231        self.assertTrue(isinstance(res, HttpResponseRedirect))
232        self.assertEqual(res['Location'], '/hi-there/')
233
234        res = redirect(FakeObj(), permanent=True)
235        self.assertTrue(isinstance(res, HttpResponsePermanentRedirect))
236        self.assertEqual(res['Location'], '/hi-there/')
237
238    def test_redirect_to_view_name(self):
239        res = redirect('hardcoded2')
240        self.assertEqual(res['Location'], '/hardcoded/doc.pdf')
241        res = redirect('places', 1)
242        self.assertEqual(res['Location'], '/places/1/')
243        res = redirect('headlines', year='2008', month='02', day='17')
244        self.assertEqual(res['Location'], '/headlines/2008.02.17/')
245        self.assertRaises(NoReverseMatch, redirect, 'not-a-view')
246
247    def test_redirect_to_url(self):
248        res = redirect('/foo/')
249        self.assertEqual(res['Location'], '/foo/')
250        res = redirect('http://example.com/')
251        self.assertEqual(res['Location'], 'http://example.com/')
252
253    def test_redirect_view_object(self):
254        from views import absolute_kwargs_view
255        res = redirect(absolute_kwargs_view)
256        self.assertEqual(res['Location'], '/absolute_arg_view/')
257        self.assertRaises(NoReverseMatch, redirect, absolute_kwargs_view, wrong_argument=None)
258
259
260class NamespaceTests(TestCase):
261    urls = 'regressiontests.urlpatterns_reverse.namespace_urls'
262
263    def test_ambiguous_object(self):
264        "Names deployed via dynamic URL objects that require namespaces can't be resolved"
265        self.assertRaises(NoReverseMatch, reverse, 'urlobject-view')
266        self.assertRaises(NoReverseMatch, reverse, 'urlobject-view', args=[37,42])
267        self.assertRaises(NoReverseMatch, reverse, 'urlobject-view', kwargs={'arg1':42, 'arg2':37})
268
269    def test_ambiguous_urlpattern(self):
270        "Names deployed via dynamic URL objects that require namespaces can't be resolved"
271        self.assertRaises(NoReverseMatch, reverse, 'inner-nothing')
272        self.assertRaises(NoReverseMatch, reverse, 'inner-nothing', args=[37,42])
273        self.assertRaises(NoReverseMatch, reverse, 'inner-nothing', kwargs={'arg1':42, 'arg2':37})
274
275    def test_non_existent_namespace(self):
276        "Non-existent namespaces raise errors"
277        self.assertRaises(NoReverseMatch, reverse, 'blahblah:urlobject-view')
278        self.assertRaises(NoReverseMatch, reverse, 'test-ns1:blahblah:urlobject-view')
279
280    def test_normal_name(self):
281        "Normal lookups work as expected"
282        self.assertEqual('/normal/', reverse('normal-view'))
283        self.assertEqual('/normal/37/42/', reverse('normal-view', args=[37,42]))
284        self.assertEqual('/normal/42/37/', reverse('normal-view', kwargs={'arg1':42, 'arg2':37}))
285
286    def test_simple_included_name(self):
287        "Normal lookups work on names included from other patterns"
288        self.assertEqual('/included/normal/', reverse('inc-normal-view'))
289        self.assertEqual('/included/normal/37/42/', reverse('inc-normal-view', args=[37,42]))
290        self.assertEqual('/included/normal/42/37/', reverse('inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
291
292    def test_namespace_object(self):
293        "Dynamic URL objects can be found using a namespace"
294        self.assertEqual('/test1/inner/', reverse('test-ns1:urlobject-view'))
295        self.assertEqual('/test1/inner/37/42/', reverse('test-ns1:urlobject-view', args=[37,42]))
296        self.assertEqual('/test1/inner/42/37/', reverse('test-ns1:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
297
298    def test_embedded_namespace_object(self):
299        "Namespaces can be installed anywhere in the URL pattern tree"
300        self.assertEqual('/included/test3/inner/', reverse('test-ns3:urlobject-view'))
301        self.assertEqual('/included/test3/inner/37/42/', reverse('test-ns3:urlobject-view', args=[37,42]))
302        self.assertEqual('/included/test3/inner/42/37/', reverse('test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
303
304    def test_namespace_pattern(self):
305        "Namespaces can be applied to include()'d urlpatterns"
306        self.assertEqual('/ns-included1/normal/', reverse('inc-ns1:inc-normal-view'))
307        self.assertEqual('/ns-included1/normal/37/42/', reverse('inc-ns1:inc-normal-view', args=[37,42]))
308        self.assertEqual('/ns-included1/normal/42/37/', reverse('inc-ns1:inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
309
310    def test_multiple_namespace_pattern(self):
311        "Namespaces can be embedded"
312        self.assertEqual('/ns-included1/test3/inner/', reverse('inc-ns1:test-ns3:urlobject-view'))
313        self.assertEqual('/ns-included1/test3/inner/37/42/', reverse('inc-ns1:test-ns3:urlobject-view', args=[37,42]))
314        self.assertEqual('/ns-included1/test3/inner/42/37/', reverse('inc-ns1:test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
315
316    def test_nested_namespace_pattern(self):
317        "Namespaces can be nested"
318        self.assertEqual('/ns-included1/ns-included4/ns-included1/test3/inner/', reverse('inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view'))
319        self.assertEqual('/ns-included1/ns-included4/ns-included1/test3/inner/37/42/', reverse('inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view', args=[37,42]))
320        self.assertEqual('/ns-included1/ns-included4/ns-included1/test3/inner/42/37/', reverse('inc-ns1:inc-ns4:inc-ns1:test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
321
322    def test_app_lookup_object(self):
323        "A default application namespace can be used for lookup"
324        self.assertEqual('/default/inner/', reverse('testapp:urlobject-view'))
325        self.assertEqual('/default/inner/37/42/', reverse('testapp:urlobject-view', args=[37,42]))
326        self.assertEqual('/default/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
327
328    def test_app_lookup_object_with_default(self):
329        "A default application namespace is sensitive to the 'current' app can be used for lookup"
330        self.assertEqual('/included/test3/inner/', reverse('testapp:urlobject-view', current_app='test-ns3'))
331        self.assertEqual('/included/test3/inner/37/42/', reverse('testapp:urlobject-view', args=[37,42], current_app='test-ns3'))
332        self.assertEqual('/included/test3/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='test-ns3'))
333
334    def test_app_lookup_object_without_default(self):
335        "An application namespace without a default is sensitive to the 'current' app can be used for lookup"
336        self.assertEqual('/other2/inner/', reverse('nodefault:urlobject-view'))
337        self.assertEqual('/other2/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42]))
338        self.assertEqual('/other2/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
339
340        self.assertEqual('/other1/inner/', reverse('nodefault:urlobject-view', current_app='other-ns1'))
341        self.assertEqual('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1'))
342        self.assertEqual('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1'))
343
344class RequestURLconfTests(TestCase):
345    def setUp(self):
346        self.root_urlconf = settings.ROOT_URLCONF
347        self.middleware_classes = settings.MIDDLEWARE_CLASSES
348        settings.ROOT_URLCONF = urlconf_outer.__name__
349
350    def tearDown(self):
351        settings.ROOT_URLCONF = self.root_urlconf
352        settings.MIDDLEWARE_CLASSES = self.middleware_classes
353
354    def test_urlconf(self):
355        response = self.client.get('/test/me/')
356        self.assertEqual(response.status_code, 200)
357        self.assertEqual(response.content, 'outer:/test/me/,'
358                                           'inner:/inner_urlconf/second_test/')
359        response = self.client.get('/inner_urlconf/second_test/')
360        self.assertEqual(response.status_code, 200)
361        response = self.client.get('/second_test/')
362        self.assertEqual(response.status_code, 404)
363
364    def test_urlconf_overridden(self):
365        settings.MIDDLEWARE_CLASSES += (
366            '%s.ChangeURLconfMiddleware' % middleware.__name__,
367        )
368        response = self.client.get('/test/me/')
369        self.assertEqual(response.status_code, 404)
370        response = self.client.get('/inner_urlconf/second_test/')
371        self.assertEqual(response.status_code, 404)
372        response = self.client.get('/second_test/')
373        self.assertEqual(response.status_code, 200)
374        self.assertEqual(response.content, 'outer:,inner:/second_test/')
375
376    def test_urlconf_overridden_with_null(self):
377        settings.MIDDLEWARE_CLASSES += (
378            '%s.NullChangeURLconfMiddleware' % middleware.__name__,
379        )
380        self.assertRaises(ImproperlyConfigured, self.client.get, '/test/me/')
381
382class ErrorHandlerResolutionTests(TestCase):
383    """Tests for handler404 and handler500"""
384
385    def setUp(self):
386        from django.core.urlresolvers import RegexURLResolver
387        urlconf = 'regressiontests.urlpatterns_reverse.urls_error_handlers'
388        urlconf_callables = 'regressiontests.urlpatterns_reverse.urls_error_handlers_callables'
389        self.resolver = RegexURLResolver(r'^$', urlconf)
390        self.callable_resolver = RegexURLResolver(r'^$', urlconf_callables)
391
392    def test_named_handlers(self):
393        from views import empty_view
394        handler = (empty_view, {})
395        self.assertEqual(self.resolver.resolve404(), handler)
396        self.assertEqual(self.resolver.resolve500(), handler)
397
398    def test_callable_handers(self):
399        from views import empty_view
400        handler = (empty_view, {})
401        self.assertEqual(self.callable_resolver.resolve404(), handler)
402        self.assertEqual(self.callable_resolver.resolve500(), handler)
403
404class DefaultErrorHandlerTests(TestCase):
405    urls = 'regressiontests.urlpatterns_reverse.urls_without_full_import'
406
407    def test_default_handler(self):
408        "If the urls.py doesn't specify handlers, the defaults are used"
409        try:
410            response = self.client.get('/test/')
411            self.assertEqual(response.status_code, 404)
412        except AttributeError:
413            self.fail("Shouldn't get an AttributeError due to undefined 404 handler")
414
415        try:
416            self.assertRaises(ValueError, self.client.get, '/bad_view/')
417        except AttributeError:
418            self.fail("Shouldn't get an AttributeError due to undefined 500 handler")
419
420class NoRootUrlConfTests(TestCase):
421    """Tests for handler404 and handler500 if urlconf is None"""
422    urls = None
423
424    def test_no_handler_exception(self):
425        self.assertRaises(ImproperlyConfigured, self.client.get, '/test/me/')
426
427class ResolverMatchTests(TestCase):
428    urls = 'regressiontests.urlpatterns_reverse.namespace_urls'
429
430    def test_urlpattern_resolve(self):
431        for path, name, app_name, namespace, func, args, kwargs in resolve_test_data:
432            # Test legacy support for extracting "function, args, kwargs"
433            match_func, match_args, match_kwargs = resolve(path)
434            self.assertEqual(match_func, func)
435            self.assertEqual(match_args, args)
436            self.assertEqual(match_kwargs, kwargs)
437
438            # Test ResolverMatch capabilities.
439            match = resolve(path)
440            self.assertEqual(match.__class__, ResolverMatch)
441            self.assertEqual(match.url_name, name)
442            self.assertEqual(match.args, args)
443            self.assertEqual(match.kwargs, kwargs)
444            self.assertEqual(match.app_name, app_name)
445            self.assertEqual(match.namespace, namespace)
446            self.assertEqual(match.func, func)
447
448            # ... and for legacy purposes:
449            self.assertEqual(match[0], func)
450            self.assertEqual(match[1], args)
451            self.assertEqual(match[2], kwargs)