Django HTTP Redirect outside of view function - python

Is there a way to redirect to another page in Django when outside of a view function.
I need to be able to redirect anywhere in my app, NOT just when I'm returning an HTTP response back to the client.
I've tried the redirect shortcut:
from django.shortcuts import redirect
redirect("/some/url")
and also
redirect("http://someserver.com/some/url")
but this does no noticable action.
I need something like header("Location: /some/url"); in PHP which can be used anywhere inside the script.
Thanks

You could abuse/leverage process_exception of middleware:
# middleware.py
from django import shortcuts
class Redirect(Exception):
def __init__(self, url):
self.url = url
def redirect(url):
raise Redirect(url):
class RedirectMiddleware:
def process_exception(self, request, exception):
if isinstance(exception, Redirect):
return shortcuts.redirect(exception.url)
You can then do:
from middleware import redirect
redirect('/some/url/')
Also don't forget to add RedirectMiddleware to your MIDDLEWARE_CLASSES settings.

Related

Disable CSRF validation on Wagtail Page

I'm trying to do a curl POST request on a wagtail page. Unfortunately I hit the CSRF
protection.
I tried to disabled CSRF on this specific type of page using the #csrf_exempt decorator, without success.
Here is my pseudo code (one of many attemps):
#method_decorator(csrf_exempt, name='serve')
class NewsletterPage(MedorPage):
class Meta:
verbose_name = _("newsletter page")
Seems like the csrf verifition is done even before the serve method is called.
Any idea?
thanks
You would have to decorate the wagtail.core.views.serve view itself. Since that is patchy as you want to keep its url in your wagtail_urls, you could do the following wherever you include the wagtail urls:
# urls.py
# ...
from wagtail.core import urls as wagtail_urls
# ...
### these two lines can really go anywhere ...
from wagtail.core import views
views.serve.csrf_exempt = True
### ... where they are executed at loading time
urlpatterns = [
# ...
re_path(r'^pages/', include(wagtail_urls)),
# ...
]
This will apply to all wagtail pages then, not just one specific type however.
I ended up subclassing the CSRF middleware like so:
from django.middleware.csrf import CsrfViewMiddleware
from wagtail.core.views import serve
from myproject_newsletter.models import NewsletterIndexPage
class CustomCsrfViewMiddleware(CsrfViewMiddleware):
def process_view(self, request, callback, callback_args, callback_kwargs):
if callback == serve:
# We are visiting a wagtail page. Check if this is a NewsletterPage
# and if so, do not perfom any CSRF validation
page = NewsletterIndexPage.objects.first()
path = callback_args[0]
if page and path.startswith(page.get_url_parts()[-1][1:])
return None
return super().process_view(request, callback, callback_args, callback_kwargs)

How to redirect url from middleware in Django?

How to redirect url from middleware?
Infinite loop problem.
I intend to redirect the user to a client registration url if the registration has not yet been completed.
def check_user_active(get_response):
def middleware(request):
response = get_response(request)
try:
print(Cliente.objects.get(usuario_id=request.user.id))
except Cliente.DoesNotExist:
return redirect('confirm')
return response
return middleware
Every request to server goes through Middleware. Hence, when you are going to confirm page, the request goes through middleware again. So its better put some condition here so that it ignores confirm url. You can try like this:
def check_user_active(get_response):
def middleware(request):
response = get_response(request)
if not request.path == "confirm":
try:
print(Cliente.objects.get(usuario_id=request.user.id))
except Cliente.DoesNotExist:
return redirect('confirm')
return response
return middleware
You should be using a login_required like decorator see Django authentication system for more detail.
Example:
from django.contrib.auth.decorators import login_required
#login_required(login_url="/your/login/view/url/")
def my_view(request):
...
Avoid using middlewares for any kind of redirection always you can, according to the docs
Middleware is a framework of hooks into Django’s request/response processing. It’s a light, low-level “plugin” system for globally altering Django’s input or output.
In other words, middlewares are there for processing request and responses, if you redirect to any view, you will (potentially) recursively trigger your middleware.
And, on the other hand ...
In the future, you might want to add a view that can be visited by anonymous users, this middleware will be a problem ...

Changing django-allauth render_authentication_error behavior

