urls django sem uma barra final não redirecionam

89

Tenho dois aplicativos localizados em dois computadores separados. No computador A, no urls.pyarquivo, tenho uma linha como a seguinte:

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

E esse url funcionará para ambos mySite.com/cast/e mySite.com/cast. Mas no computador BI tem um url semelhante escrito como:

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

Por algum motivo, no computador B, o url mySite.com/login/ funcionará, mas mySite.com/logintravará e não retornará mySite.com/login/como no computador A. Há algo que perdi? Ambos os url.pyarquivos parecem idênticos a mim.

O que, o que
fonte

Respostas:

104

verifique sua APPEND_SLASHconfiguração no arquivo settings.py

mais informações nos documentos do django

Jiaaro
fonte
4
"Quando definido como True, se o URL de solicitação não corresponder a nenhum dos padrões no URLconf e não terminar em uma barra, um redirecionamento de HTTP é emitido para o mesmo URL com uma barra anexada. Observe que o redirecionamento pode causar quaisquer dados enviados em uma solicitação POST serão perdidos. ". "A configuração APPEND_SLASH é usada apenas se CommonMiddleware estiver instalado ...". Prefiro a resposta de Michael Gendin para uma solução mais limpa.
Torre de Vigia de
3
Isso não funcionará se você estiver usando um url "catch all" adicional na última entrada de seus urlpatterns. A resposta de @ speedplane funcionará mesmo nessas situações. Mas, é claro, isso é mais simples e deve ser usado se não houver entradas de urlpattern "catch all".
np8 de
196

Ou você pode escrever seus urls assim:

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

O sinal de pergunta após a barra final o torna opcional em regexp. Use-o se, por alguns motivos, você não quiser usar a configuração APPEND_SLASH.

Michael Gendin
fonte
12
Pode me chamar de ingênuo - mas por que essa resposta não teve um milhão de votos positivos e uma entrada no faq do django?
Fergal Moran
42
Tenho certeza de que você não deseja fazer isso por motivos de SEO - melhor redirecionar para uma URL canônica do que ter duas URLs válidas.
Brian Frantz
47
Se você estiver criando uma API RESTful usando Django, esta pode ser uma boa solução quando os desenvolvedores POSTAR dados diretamente para a URL do endpoint. Ao usar APPEND_SLASH, se eles o enviarem acidentalmente sem uma barra final, e seu urlconf estiver COM uma barra final, eles obteriam uma exceção sobre perda de dados ao redirecionar solicitações POST.
OrPo
5
O problema com esta solução é que você está exibindo a mesma página em 2 urls (com e sem o trailing /) - desleixado, ruim para rastreadores, mais difícil de manter, mais difícil de migrar para um novo sistema (já que é tão fácil de ignorar)
Jiaaro
1
Um pouco fora do assunto (Django / Python), mas como alguém com anos de experiência em SEO, posso dizer que se você deseja otimizar para mecanismos de pesquisa, NÃO deseja 2 versões da mesma URL. site.com/users é um URL diferente de site.com/users/ Isso não é o que você deseja para o SEO. Você precisa de apenas 1 versão de um url e conteúdo! Escolha apenas uma versão e certifique-se de redirecionar a outra corretamente.
Dani
19

Isso melhora a resposta de @Michael Gendin. Sua resposta exibe a página idêntica com dois URLs separados. Seria melhor loginredirecionar automaticamente para login/e, em seguida, servir o último como a página principal:

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"),
)
avião rápido
fonte
Muito útil quando você tem uma URL pega-tudo no final.
thclark
Como isso funcionaria com regexs? Se o url original corresponder a um regex com um nome de cliente, por exemplo
Nicolò Gasparini
@ NicolòGasparini - versões mais recentes do Django possuem um pattern_namearg que é usado redirectjunto com todos os args de url que são correspondidos.
Tim Tisdall
2

Eu também tive o mesmo problema. Minha solução foi colocada um (| /) antes da linha final da minha expressão regular.

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

Atahualpa Silva Falcón
fonte
1

Anexe a barra sem redirecionar , use-a em vez de CommonMiddleware nas configurações, Django 2.1:

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

Adicione ao diretório principal do aplicativo 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
fonte
0

Eu tive o mesmo problema. No meu caso, era uma sobra desatualizada de alguma versão antiga em urls.py, anterior a arquivos estáticos:

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

MEDIA_URL estava vazio, então esse padrão correspondeu a tudo.

Janek37
fonte