From ba8e037951379c29fc353ba025b0e0dd3fe4d7f3 Mon Sep 17 00:00:00 2001 From: decadenza Date: Thu, 27 Feb 2025 16:27:05 +0000 Subject: [PATCH] Adding optional trailing_slash for SimpleRouter and test. This does not break the previous behaviour, where trailing_slash can be only True or False. By default it stays True. It adds an extra option: when trailing_slash=None (or any other value), the trailing_slash becomes optional. --- rest_framework/routers.py | 7 ++++++- tests/test_routers.py | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 2b9478e90..523214d6e 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -136,7 +136,12 @@ class SimpleRouter(BaseRouter): ] def __init__(self, trailing_slash=True, use_regex_path=True): - self.trailing_slash = '/' if trailing_slash else '' + if trailing_slash is True: + self.trailing_slash = '/' + elif trailing_slash is False: + self.trailing_slash = '' + else: + self.trailing_slash = "/?" self._use_regex = use_regex_path if use_regex_path: self._base_pattern = '(?P<{lookup_prefix}{lookup_url_kwarg}>{lookup_value})' diff --git a/tests/test_routers.py b/tests/test_routers.py index 887f601d5..7e60f2c24 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -328,6 +328,21 @@ class TestTrailingSlashRemoved(TestCase): assert expected[idx] == self.urls[idx].pattern.regex.pattern +class TestTrailingSlashOptional(TestCase): + def setUp(self): + class NoteViewSet(viewsets.ModelViewSet): + queryset = RouterTestModel.objects.all() + + self.router = SimpleRouter(trailing_slash=None) + self.router.register(r'notes', NoteViewSet) + self.urls = self.router.urls + + def test_urls_have_trailing_slash_by_default(self): + expected = ['^notes/?$', '^notes/(?P[^/.]+)/?$'] + for idx in range(len(expected)): + assert expected[idx] == self.urls[idx].pattern.regex.pattern + + class TestNameableRoot(TestCase): def setUp(self): class NoteViewSet(viewsets.ModelViewSet):