Skip to content

Option to have default router accept URLs without trailing slash #905

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
bobobo1618 opened this issue Jun 2, 2013 · 22 comments
Closed

Option to have default router accept URLs without trailing slash #905

bobobo1618 opened this issue Jun 2, 2013 · 22 comments

Comments

@bobobo1618
Copy link

It'd just be nice to be able to create a default router which, when included in a URL configuration file, will accept URLs without a trailing slash.

This is entirely the fault of Angular.js which strips the slashes off its URLs and doesn't show any sign of changing. There are other developers who have had the same problem. The main issue is that Angular will POST without the slash and Django can't redirect that.

I know that it's possible to have this by building a router from scratch and using custom URLs for everything but it seems like a very bad way of doing things.

Ideally, I'd like to be able to initialize my router with something like

router = routers.DefaultRouter(trailing_slash='optional')
router.register(r'Place', PlaceViewSet)

which would accept URLs of the form Place/?$ instead of Place/$

@tomchristie
Copy link
Member

Seems reasonable, yeah. How would you feel about a simple Boolean? I'm not sure if an 'optional' case is really necessary/desirable.

@bobobo1618
Copy link
Author

Boolean is totally fine.

I solved my own problem temporarily. I just extended the SimpleRouter class
and copy pasted the routes from the original then put question marks at the
end of all the routes.
On 02/06/2013 7:03 PM, "Tom Christie" notifications@github.com wrote:

Seems reasonable, yeah. How would you feel about a simple Boolean? I'm not
sure if an 'optional' case is really necessary/desirable.


Reply to this email directly or view it on GitHubhttps://github.com//issues/905#issuecomment-18804272
.

@alexf101
Copy link

"I solved my own problem temporarily. I just extended the SimpleRouter class
and copy pasted the routes from the original then put question marks at the
end of all the routes."

I will use this solution instead of the trailing_slash boolean (which, if false, insists that a trailing slash is NOT used), as I think it's needlessly frustrating to make user's worry about whether or not to use a trailing slash. What on Earth is the use case for causing a request to fail if it does/does not have a trailing slash?

@bobobo1618
Copy link
Author

That is an excellent question. I believe it has something to do with some
standard written solely for APIs.

On Tue, Jul 30, 2013 at 9:12 PM, alexf101 notifications@github.com wrote:

"I solved my own problem temporarily. I just extended the SimpleRouter
class
and copy pasted the routes from the original then put question marks at the
end of all the routes."

I will use this solution instead of the trailing_slash boolean (which, if
false, insists that a trailing slash is NOT used), as I think it's
needlessly frustrating to make user's worry about whether or not to use a
trailing slash. What on Earth is the use case for causing a request to fail
if it does/does not have a trailing slash?


Reply to this email directly or view it on GitHubhttps://github.com//issues/905#issuecomment-21783916
.

@kevin-brown
Copy link
Member

I am subclassing DefaultRouter and setting self.trailing_slash = "/?" during the init. This works for the list views, but not for the detail views. The detail views return a 404 if a format suffix is in place.

@alexf101
Copy link

As bobobo1618 said, literally copy and paste the list of routers, and replace everything there. It worked for me (I don't know why your solution didn't work for you).

@jgrund
Copy link

jgrund commented Oct 28, 2013

On the Angular side you can use a request interceptor to add the trailing slash back in as needed.

@benspaulding
Copy link

If you want to accept both a trailing slash or no trailing slash, we have found this to work so far. (Though it has not been tested much yet.)

## DOCUMENTAION EXAMPLE with noted additions.
from django.conf.urls import patterns, url, include
from rest_framework import routers
from quickstart import views

router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)

# First addition
# Now setup a router that does not require a trailing slash
slashless_router = routers.DefaultRouter(trailing_slash=False)
slashless_router.registry = router.registry[:]

urlpatterns = patterns('',
    url(r'^', include(router.urls)),
    # Second addition
    url(r'^', include(slashless_router.urls)),
)

@alexf101
Copy link

Excellent solution :) so simple
On Mar 12, 2014 7:57 AM, "Ben Spaulding" notifications@github.com wrote:

If you want to accept both a trailing slash or no trailing slash, we
have found this to work so far. (Though it has not been tested much yet.)

DOCUMENTAION EXAMPLE with noted additions.

from django.conf.urls import patterns, url, include
from rest_framework import routers
from quickstart import views

router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)

