48

The docs say you can set trailing_slash=False but how can you allow both endpoints to work, with or without a trailing slash?

Ryan Allen
  • 5,414
  • 4
  • 26
  • 33

6 Answers6

54

You can override the __init__ method of the SimpleRouter class:

from rest_framework.routers import SimpleRouter


class OptionalSlashRouter(SimpleRouter):

    def __init__(self):
        super().__init__()
        self.trailing_slash = '/?'

The ? character will make the slash optional for all available routes.

Cesar Canassa
  • 18,659
  • 11
  • 66
  • 69
Ryan Allen
  • 5,414
  • 4
  • 26
  • 33
  • Funny I was thinking about this today. But I'm making use of url Router. Will try that and see what happens. +1 – Bernard 'Beta Berlin' Parah Sep 11 '17 at 20:34
  • 12
    I use `DefaultRouter` and had to do this: `class OptionalSlashRouter(DefaultRouter): def __init__(self, *args, **kwargs): super(DefaultRouter, self).__init__(*args, **kwargs) self.trailing_slash = '/?' ` – Def_Os Apr 02 '18 at 23:35
  • 2
    You can pass `/?` [to](https://github.com/encode/django-rest-framework/blob/3.9.1/rest_framework/routers.py#L168) the [constructor](https://www.django-rest-framework.org/api-guide/routers/#simplerouter). – x-yuri Feb 21 '19 at 16:36
  • 3
    The __init__ method [overrides `trailing_slash` to always be `/`](https://github.com/encode/django-rest-framework/blob/453196e9c3a581bac3bf68eb8c9cdd7d28d2dcd6/rest_framework/routers.py#L169) when truthy. – Ryan Allen Feb 21 '19 at 16:48
  • 5
    Put the super init method call before `self.trailing_slash = '/?'`. – Mcmil Mar 13 '20 at 10:58
  • Getting - `django.core.exceptions.ImproperlyConfigured: "^?$" is not a valid regular expression: nothing to repeat at position 1`. For this solution. – Vikram Ray Sep 06 '21 at 08:05
35

You can also override this setting by passing a trailing_slash argument to the SimpleRouter constructor as follows:

from rest_framework import routers

router = routers.SimpleRouter(trailing_slash=False)
dill
  • 375
  • 3
  • 3
  • 10
    Then the URL with a trailing slash doesn't work though – Ryan Allen Feb 16 '18 at 14:59
  • That's the point, but in such case you will probably have to configure to support both at the http server level. Example: Apache has the mod_rewrite module and you can do a simple redirect to urls without trailing slash – OzzyTheGiant Apr 16 '19 at 14:53
  • 1
    If I have `trailing_slash=False`, and I write the URLs to register with a slash at the end, and have APPEND_SLASH not set to False, then both the URLs with and without the trailing slash work fine, as the URL without a trailing slash redirects to the one with a trailing slash. – mic Oct 13 '20 at 18:58
  • The issue is, URLs for subpages from DRF such as `my_model/1/` become `my_model//1`. So it seems better to keep the default of `trailing_slash=True`, register the URL without a trailing slash, and have APPEND_SLASH=True. – mic Oct 13 '20 at 19:12
13

If you're using DRF's routers and viewsets, you can include /? in your prefix.

from rest_framework import routers

from .views import ClientViewSet

router = routers.SimpleRouter(trailing_slash=False)
router.register(r"clients/?", ClientViewSet)
Noel Llevares
  • 15,018
  • 3
  • 57
  • 81
5

I found the easiest way to do this is just to set up your URLs individually to handle the optional trailing slash, e.g.

from django.urls import re_path

urlpatterns = [
    re_path('api/end-point/?$', api.endPointView),
    ...

Not a DRY solution, but then it's only an extra two characters for each URL. And it doesn't involve overriding the router.

gornvix
  • 3,154
  • 6
  • 35
  • 74
1

For anyone using ExtendedSimpleRouter in rest_framework_extensions, the accepted solution needs a small modification. The self.trailling_slash has to be after the super().__init__() like this.

from rest_framework_extensions.routers import ExtendedSimpleRouter

class OptionalSlashRouter(ExtendedSimpleRouter):
    def __init__(self):
        super(ExtendedSimpleRouter, self).__init__()
        self.trailing_slash = "/?"
Hoai Nam
  • 45
  • 10
Cedric Holz
  • 51
  • 1
  • 4
1

For the DefaultRouter class, it's the same as Ryan Allen's answer:

from rest_framework.routers import DefaultRouter


class OptionalSlashRouter(DefaultRouter):
    """Make all trailing slashes optional in the URLs used by the viewsets
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.trailing_slash = '/?'


router = OptionalSlashRouter()
...
Brian Sidebotham
  • 1,706
  • 11
  • 15