diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py index 038e9ee38..2e4369f71 100644 --- a/rest_framework/urlpatterns.py +++ b/rest_framework/urlpatterns.py @@ -21,7 +21,7 @@ def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required): else: # Regular URL pattern - regex = urlpattern.regex.pattern.rstrip('$') + suffix_pattern + regex = urlpattern.regex.pattern.rstrip('$').rstrip('/') + suffix_pattern view = urlpattern._callback or urlpattern._callback_str kwargs = urlpattern.default_args name = urlpattern.name @@ -55,8 +55,8 @@ def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None): allowed_pattern = allowed[0] else: allowed_pattern = '(%s)' % '|'.join(allowed) - suffix_pattern = r'\.(?P<%s>%s)$' % (suffix_kwarg, allowed_pattern) + suffix_pattern = r'\.(?P<%s>%s)/?$' % (suffix_kwarg, allowed_pattern) else: - suffix_pattern = r'\.(?P<%s>[a-z0-9]+)$' % suffix_kwarg + suffix_pattern = r'\.(?P<%s>[a-z0-9]+)/?$' % suffix_kwarg return apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required) diff --git a/tests/test_urlpatterns.py b/tests/test_urlpatterns.py index 708dc994e..f58388c52 100644 --- a/tests/test_urlpatterns.py +++ b/tests/test_urlpatterns.py @@ -17,7 +17,8 @@ def dummy_view(request, *args, **kwargs): class FormatSuffixTests(TestCase): """ - Tests `format_suffix_patterns` against different URLPatterns to ensure the URLs still resolve properly, including any captured parameters. + Tests `format_suffix_patterns` against different URLPatterns to ensure the + URLs still resolve properly, including any captured parameters. """ def _resolve_urlpatterns(self, urlpatterns, test_paths): factory = APIRequestFactory() @@ -35,6 +36,33 @@ class FormatSuffixTests(TestCase): self.assertEqual(callback_args, test_path.args) self.assertEqual(callback_kwargs, test_path.kwargs) + def test_trailing_slash(self): + factory = APIRequestFactory() + urlpatterns = format_suffix_patterns([ + url(r'^test/$', dummy_view), + ]) + resolver = urlresolvers.RegexURLResolver(r'^/', urlpatterns) + + test_paths = [ + (URLTestPath('/test.api', (), {'format': 'api'}), True), + (URLTestPath('/test/.api', (), {'format': 'api'}), False), + (URLTestPath('/test.api/', (), {'format': 'api'}), True), + ] + + for test_path, expected_resolved in test_paths: + request = factory.get(test_path.path) + try: + callback, callback_args, callback_kwargs = resolver.resolve(request.path_info) + except urlresolvers.Resolver404: + callback, callback_args, callback_kwargs = (None, None, None) + if not expected_resolved: + assert callback is None + continue + + print(test_path, callback, callback_args, callback_kwargs) + assert callback_args == test_path.args + assert callback_kwargs == test_path.kwargs + def test_format_suffix(self): urlpatterns = [ url(r'^test$', dummy_view),