Adresy URL django bez końcowego ukośnika nie powodują przekierowań

89

Mam dwie aplikacje umieszczone na dwóch oddzielnych komputerach. Na komputerze A w urls.pypliku mam wiersz podobny do następującego:

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

Ten adres URL będzie działać zarówno w przypadku, jak mySite.com/cast/i mySite.com/cast. Ale na komputerze BI mają podobny adres URL zapisany na przykład:

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

Z jakiegoś powodu na komputerze B url mySite.com/login/ będzie działał, ale mySite.com/loginbędzie się zawieszał i nie przekieruje z powrotem do mySite.com/login/takiego, jak na komputerze A. Czy jest coś, co przegapiłem? Oba url.pypliki wyglądają identycznie jak ja.

co co
źródło

Odpowiedzi:

103

sprawdź swoje APPEND_SLASHustawienia w pliku settings.py

więcej informacji w dokumentacji django

Jiaaro
źródło
4
„Po ustawieniu na True, jeśli adres URL żądania nie pasuje do żadnego ze wzorców w URLconf i nie kończy się ukośnikiem, na ten sam adres URL jest wysyłane przekierowanie HTTP z dodanym ukośnikiem. Pamiętaj, że przekierowanie może powodować wszelkie dane przesłane w żądaniu POST, które mają zostać utracone. ”. „Ustawienie APPEND_SLASH jest używane tylko wtedy, gdy zainstalowano CommonMiddleware ...”. Wolę odpowiedź Michaela Gendina za czystsze rozwiązanie.
Wtower
3
To nie zadziała, jeśli używasz dodatkowego adresu URL typu „catch all” w ostatnim wpisie swoich wzorców url. Odpowiedź @ speedplane zadziała nawet w takich sytuacjach. Ale oczywiście jest to prostsze i powinno być używane, jeśli nie ma wpisów typu „catch all” ze wzorami url.
np8
195

Lub możesz napisać swoje adresy URL w ten sposób:

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

Znak zapytania po końcowym ukośniku sprawia, że ​​jest on opcjonalny w wyrażeniu regularnym. Użyj go, jeśli z jakichś powodów nie chcesz używać ustawienia APPEND_SLASH.

Michael Gendin
źródło
12
Nazwijcie mnie naiwnym - ale dlaczego ta odpowiedź nie ma miliona głosów za i wpisu w FAQ django?
Fergal Moran
42
Na pewno nie chcesz tego robić ze względów SEO - lepiej przekierować do kanonicznego adresu URL niż mieć dwa prawidłowe adresy URL.
Brian Frantz
47
Jeśli tworzysz RESTful API przy użyciu Django, może to być dobre rozwiązanie, gdy programiści przesyłają dane bezpośrednio do adresu URL punktu końcowego. Podczas używania APPEND_SLASH, jeśli przypadkowo wysłali go bez końcowego ukośnika, a twój urlconf jest Z końcowym ukośnikiem, otrzymaliby wyjątek dotyczący utraty danych podczas przekierowywania żądań POST.
OrPo
5
Problem z tym rozwiązaniem polega na tym, że udostępniasz tę samą stronę pod 2 adresami URL (z końcowymi adresami URL i bez nich /) - niechlujnie, źle dla robotów, trudniejsza w utrzymaniu, trudniejsza do migracji do nowego systemu (ponieważ tak łatwo ją przeoczyć)
Jiaaro
Dobra odpowiedź. Wolałbym nie zezwalać na ukośnik (ponieważ oznaczał początek czegoś nowego, a nie koniec czegoś (np. / Etc), ale pozwala to na standardowe (/ view) i niestandardowe (/ view /).
David Betz
19

To poprawia odpowiedź @Michaela Gendina. Jego odpowiedź zawiera identyczną stronę z dwoma oddzielnymi adresami URL. Lepiej byłoby mieć loginautomatyczne przekierowanie do login/, a następnie wyświetlać tę ostatnią jako stronę główną:

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
źródło
Bardzo przydatne, gdy na końcu masz adres URL typu catch-all.
thclark
Jak to działa z wyrażeniami regularnymi? Jeśli oryginalny adres URL pasuje do wyrażenia regularnego z nazwą klienta, na przykład
Nicolò Gasparini
@ NicolòGasparini - nowsze wersje Django mają pattern_nameargument, który jest używany redirectwraz ze wszystkimi dopasowanymi argumentami adresu URL.
Tim Tisdall
2

Ja też miałem ten sam problem. Moje rozwiązanie zostało wstawione (| /) przed końcową linią wyrażenia regularnego.

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

Atahualpa Silva Falcón
źródło
1

Dołącz ukośnik bez przekierowania , użyj go zamiast CommonMiddleware w ustawieniach, Django 2.1:

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

Dodaj do głównego katalogu aplikacji 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
źródło
0

Miałem ten sam problem. W moim przypadku była to nieaktualna pozostałość po starej wersji w urls.py, sprzed staticfiles:

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

MEDIA_URL był pusty, więc ten wzorzec pasuje do wszystkiego.

janek37
źródło