diff --git a/docs/topics/credits.md b/docs/topics/credits.md index d4c00bc4e..70f0e6871 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -182,6 +182,7 @@ The following people have helped make REST framework great. * Ian Foote - [ian-foote] * Chuck Harmston - [chuckharmston] * Philip Forget - [philipforget] +* Miroslav Shubernetskiy - [miki725] Many thanks to everyone who's contributed to the project. @@ -400,3 +401,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [ian-foote]: https://github.com/ian-foote [chuckharmston]: https://github.com/chuckharmston [philipforget]: https://github.com/philipforget +[miki725]: https://github.com/miki725 diff --git a/rest_framework/tests/test_urlpatterns.py b/rest_framework/tests/test_urlpatterns.py index 8132ec4c8..d9899f539 100644 --- a/rest_framework/tests/test_urlpatterns.py +++ b/rest_framework/tests/test_urlpatterns.py @@ -74,3 +74,30 @@ class FormatSuffixTests(TestCase): URLTestPath('/test/path.asdf', (), {'foo': 'bar', 'format': 'asdf'}), ] self._resolve_urlpatterns(urlpatterns, test_paths) + + def test_trailing_slash(self): + urlpatterns = patterns( + '', + url(r'^test/$', dummy_view), + ) + test_paths = [ + URLTestPath('/test/', (), {}), + URLTestPath('/test.api/', (), {'format': 'api'}), + URLTestPath('/test.asdf/', (), {'format': 'asdf'}), + ] + self._resolve_urlpatterns(urlpatterns, test_paths) + + def test_optional_trailing_slash(self): + urlpatterns = patterns( + '', + url(r'^test/?$', dummy_view), + ) + test_paths = [ + URLTestPath('/test', (), {}), + URLTestPath('/test/', (), {}), + URLTestPath('/test.api', (), {'format': 'api'}), + URLTestPath('/test.api/', (), {'format': 'api'}), + URLTestPath('/test.asdf', (), {'format': 'asdf'}), + URLTestPath('/test.asdf/', (), {'format': 'asdf'}), + ] + self._resolve_urlpatterns(urlpatterns, test_paths) diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py index 0ff137b00..307a9f350 100644 --- a/rest_framework/urlpatterns.py +++ b/rest_framework/urlpatterns.py @@ -21,7 +21,26 @@ 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('$') + + trailing = False + optional = False + # optional trailing slash + if regex.endswith('/?'): + optional = True + regex = regex.rstrip('?') + # trailing slash + if regex.endswith('/'): + trailing = True + regex = regex.rstrip('/') + + regex += suffix_pattern + if trailing: + regex += '/' + if optional: + regex += '?' + regex += '$' + view = urlpattern._callback or urlpattern._callback_str kwargs = urlpattern.default_args name = urlpattern.name @@ -55,8 +74,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)