I'm a newcomer to the python/Django universe and just started a huge project I'm pretty excited about. I need to have my users login through Facebook and my app has a really specific user flow. I've set up django-allauth and everything works as I needed. I've overriden LOGIN_REDIRECT_URL so that my users land on the page I want when they log in.
BUT. When the user opens Facebook login dialog box and then closes it without logging in, the authentication_error.html template gets rendered by allauth.socialaccount.helpers.render_authentication_error, and this is not the behaviour I want. I want the user to simply be redirected to the login page.
Yes, I know I could simply override the template by putting it in my TEMPLATE_DIRS, but then the url wouldn't be the same.
I've come to the conclusion I needed a middleware to intercept the response to the http request.
from django.shortcuts import redirect
class Middleware():
"""
A middleware to override allauth user flow
"""
def __init__(self):
self.url_to_check = "/accounts/facebook/login/token/"
def process_response(self, request, response):
"""
In case of failed faceboook login
"""
if request.path == self.url_to_check and\
not request.user.is_authenticated():
return redirect('/')
return response
But I'm not sure about the efficiency of my solution nor the pythonesquitude (I juste came up with that word) of it. Is there anything else I could do to change that default django-allauth behavior without using a middleware or a signal?
Thanks!
Yes, I know I could simply override the template by putting it in my TEMPLATE_DIRS, but then the url wouldn't be the same.
Overriding a template won't change the URL. In your overridden template, you could do a client-side redirect to whatever URL you prefer.
I decided to use a middleware and redirect to home url in case a GET request is made on a URL of the form ^/accounts/.*$
from django.shortcuts import redirect
import re
class AllauthOverrideMiddleware():
"""
A middleware to implement a custom user flow
"""
def __init__(self):
# allauth urls
self.url_social = re.compile("^/accounts/.*$")
def process_request(self, request):
# WE CAN ONLY POST TO ALLAUTH URLS
if request.method == "GET" and\
self.url_social.match(request.path):
return redirect("/")

Django, redirect all non-authenticated users to landing page

I have a django website with many urls and views. Now I have asked to redirect all non-authenticated users to a certain landing page. So, all views must check if user.is_authenticated() and return to a new set of landing pages.
Can it be done in a pretty way, instead of messing with my views.py/urls.py that much?
There is a simpler way to do this, just add the "login_url" parameter to #login_required and if the user is not login he will be redirected to the login page. You can find it here
from django.contrib.auth.decorators import login_required
#login_required(login_url='/accounts/login/')
def my_view(request):
...
You can use Middleware.
Something like this will check user auth every request:
class AuthRequiredMiddleware(object):
def process_request(self, request):
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('landing_page')) # or http response
return None
Docs: process_request
Also, don't forget to enable it in settings.py
MIDDLEWARE_CLASSES = (
...
'path.to.your.AuthRequiredMiddleware',
)
see the docs for login required decorator
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
...
another option is to add it to your urls.py patterns, see this answer
urlpatterns = patterns('',
(r'^foo/$', login_required(direct_to_template), {'template': 'foo_index.html'}),
)
As of Django 1.10, the custom middleware classes must implement the new style syntax. You can use the following class to verify that the user is logged in while trying to access any views.
from django.shortcuts import HttpResponseRedirect
class AuthRequiredMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
response = self.get_response(request)
if not request.user.is_authenticated: # in Django > 3 this is a boolean
return HttpResponseRedirect('login')
# Code to be executed for each request/response after
# the view is called.
return response
You can avoid specifying login_url by setting LOGIN_URL.
Therefore, in settings.py add:
LOGIN_URL = '<some_url>'
And in your views.py annotate relevant functions with only #login_required:
#login_required
def some_view_function(request):
If you need to redirect within a view function, you can do so with:
return redirect_to_login(request.get_full_path())
This can be done with middleware.
I've found a really nifty djangosnippet that does exactly what you are asking for. You can find it here, and it looks like:
from django.http import HttpResponseRedirect
from django.conf import settings
from re import compile
EXEMPT_URLS = [compile(settings.LOGIN_URL.lstrip('/'))]
if hasattr(settings, 'LOGIN_EXEMPT_URLS'):
EXEMPT_URLS += [compile(expr) for expr in settings.LOGIN_EXEMPT_URLS]
class LoginRequiredMiddleware:
"""
Middleware that requires a user to be authenticated to view any page other
than LOGIN_URL. Exemptions to this requirement can optionally be specified
in settings via a list of regular expressions in LOGIN_EXEMPT_URLS (which
you can copy from your urls.py).
Requires authentication middleware and template context processors to be
loaded. You'll get an error if they aren't.
"""
def process_request(self, request):
assert hasattr(request, 'user'), "The Login Required middleware\
requires authentication middleware to be installed. Edit your\
MIDDLEWARE_CLASSES setting to insert\
'django.contrib.auth.middlware.AuthenticationMiddleware'. If that doesn't\
work, ensure your TEMPLATE_CONTEXT_PROCESSORS setting includes\
'django.core.context_processors.auth'."
if not request.user.is_authenticated():
path = request.path_info.lstrip('/')
if not any(m.match(path) for m in EXEMPT_URLS):
return HttpResponseRedirect(settings.LOGIN_URL)
All you have to do is to save the file as middleware.py and include the class in you're settings.py, i.e.
MIDDLEWARE_CLASSES += ('projectname.common.middleware.RequireLoginMiddleware',)
You can also define a LOGIN_URL in settings.py, so that you'll be redirected to your custom login page. The default LOGIN_URL is '/accounts/login/'.
Maybe too late but in django 1.9+ it's too easy.
Django introduced Login Required mixin for generic classes and this a great example
here by William S. Vincent
simply in your view add LoginRequiredMixin as parent class
from django.contrib.auth.mixins import LoginRequiredMixin
class BlogUpdateView(LoginRequiredMixin, UpdateView):
model = Post
template_name = 'post_edit.html'
fields = ['title', 'body']
Also you can use login_required decorator for method request
from django.contrib.auth.decorators import login_required
#login_required(login_url='/login/')
def home(request):
return render(request, "home.html")
It's showing like this: http://127.0.0.1:1235/login/?next=/home/
I'm using class based views and I couldn't get any of these solutions to work for what I needed so I'll post my working solution here.
class ManagementPageView(TemplateView):
# Make a dispatch method to handle authentication
def dispatch(self, *args, **kwargs):
# Check if user is authenticated
if not self.request.user.is_authenticated:
# Redirect them to the home page if not
return redirect('home')
# Render the template if they are
return render(self.request, 'management.html')

