106

I've got two applications located on two separate computers. On computer A, in the urls.py file I have a line like the following:

(r'^cast/$', 'mySite.simulate.views.cast')

And that url will work for both mySite.com/cast/ and mySite.com/cast. But on computer B I have a similar url written out like:

(r'^login/$', 'mySite.myUser.views.login')

For some reason on computer B the url mySite.com/login/ will work but mySite.com/login will hang and won't direct back to mySite.com/login/ like it will on computer A. Is there something I missed? Both url.py files look identical to me.

Games Brainiac
  • 80,178
  • 33
  • 141
  • 199
whatWhat
  • 3,987
  • 7
  • 37
  • 44

8 Answers8

222

Or you can write your urls like this:

(r'^login/?$', 'mySite.myUser.views.login')

The question sign after the trailing slash makes it optional in regexp. Use it if for some reasons you don't want to use APPEND_SLASH setting.

Michael Gendin
  • 3,285
  • 2
  • 18
  • 23
  • 14
    Call me naive - but why hasn't this answer got a million upvotes and an entry in the django faq? – Fergal Moran Oct 31 '12 at 00:13
  • 47
    Pretty sure you don't want to do this for SEO reasons - better to redirect to a canonical URL than have two valid URLs. – Brian Frantz Dec 12 '12 at 16:06
  • 55
    If you're creting a RESTful API using Django, this can be a good solution when developers POST data directly to endpoint URL. When using `APPEND_SLASH`, if they accidently sent it without trailing slash, and your urlconf is WITH a trailing slash they would get an exception about data lose when redirecting POST requests. – OrPo May 01 '13 at 10:46
  • 7
    The problem with this solution is that you are serving the same page under 2 urls (with and without the trailing `/`) - sloppy, bad for crawlers, harder to maintain, harder to migrate to a new system (since it's so easy to overlook) – Jiaaro May 14 '15 at 21:01
  • Good answer. I'd prefer to disallow the slash (since it signified a beginning of something new, not the end of something (e.g. /etc), but this allows for the standard (/view) and the non-standard (/view/). – David Betz Nov 03 '15 at 16:58
  • also, you won't be able to `reverse` to such an url – vehsakul Jan 25 '17 at 14:22
  • I just tried this and got a message `System check identified some issues: Your URL pattern '^jobs/?$' uses include with a regex ending with a '$'. Remove the dollar from the regex to avoid problems including URLs.` I removed the dollar sign and it seems to work the same way but without the error. – calico_ Oct 03 '17 at 16:46
  • 4
    A lil off topic (Django/Python) but as someone with years of SEO experience, I can tell you that if you want to optimize for Search engines, you do NOT want 2 versions of the same URL. site.com/users is a different url from site.com/users/ That's not what you want for SEO. You need just 1 version of a url and content! Choose just 1 version and make sure that you redirect the other one properly. – Dani Jan 04 '21 at 10:42
  • You need to use ```re_path``` instead of regular ```path``` here, am I correct? – momo668 Mar 01 '23 at 13:28
121

check your APPEND_SLASH setting in the settings.py file

more info in the django docs

Jiaaro
  • 74,485
  • 42
  • 169
  • 190
  • 4
    "When set to True, if the request URL does not match any of the patterns in the URLconf and it doesn’t end in a slash, an HTTP redirect is issued to the same URL with a slash appended. Note that the redirect may cause any data submitted in a POST request to be lost.". "The APPEND_SLASH setting is only used if CommonMiddleware is installed...". I prefer Michael Gendin's answer for a cleaner solution. – Wtower Feb 11 '15 at 09:26
  • 4
    This does not work if you are using additional "catch all" url at the last entry of your urlpatterns. @speedplane's answer will work even at those situations. But, of course, this is simpler and should be used if there are no "catch all" urlpattern entries. – Niko Föhr Aug 16 '17 at 17:51
23

This improves on @Michael Gendin's answer. His answer serves the identical page with two separate URLs. It would be better to have login automatically redirect to login/, and then serve the latter as the main page:

from django.conf.urls import patterns
from django.views.generic import RedirectView

urlpatterns = patterns('',
    # Redirect login to login/
    (r'^login$', RedirectView.as_view(url = '/login/')),
    # Handle the page with the slash.
    (r'^login/', "views.my_handler"),
)
speedplane
  • 15,673
  • 16
  • 86
  • 138
3

I've had the same problem too. My solution was put an (|/) before the end line of my regular expression.

url(r'^artists/(?P[\d]+)(|/)$', ArtistDetailView.as_view()),

2

Append slash without redirect, use it instead of CommonMiddleware in settings, Django 2.1:

MIDDLEWARE = [
    ...
    # 'django.middleware.common.CommonMiddleware',
    'htx.middleware.CommonMiddlewareAppendSlashWithoutRedirect',
    ...
]

Add to your main app directory middleware.py:

from django.http import HttpResponsePermanentRedirect, HttpRequest
from django.core.handlers.base import BaseHandler
from django.middleware.common import CommonMiddleware
from django.conf import settings


class HttpSmartRedirectResponse(HttpResponsePermanentRedirect):
    pass


class CommonMiddlewareAppendSlashWithoutRedirect(CommonMiddleware):
    """ This class converts HttpSmartRedirectResponse to the common response
        of Django view, without redirect.
    """
    response_redirect_class = HttpSmartRedirectResponse

    def __init__(self, *args, **kwargs):
        # create django request resolver
        self.handler = BaseHandler()

        # prevent recursive includes
        old = settings.MIDDLEWARE
        name = self.__module__ + '.' + self.__class__.__name__
        settings.MIDDLEWARE = [i for i in settings.MIDDLEWARE if i != name]

        self.handler.load_middleware()

        settings.MIDDLEWARE = old
        super(CommonMiddlewareAppendSlashWithoutRedirect, self).__init__(*args, **kwargs)

    def process_response(self, request, response):
        response = super(CommonMiddlewareAppendSlashWithoutRedirect, self).process_response(request, response)

        if isinstance(response, HttpSmartRedirectResponse):
            if not request.path.endswith('/'):
                request.path = request.path + '/'
            # we don't need query string in path_info because it's in request.GET already
            request.path_info = request.path
            response = self.handler.get_response(request)

        return response
Max Tkachenko
  • 792
  • 1
  • 12
  • 30
1

In some cases, we have issues when some of our users call API with different endings. Usually, our users use Postman for that and are not worried about slash at the endpoint. As result, we receive issue requests in support, because users forgot to append a slash / at the end of POST requests.

We solved it by using a custom middleware that works for us in Django 3.2+ and Django 4.0+. After that, Django may handle any POST/PUT/DELETE requests to your API with slash or without them. With this middleware unneeded to change APPEND_SLASH property in settings.py

So, in the settings.py need to remove your current 'django.middleware.common.CommonMiddleware' and insert new middleware. Make sure, you change your_project_name in my example below on your real project name.

MIDDLEWARE = [
...
  # 'django.middleware.common.CommonMiddleware',
    'your_project_name.middleware.CommonMiddlewareAppendSlashWithoutRedirect',
...
]

Add to your main app directory middleware.py:

from django.http import HttpResponsePermanentRedirect, HttpRequest
from django.core.handlers.base import BaseHandler
from django.middleware.common import CommonMiddleware
from django.utils.http import escape_leading_slashes
from django.conf import settings


class HttpSmartRedirectResponse(HttpResponsePermanentRedirect):
    pass


class CommonMiddlewareAppendSlashWithoutRedirect(CommonMiddleware):
    """ This class converts HttpSmartRedirectResponse to the common response
            of Django view, without redirect. This is necessary to match status_codes
            for urls like /url?q=1 and /url/?q=1. If you don't use it, you will have 302
            code always on pages without slash.
    """
    response_redirect_class = HttpSmartRedirectResponse

def __init__(self, *args, **kwargs):
    # create django request resolver
    self.handler = BaseHandler()

    # prevent recursive includes
    old = settings.MIDDLEWARE
    name = self.__module__ + '.' + self.__class__.__name__
    settings.MIDDLEWARE = [i for i in settings.MIDDLEWARE if i != name]

    self.handler.load_middleware()

    settings.MIDDLEWARE = old
    super(CommonMiddlewareAppendSlashWithoutRedirect, self).__init__(*args, **kwargs)

def get_full_path_with_slash(self, request):
    """ Return the full path of the request with a trailing slash appended
        without Exception in Debug mode
    """
    new_path = request.get_full_path(force_append_slash=True)
    # Prevent construction of scheme relative urls.
    new_path = escape_leading_slashes(new_path)
    return new_path

def process_response(self, request, response):
    response = super(CommonMiddlewareAppendSlashWithoutRedirect, self).process_response(request, response)

    if isinstance(response, HttpSmartRedirectResponse):
        if not request.path.endswith('/'):
            request.path = request.path + '/'
        # we don't need query string in path_info because it's in request.GET already
        request.path_info = request.path
        response = self.handler.get_response(request)

    return response

This answer may look similar to Max Tkachenko answer. But his code didn't work for me in the latest versions of Django.

Tehdrew
  • 856
  • 11
  • 7
0

I've had the same problem. In my case it was a stale leftover from some old version in urls.py, from before staticfiles:

url(r'^%s(?P<path>.*)$' % settings.MEDIA_URL.lstrip('/'),
    'django.views.static.serve',
    kwargs={'document_root': settings.MEDIA_ROOT}),

MEDIA_URL was empty, so this pattern matched everything.

janek37
  • 572
  • 5
  • 16
0

In your Django project's settings file (settings.py), make sure the APPEND_SLASH setting is set to True:

APPEND_SLASH = True

Next, add the CommonMiddleware to the MIDDLEWARE setting:

MIDDLEWARE = [
    # other middleware classes...
    'django.middleware.common.CommonMiddleware',
    # other middleware classes...
]

With this configuration, Django's CommonMiddleware will automatically handle URL redirection and add or remove the trailing slash as needed. This means that requests to both 'xyz/' and 'xyz' will be correctly processed by Django REST Framework.

Note - When making requests, it is generally recommended to include the trailing slash to conform to RESTful conventions. However, with the above configuration, requests without the trailing slash will still work as expected.

Mahendra
  • 81
  • 1
  • 5