Django Redirect after Successful Login - django

I have a requirement that whenever I login or attempt to request a view that has a login_decorator then the next page be a page where I am required to ask the user to select a business entity (irreespective of the original view requested).
Let's say that the page is http://127.0.0.1:8999/APP/business_unit
To achieve this I configured the following in my settings.py
LOGIN_REDIRECT_URL='/APP/business_unit_selector'
Now when i try to access http://127.0.0.1:8999/APP/range_list
the page goes to http://127.0.0.1:8999/APP/login?next=/APP/range_list I was expecting that the next page after login be /APP/business_unit
but instead, the next page was /APP/range_list
The browser address bar has http://127.0.0.1:8999/APP/login?next=/APP/range_list
Is it possible to achieve what I am trying in Django?

LOGIN_REDIRECT_URL is used unly when next is unspecified. In your test request there is next=/APP/range_list - and that address is used to redirect user after login.
Probably the easiest and most effective solution is to make your own decorator, similar to login_required which redirects to /APP/business_unit_selector&next=<redirect_url> if unit is not selected, and apply it together with login_required. It is not the most efficient solution in terms of redirects number, but is quite clean, and doesn't mess up the login page.
You will also have to handle next parameter in your business_unit_selector view, if you like to achieve natural flow.
Your decorator should be something like
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.utils.http import urlquote
import functools
def business_unit_required(view):
#login_required # probably you want to incorporate it here
#functools.wraps(view)
def wrapped(request, *args, **kwargs):
if not 'selected_business_unit' in request.session:
return redirect('%s?next=%s' % (
reverse('business_unit_selector'),
urlquote(request.get_full_path())))
return view(request, *args, **kwargs)
return wrapped

The reason that http://127.0.0.1:8999/APP/login?next=/APP/range_list is redirecting you to range_list after logging in, is because with next= you are overriding what is specified in your settings file, LOGIN_REDIRECT_URL='/APP/business_unit_selector'.
If I understand correctly you need to user to choose a business entity after logging in.
A couple solutions that come to mind are as follows:
1.) Don't use a separate forms for login and business entity. Instead combine them.
Username
Password
Business Entity
2.) You can also specify in your view if the user doesn't have a buisness entity ResponseRedirect("/APP/business_unit_selector")
docs here

Related

Django URL and making a portion of it optional, but using the same class API View

So I have this API URL on the back-end and I am wondering how to make a portion of it optional.
url(r'^api/documents/(?P<id>[0-9]+)$', GetDocumentsAPIView.as_view(), name='documents'),
So two things can happen coming from the front-end.
1) When the user logs in, it brings them to the /home which lists all of their projects. Clicking on the project brings them /documents/85 where the number is the number of the project and it lists all the documents for that project. This is sent to the API URL /api/documents/85. This much is working fine.
2) The other option is the user will just click on a Documents link in the navbar, which will just bring them to /documents. This should just go to the API URL /api/documents/ and eventually onto the serializer.py where their most recent project is determined and the documents for that are returned and rendered in the front-end
This I can't get working. Not sure if I can do it with just one url(), or if I need two. Was hoping the one I posted would look at the (?P<id>[0-9]+)$ as optional and if nothing was there would return the same GetDocumentsAPIView, but this is not the case.
EDIT: Including the view I am using for testing at this point. Just print() to see if anything is being routed to the view.
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
class GetDocumentsAPIView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
print(kwargs.get('id'))
return Response('Test')
No you need to add a URL pattern for r'^api/documents/$', which can launch the same view by the way. Only inside the view you'll have to assume id is optional:
if kwargs.get('id'):
# return specific document
else:
# return list of documents
I would rename your first URL pattern to 'view_document' so you can use the name 'documents' on the other, more generic view.
Note: I think your URL structure is wrong. Reading your URL, according to REST, I would expect to see the document with id 85 when fetching documents/85. If you actually are listing the docs of a project, the URL should be projects/85/documents/ or if unspecified projects/documents.

Logout user when they leave site

I want to be able to automatically log users out when they leave my Django site. Not just when they close the browser, but whenever they navigate away to a different site. Is this possible?
I'm displaying sensitive data to the user, and if they were to go to a new URL, and then hit the back button, I don't want the sensitive data displayed. I want them to be forced to log back in.
Is this possible? Or is this something I would have to do on the front end?
Check out this answer on how to detect when a visitor moves away from the page.
Documentation: Page Visibility API
Logout user via Ajax:
from django.views.generic import View
from django.http import JsonResponse
from django.contrib.auth import logout
class LogoutView(View):
"""
The logout view class. This will log the user out and invalidate the session.
"""
def post(self, *args, **kwargs):
logout(self.request)
return JsonResponse({'success': True}, **kwargs)
If you only want to 'logout' user if they leave the page, try hooking into onbeforeunload event.
$(window).unload(function () {
/* login for ajax call goes here */
});
WindowEventHandlers.onbeforeunload