First addition

Now setup a router that does not require a trailing slash

slashless_router = routers.DefaultRouter(trailing_slash=False)
slashless_router.registry = router.registry[:]

urlpatterns = patterns('',
url(r'^', include(router.urls)),
# Second addition
url(r'^', include(slashless_router.urls)),
)

Reply to this email directly or view it on GitHubhttps://github.com//issues/905#issuecomment-37417867
.

@syphar
Copy link
Contributor

syphar commented Mar 13, 2014

@alexf101 @benspaulding this solution will lead to duplication in the breadcrums in the browsable API. If this is ok for you, you can use it :)

I like the changed regex in Default/SimpleRouter more.

@kevin-brown
Copy link
Member

An update to what I said a few months back...

I am subclassing DefaultRouter and setting self.trailing_slash = "/?" during the init.

You will also need to override get_lookup_regex as the patch does not completely work for this special case, so you can get detail views to work with this snippet that's modified from 005f475:

def get_lookup_regex(self, viewset):
    """                                                                     
    Given a viewset, return the portion of URL regex that is used           
    to match against a single instance.                                     
    """

    # Don't consume `.json` style suffixes                                  
    base_regex = '(?P<{lookup_field}>[^/.]+)'
    lookup_field = getattr(viewset, 'lookup_field', 'pk')

    return base_regex.format(lookup_field=lookup_field)

This also does not cause issues within the browsable API.

@benspaulding
Copy link

@syphar Thanks for pointing that out. We are not currently using the browsable API, so we didn’t notice it, but we are likely to in the future. Good to know.

@kevin-brown Oh really? I’ll have to try again, then, because I could not get your solution to work. Thanks!

@mgol
Copy link

mgol commented Apr 9, 2014

FWIW, the fix for this issue has just landed in Angular: angular/angular.js@3878be5. The commit message contains info how to configure it.

@nikolas
Copy link
Contributor

nikolas commented Oct 2, 2014

ember-data doesn't use trailing slashes by default: http://emberjs.com/guides/models/the-rest-adapter/#toc_url-conventions

Is there a config option to allow the absence of trailing slashes yet in django-rest-framework?

@nikolas
Copy link
Contributor

nikolas commented Oct 2, 2014

@kevin-brown I'm having trouble getting your solution to work. I've tried with and without overriding get_lookup_regex.

class MyAPIRouter(routers.SimpleRouter):
    trailing_slash = "/?"

@nikolas
Copy link
Contributor

nikolas commented Oct 2, 2014

Nevermind, I should have just read the documentation. http://www.django-rest-framework.org/api-guide/routers.html#simplerouter

router = routers.SimpleRouter(trailing_slash=False)

@diosney
Copy link

diosney commented Jan 24, 2015

I've been testing with the trailing_slash=False option and is working smoothly until now.

@psychok7
Copy link

Based on the comments here i ended up doing something like this:

class OptionalSlashDefaultRouter(DefaultRouter):
    def __init__(self, *args, **kwargs):
        self.trailing_slash = '/?'

        if 'root_renderers' in kwargs:
            self.root_renderers = kwargs.pop('root_renderers')
        else:
            self.root_renderers = list(api_settings.DEFAULT_RENDERER_CLASSES)

        super(SimpleRouter, self).__init__()


router = OptionalSlashDefaultRouter()

@adeelyounas
Copy link

@psychok7 does follow class cause any problems?

class OptionalTrailingSlashRouter(routers.DefaultRouter):
    def __init__(self, *args, **kwargs):
        super(OptionalTrailingSlashRouter, self).__init__(*args, **kwargs)
        self.trailing_slash = '/?'

@psychok7
Copy link

@adeelyounas i don't think i have tried that one, but i guess it shouldn't. Please let us know if it works properly.

@adeelyounas
Copy link

adeelyounas commented Apr 23, 2018

@psychok7 I did not find any issues so far but will share if I find one.

@awaisdar001
Copy link

Complete solution with Django 1.11

from django.conf.urls import include, url
from rest_framework.routers import DefaultRouter

from api.v1.views import BookViewSet

router = DefaultRouter()
slashless_router = DefaultRouter(trailing_slash=False)
slashless_router.registry = router.registry[:]
router.register(r'^books', BookViewSet, base_name='books')

urlpatterns = [
    url(r'^', include(router.urls)),
    url(r'^', include(slashless_router.urls)),
]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests