I'm using Django 2.0.6 (using the development server for now) and am having issues with automatic trailing slashes being appended to URL's.
My goal is to have a URL structure as follows:
/teams/ => Index view for all teams
/teams/create => Form view to create a new team (note the lack of trailing slash)
/teams/xyz/ => Index view for team with slug xyz
/teams/xyz/delete => Form view to delete team xyz (require confirmation, etc)
The problem is that, somewhere in Django framework, a trailing slash is automatically being appended to 'create' URL's. Because of this, the router the attempts to load the team index page for a team with slug 'create'. Obviously, a workaround would be to stop using slugs and use ID's, but that seems like an unnecessary concession.
Looking around, it seems that setting APPEND_SLASH to False should tell CommonMiddleware to stop appending slashes, but this didn't help.
Is there a way to easily accomplish my URL scheme and, if not, what's the idiomatic Django way to do this?
urls.py:
from django.urls import path
urlpatterns = [
path('create', views.create, name='create'),
path('<slug:team_slug>/', views.view, name='view'),
path('<slug:team_slug>/invite/', views.invite, name='invite')
]
settings.py:
APPEND_SLASH = False
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
]
Links are correctly generated with the following:
<a class="btn btn-primary" href="{% url 'teams:create' %}">New Team</a>
Which translates to: /teams/create
However, when the user clicks the link, it appears to be immediately redirected to /teams/create/
Create a directory named common in your project directory.
Create __init__.py and redirect.py in common directory.
# redirect.py
from django.conf import settings
from django.core import urlresolvers
from django import http
'''
Based on django/middleware/common.py
Django convention is to add trailing slashes to most urls
This method does the opposite and redirects trailing slashes to the
no trailing slash url if it exists
'''
class RedirectTrailingSlashMiddleware(object):
def process_request(self, request):
if settings.APPEND_SLASH:
return
if '/admin' in request.path:
settings.APPEND_SLASH = True
return
new_url = old_url = request.path
if (old_url.endswith('/')):
urlconf = getattr(request, 'urlconf', None)
if (not urlresolvers.is_valid_path(request.path_info, urlconf) and
urlresolvers.is_valid_path(request.path_info[:-1], urlconf)):
new_url = new_url[:-1]
if settings.DEBUG and request.method == 'POST':
raise RuntimeError((""
"You called this URL via POST, but the URL ends "
"in a slash and you have APPEND_SLASH set. Django can't "
"redirect to the non-slash URL while maintaining POST data. "
"Change your form to point to %s (note no trailing "
"slash), or set APPEND_SLASH=True in your Django "
"settings.") % (new_url))
if new_url == old_url:
# No redirects required.
return
return http.HttpResponsePermanentRedirect(new_url)
In your settings.py, add:
MIDDLEWARE = [
'common.redirect.RedirectTrailingSlashMiddleware',
# Add as first element
]
Source/credit: http://bunwich.blogspot.com/2013/06/django-change-trailing-slash-url.html
Related
Very basic question and I was surprised I wasn't able to find an answer. I'm just starting looking at django and did an out-of-box intallation. Created a project and created an app.
The default content of urls.py is very simple:
urlpatterns = [
path('admin/', admin.site.urls),
]
And if I open the django site home page, I get the content with a rocket picture. However, like I said, I have created another app in the project, let's say called 'bboard'. And I have created a simple 'hello world' function in bboard/views.py
def index(request):
return HttpResponse('Hello world')
to enable it for access through browser, I have modified the original urls.py file in the following way:
from bboard.views import index
urlpatterns = [
path('admin/', admin.site.urls),
path('bboard/', index),
]
This way I can access localhost:port/admin and localhost:port/bboard URLs, but if I try to open the home page with localhost:port now, I get Page not found error.
Using the URLconf defined in samplesite.urls, Django tried these URL patterns, in this order:
admin/
bboard/
The empty path didn't match any of these.
if I comment out the second item in urlpatterns list, everything works. So why does the additional pattern impact this and what needs to be done to fix it?
You need to add an empty url in root urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('bboard.urls'))
]
Before you add your own routes Django will serve the default home page at the '/' url. After you add your own route config, django no longer serves up its default sample home page.
From the django's django/views/debug.py:
def technical_404_response(request, exception):
"""Create a technical 404 error response. `exception` is the Http404."""
try:
error_url = exception.args[0]['path']
except (IndexError, TypeError, KeyError):
error_url = request.path_info[1:] # Trim leading slash
try:
tried = exception.args[0]['tried']
except (IndexError, TypeError, KeyError):
tried = []
else:
if (not tried or ( # empty URLconf
request.path == '/' and
len(tried) == 1 and # default URLconf
len(tried[0]) == 1 and
getattr(tried[0][0], 'app_name', '') == getattr(tried[0][0], 'namespace', '') == 'admin'
)):
return default_urlconf(request)
Note the final else block that returns a default_urlconf if the only url path included is the admin path and the requested url is /. This default_urlconf is the sample Rocket page you mentioned. As soon as you add any of your own routes the if statement in the else block will be false so the default_urlconf is not returned and instead falls through to the normal 404 handler.
Here's the default_urlconf
def default_urlconf(request):
"""Create an empty URLconf 404 error response."""
with Path(CURRENT_DIR, 'templates', 'default_urlconf.html').open() as fh:
t = DEBUG_ENGINE.from_string(fh.read())
c = Context({
'version': get_docs_version(),
})
return HttpResponse(t.render(c), content_type='text/html')
The other reason that you might be getting this error is because
You haven't passed an empty URL inside your application URL
Kindly (this is a simple but a very crucial one) check for typos
Very basic question and I was surprised I wasn't able to find an answer. I'm just starting looking at django and did an out-of-box intallation. Created a project and created an app.
The default content of urls.py is very simple:
urlpatterns = [
path('admin/', admin.site.urls),
]
And if I open the django site home page, I get the content with a rocket picture. However, like I said, I have created another app in the project, let's say called 'bboard'. And I have created a simple 'hello world' function in bboard/views.py
def index(request):
return HttpResponse('Hello world')
to enable it for access through browser, I have modified the original urls.py file in the following way:
from bboard.views import index
urlpatterns = [
path('admin/', admin.site.urls),
path('bboard/', index),
]
This way I can access localhost:port/admin and localhost:port/bboard URLs, but if I try to open the home page with localhost:port now, I get Page not found error.
Using the URLconf defined in samplesite.urls, Django tried these URL patterns, in this order:
admin/
bboard/
The empty path didn't match any of these.
if I comment out the second item in urlpatterns list, everything works. So why does the additional pattern impact this and what needs to be done to fix it?
You need to add an empty url in root urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('bboard.urls'))
]
Before you add your own routes Django will serve the default home page at the '/' url. After you add your own route config, django no longer serves up its default sample home page.
From the django's django/views/debug.py:
def technical_404_response(request, exception):
"""Create a technical 404 error response. `exception` is the Http404."""
try:
error_url = exception.args[0]['path']
except (IndexError, TypeError, KeyError):
error_url = request.path_info[1:] # Trim leading slash
try:
tried = exception.args[0]['tried']
except (IndexError, TypeError, KeyError):
tried = []
else:
if (not tried or ( # empty URLconf
request.path == '/' and
len(tried) == 1 and # default URLconf
len(tried[0]) == 1 and
getattr(tried[0][0], 'app_name', '') == getattr(tried[0][0], 'namespace', '') == 'admin'
)):
return default_urlconf(request)
Note the final else block that returns a default_urlconf if the only url path included is the admin path and the requested url is /. This default_urlconf is the sample Rocket page you mentioned. As soon as you add any of your own routes the if statement in the else block will be false so the default_urlconf is not returned and instead falls through to the normal 404 handler.
Here's the default_urlconf
def default_urlconf(request):
"""Create an empty URLconf 404 error response."""
with Path(CURRENT_DIR, 'templates', 'default_urlconf.html').open() as fh:
t = DEBUG_ENGINE.from_string(fh.read())
c = Context({
'version': get_docs_version(),
})
return HttpResponse(t.render(c), content_type='text/html')
The other reason that you might be getting this error is because
You haven't passed an empty URL inside your application URL
Kindly (this is a simple but a very crucial one) check for typos
I have the following middleware function:
class LastVisitMiddleware(object):
def process_response(self, request, response):
if request.user.is_authenticated():
customer = get_customer(request)
Customer.objects.filter(pk=customer.pk).update(last_visit=now())
return response
My middleware entries look like this:
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'my.middleware.LastVisitMiddleware',
)
My url looks like:
url(r'^dashboard/$', views.dashboard, name='dashboard'),
When I go to urls that have a forward slash the page loads normally. When I omit the forward slash I get the error:
WSGIRequest object has no attribute user
When I remove the middleware I have no issues whether I use the forward slash or not.
How can I prevent from throwing this error with or without a forward slash?
I know that Django redirects any urls without the trailing /, so /home to /home/, but I am not sure when Django does this redirection (apparently after it has run the middleware?). One way to get around this is to check if the user object has been set;
if hasattr(request, 'user') and request.user.is_authenticated():
This should fix your problem.
During the response phases (process_response() and process_exception() middleware), the classes are applied in reverse order, from the bottom up
similar question:
Django: WSGIRequest' object has no attribute 'user' on some pages?
I am using this middleware to redirect all pages to a landing page: (part of AuthRequiredMiddleware class.
def process_request(self, request):
assert hasattr(request, 'user')
if not request.user.is_authenticated():
path = request.path_info.lstrip('/')
if path not in ['ipn/', 'pp_cancel/', 'pp_success/', 'sitemap/', 'welcome/']:
lang = request.GET.get('lang', 'en')
ru = request.GET.get('ru', '')
return render_to_response('landing_en.html', RequestContext(request, {'ru': ru}))
and this is my settings.py
MIDDLEWARE_CLASSES = (
'django.middleware.cache.UpdateCacheMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'main.common.SessionBasedLocaleMiddleware.SessionBasedLocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'main.common.tz_middleware.TimezoneMiddleware',
'main.common.sslMiddleware.SSLRedirect',
'main.common.RedirectAllMiddleware.AuthRequiredMiddleware',
)
if the url is (for example) /welcome/ and no redirection is performed {% csrf_token %} works and shows in the form. If user is redirected no csrf_token is shown in the form.
What am I doing wrong?
From the wiki page about CSRF:
Cross-site request forgery, also known as a one-click attack or
session riding [...] is a type of malicious exploit of a website
whereby unauthorized commands are transmitted from a user that the
website trusts.
and later, under prevention:
Verifying that the request's header contains a X-Requested-With (used
by Ruby on Rails before v2.0 and Django before v1.2.5), or checking
the HTTP Referer header and/or HTTP Origin header.
So actually, your csrf protection is working well. Because, while I'm not 100% that the problem is specifically the missing referrer, I do think that it's caused by not using a proper redirect which triggers a csrf violation.
The solution - use HttpResponseRedirect and pass the information to the other view. You can pass it as GET data:
d = {'ru': ru, 'other': 'variables'}
url = '/landing/?%' % '&'.join( map(lambda x: '='.join(x), d.items()) )
return HttpResponseRedirect(url)
You can also use regex patterns in the urls (if that makes sense) or use sessions if there's anything sensitive in there.
I'm using the i18n_patterns to add a prefix of current lang_code to my url.
urlpatterns += i18n_patterns('',
url(r'^', include('blaszczakphoto2.gallery.urls')),
)
It allowes me to get urls like /en/about-us/ , /pl/about-us/ etc..
My default language is pl
LANGUAGE_CODE = 'pl'
I want url like /about-us/ for clients viewing my site in polish lenguage. Is there any way to hide lang_code prefix from url for default lang_code?
Django >=1.10 can handle this natively. There is a new prefix_default_language argument in i18n_patterns function.
Setting prefix_default_language to False removes the prefix from
the default language (LANGUAGE_CODE). This can be useful when adding
translations to existing site so that the current URLs won’t change.
Source: https://docs.djangoproject.com/en/dev/topics/i18n/translation/#language-prefix-in-url-patterns
Example:
# Main urls.py:
urlpatterns = i18n_patterns(
url(r'^', include('my_app.urls', namespace='my_app')),
prefix_default_language=False
)
# my_app.urls.py:
url(r'^contact-us/$', ...),
# settings:
LANGUAGE_CODE = 'en' # Default language without prefix
LANGUAGES = (
('en', _('English')),
('cs', _('Czech')),
)
The response of example.com/contact-us/ will be in English and example.com/cs/contact-us/ in Czech.
Here is a very simple package: django-solid-i18n-urls
After setup, urls without language prefix will always use default language, that is specified in settings.LANGUAGE_CODE. Redirects will not occur.
If url will have language prefix, then this language will be used.
Also answered here: https://stackoverflow.com/a/16580467/821594.
I faced this problem and solved this way:
Created an alternative i18n_patterns that do not prefix the site main language (defined in settings.LANGUAGE_CODE).
Created an alternative middleware that only uses the URL prefixes language to activate the current language.
I didn't see any side-effect using this technique.
The code:
# coding: utf-8
"""
Cauê Thenório - cauelt(at)gmail.com
This snippet makes Django do not create URL languages prefix (i.e. /en/)
for the default language (settings.LANGUAGE_CODE).
It also provides a middleware that activates the language based only on the URL.
This middleware ignores user session data, cookie and 'Accept-Language' HTTP header.
Your urls will be like:
In your default language (english in example):
/contact
/news
/articles
In another languages (portuguese in example):
/pt/contato
/pt/noticias
/pt/artigos
To use it, use the 'simple_i18n_patterns' instead the 'i18n_patterns'
in your urls.py:
from this_sinppet import simple_i18n_patterns as i18n_patterns
And use the 'SimpleLocaleMiddleware' instead the Django's 'LocaleMiddleware'
in your settings.py:
MIDDLEWARE_CLASSES = (
...
'this_snippet.SimpleLocaleMiddleware'
...
)
Works on Django >=1.4
"""
import re
from django.conf import settings
from django.conf.urls import patterns
from django.core.urlresolvers import LocaleRegexURLResolver
from django.middleware.locale import LocaleMiddleware
from django.utils.translation import get_language, get_language_from_path
from django.utils import translation
class SimpleLocaleMiddleware(LocaleMiddleware):
def process_request(self, request):
if self.is_language_prefix_patterns_used():
lang_code = (get_language_from_path(request.path_info) or
settings.LANGUAGE_CODE)
translation.activate(lang_code)
request.LANGUAGE_CODE = translation.get_language()
class NoPrefixLocaleRegexURLResolver(LocaleRegexURLResolver):
#property
def regex(self):
language_code = get_language()
if language_code not in self._regex_dict:
regex_compiled = (re.compile('', re.UNICODE)
if language_code == settings.LANGUAGE_CODE
else re.compile('^%s/' % language_code, re.UNICODE))
self._regex_dict[language_code] = regex_compiled
return self._regex_dict[language_code]
def simple_i18n_patterns(prefix, *args):
"""
Adds the language code prefix to every URL pattern within this
function, when the language not is the main language.
This may only be used in the root URLconf, not in an included URLconf.
"""
pattern_list = patterns(prefix, *args)
if not settings.USE_I18N:
return pattern_list
return [NoPrefixLocaleRegexURLResolver(pattern_list)]
The code above is available on:
https://gist.github.com/cauethenorio/4948177
You might want to check yawd-translations which does exactly that. If you don't care about the extra functionality (manage languages from db), you can have a look at the urls.translation_patterns method and the middleware.TranslationMiddleware to see how it can be done.
This is my solution:
Create django middleware: django_app/lib/middleware/locale.py
from django.utils import translation
class SwitchLanguageMiddleware(object):
def process_request(self, request):
lang = request.GET.get('lang', '')
if lang:
translation.activate(lang)
request.LANGUAGE_CODE = translation.get_language()
def process_response(self, request, response):
request.session['django_language'] = translation.get_language()
if 'Content-Language' not in response:
response['Content-Language'] = translation.get_language()
translation.deactivate()
return response
It's read the get parameter of request and if it has lang attribute, then switching language. Ex.: /about-us/?lang=pl
Include this middleware to settings.py:
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.common.CommonMiddleware',
'django_app.libs.middleware.locale.SwitchLanguageMiddleware',
)