Django REST Framework CSRF Failed: CSRF cookie not set

I am using the django rest framework to perform API calls via IOS
and I get the following error
"CSRF Failed: CSRF cookie not set."
Here's my django API code:
class LoginView(APIView):
"""
List all snippets, or create a new snippet.
"""
#csrf_exempt
def get(self, request, format=None):
startups = Startup.objects.all()
serializer = StartupSerializer(startups, many=True)
return Response(serializer.data)
#csrf_exempt
def post(self, request, format=None):
profile = request.POST
....
What can I do?
If anyone is still following this question, the direct answer is that you need to use the decorator on the view method itself. The get and post methods defined on the APIView class just tell DRF how the actual view should behave, but the view method that the django router expects is not actually instantiated until you call LoginView.as_view().
Thus, the solution is to add the csrf_exempt decorator to urls.py. It might look as follows:
#file: urls.py
from django.conf.urls import patterns, url
from django.views.decorators.csrf import csrf_exempt
import views
urlpatterns = patterns('',
url('^login/$', csrf_exempt(views.LoginView.as_view())),
...
)
However, as Mark points out above, csrf protection is important to prevent your sessions from being hijacked. I haven't worked with iOS myself, but I would look into using django's cookie-based csrf tokens. You can use the ensure_csrf_cookie decorator to make django send a csrftoken cookie with a response, and your POST requests will validate as long as you include that token as an X-CSRFToken header.
I've had the same issue. My problem was that I forgot to put .as_view() in urls.py on MyAPIView. So it have to be like:
url(r'$', GetLikesAPI.as_view(), name='list')
not:
url(r'$', GetLikesAPI, name='list')
The problem you encounter here is that django for processing your view is using whatever as_view() method will return, not directly method get() or post().
Therefore you should decorate your class-based view in one of following ways:
In urls.py
urlpatterns = patterns('',
url('^login/$', csrf_exempt(views.LoginView.as_view())),
...
)
or on dispatch() method (pre django 1.9)
from django.utils.decorators import method_decorator
class LoginView(APIView):
#method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
...
or on class view itself (from django 1.9)
from django.utils.decorators import method_decorator
#method_decorator(csrf_exempt, name='dispatch')
class LoginView(APIView):
...
For GETs, you shouldn't be modifying data, so a CSRF isn't required.
If you are modifying data with a POST, then you SHOULD have a CSRF if you are using session based authentication. Otherwise, you're opening up a security hole. Even though you think your Django server is going to be servicing iPhone apps, there's nothing to stop someone with your app from sniffing the packets on the traffic to your server, and then reverse engineering access to the server with other kinds of web clients. For this reason, Django Rest Framework requires a CSRF in some cases. This is mentioned in the Django rest framework documentation.
The path around this requirement for POSTs is to not use session authentication. For example, you could use BasicAuthentication over HTTPS. With this authentication mechanism, you should use HTTPS to prevent credentials from being passed in the clear with every request.
This is an old question but something we ran into recently.
DRF disables CSRF by default, unless using session authentication. By default NSURLconnection is set up to handle cookies. You need to explicitly tell the iOS app to not use cookies. Then you can keep using session auth if needed and not have to csrf exempt your views.
urlpatterns = patterns('',
url('^login/$', csrf_exempt(views.LoginView.as_view())),
...
)
Guys. I had the same error and spent a lot of time just for finding that:
1) I had another router with 'login' and there i missed '$'. I mean sometimes you can forget something in routing and get this error.
In my case it happend because I sent put request to url='http://example.com/list/5' without slash at the end. When I changed url to url='http://example.com/list/5/' all started to work.

Categories

Resources