How can I redirect a user to a specific page or run some code logic in a common place after login in Django?

What I want is to redirect the user to a specific page when they log in if a flag is raised. So I know that all of the login requests go through django.contrib.auth.views.login. Do I have to modify this to accomplish what I want to do or is there some other way to do this. So when I log in I hit the index page and that's fine, I can work with that but what if the user tries to access a page where log in is required. Here's what it will look like
login/?next=/page_that_requires_login
so now after login, it will not hit the index page anymore, it will go directly to page_that_requires_login. I am a little new to django but my instinct tells me that I shouldn't have to check this flag on every page. Is there a common place that I can do some code logic after a user is logged in no matter what page they get redirect to?
Django provides decorator called, login_required() which we can attach to any view, where we require the user to be logged in. If a user is not logged in and they try to access a page which calls that view, then the user is redirected to another page which you can set, typically the login page.The following is an example code for a view called restricted.
#login_required
def restricted(request):
return HttpResponse("since you are logged in you can see this page)
Note that to use a decorator, you place it directly above the function signature, and put a # before naming the decorator. Python will execute the decorator before executing the code of your function/method. To use the decorator you will have to import it, so also add the following import:
from django.contrib.auth.decorators import login_required
To set a page to which the user is redirected if he is not logged in, you need to add the something like this to settings.py
LOGIN_URL = "/app_name/login/"
This ensures that the login_required() decorator will redirect any user not logged in to the URL /app_name/login/.
I redirected by groupwise as :
from django.http import HttpResponseRedirect
def loggedin_view(request):
usergroup = None
if request.user.is_authenticated():
usergroup = request.user.groups.values_list('name', flat=True).first()
if usergroup == "staffGroup":
return HttpResponseRedirect("/cmit/userDash")
elif usergroup == "supervisorGroup":
return HttpResponseRedirect("/cmit/supervisorDash")
elif usergroup == "auditGroup":
return HttpResponseRedirect("/cmit/auditDash")
elif usergroup == "mgtGroup":
return HttpResponseRedirect("/cmit/mgtDash")
else:
return HttpResponseRedirect("/admin/")

Django: re-use login_required decorator inside other decorators

According to one of the comments in https://stackoverflow.com/a/8715790/210481, which I agree with, we should avoid multiple decorators if one depends on the other.
So, in the example, if we have a decorator "active_required" for active users, we should not have to use both active_required and login_required on the same view.
We should have "login_required" decorator "called" somehow inside the "active_required" one.
Is it possible to do it with the standard "login_required" decorator that comes with django?
My requirements are:
1) if the user is not authenticated, I should redirect him to LOGIN_URL
2) if the user is authenticated (passed login_required), but is not active, I should redirect him to a page to "re-activate" his account
3) if the user is authenticated and active, the user can access the view
Thanks in advance
When thinking about your question, I found it easier to create a simple active_required decorator first. This is very easy, because we can use the the user_passes_test function in django.contrib.auth.decorators.
The question then changes to "how do I combine the login_required and active_required into one decorator?". We need to define a function which:
takes a view function as it's argument
applies both decorators to it to create a new view function
returns the new view function
Putting it all together, you have the following:
from django.contrib.auth.decorators import user_passes_test, login_required
active_required = user_passes_test(lambda u: u.is_active, login_url=REACTIVATE_URL)
def active_and_login_required(view_func):
decorated_view_func = login_required(active_required(view_func))
return decorated_view_func

How do I redirect users who try to access admin area in Django?

I've noticed an interesting problem with Django's admin area. If I revoke my staff permissions and try to access /admin directly, I would normally expect a redirect to my login page with /admin/ in the query string as a future redirect. However, I get a proper page returned with HTTP code 200 which actually uses my admin/login.html template to render that requested page instead of redirecting. It seems the problem lies within the #staff_member_required decorator, which admin views obviously use.
The question is: is this done on purpose? If not, how can I change this behaviour without too much monkey-patching?
This is done on purpose, because many people implement redirects in thier sites which could block access to the admin panel. Because the admin panel is it's own app it redirects to itself.
# Put this code somewhere it will be imported REALLY early
from django.contrib.admin.views import decorators
def staff_member_required(view_func):
"""
Decorator for views that checks that the user is logged in and is a staff
member, displaying the login page if necessary.
"""
def _checklogin(request, *args, **kwargs):
if request.user.is_active and request.user.is_staff:
# The user is valid. Continue to the admin page.
return view_func(request, *args, **kwargs)
else:
return HTTPResponseRedirect('/my/login/page/')
return wraps(view_func)(_checklogin)
decorators.staff_member_required = staff_member_required #replaces the function in-place