Lets say I have
.decs
def unwelcome_user(unwelcome_roles=[]):
def decorator(view_func):
def wrapper_func(request, *args, **kwargs):
if request.user.is_authenticated:
for group in request.user.groups.all():
if group.name in unwelcome_roles:
return redirect('main:unwelcome-user-type',
user_type=group.name)
else:
return view_func(request, *args, **kwargs)
else:
return view_func(request, *args, **kwargs)
return wrapper_func
return decorator
.urls
urlpatterns = [
path('error-unwelcome/<str:user_type>', views.unwelcome_user, name='success')
]
.views
#unwelcome_user(unwelcome_roles=['xyz'])
def success(request)
return render(request, 'success_page.html', {})
def unwelcome_user(request, user_type):
return render(request, 'errors/unwelcome_user.html', {})
I can access the unwelcome_user view directly by typing something like mysite.com/error-unwelcome/xyz in the browser even without going through the subsequent process that should result in showing that page.
How do I prevent that?
I'm still new to Django so I couldn't try out the cookie/sessions answers given (I still have to learn sessions). In the meantime, here's what I'm using:
def get_referer(request):
referer = request.META.get('HTTP_REFERER')
if not referer:
return None
return referer
then in any view
def my_view(request):
if not get_referer(request):
raise Http404
return render(request, 'sample.html', {})
Here I'm assuming that if there's no referer, then it means the person typed in the URL into the browser.
If the page is part of a chain of pages for the user to navigate, you will need to pass some state when moving from one page to the next. You can either use a cookie and check the value of the cookie in the view, or pass a GET query parameter:
def success(request):
token = request.GET.get('token', None)
if token is None or not Token.objects.filter(value=token).exists():
return redirect(...) # previous step or beginning
return render(...) # page
Previous pages should create this token. Then pass it in the URL:
/success/?token=<token>
Give this a try
The process_page will only render success.html if your_subsequent_process() returns a True, otherwise, the process.html will be rendered.
def proces_page(request):
"""Your Process View"""
if your_subsequent_process():
return render(request, 'success.html', {})
return render(request, 'proces.html', {})
You can try using sessions [Django docs]. If you are redirecting a user to the page, before redirecting just add a variable to the session indicating that they can access the view.
So just before the redirect:
request.session['is_unwelcome'] = True
return redirect('main:unwelcome-user-type', user_type=group.name)
In the view you would simply check whether the variable is present in the session:
def unwelcome_user(request, user_type):
if 'is_unwelcome' not in request.session:
# Redirect or raise Http 404?
# return redirect('somewhere')
# raise Http404()
del request.session['is_unwelcome']
return render(request, 'errors/unwelcome_user.html', {})
Related
the redirect from /login/ when user is authenticated is handled in this method:
class RedirectAuthenticatedUserMixin(object):
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated and app_settings.AUTHENTICATED_LOGIN_REDIRECTS:
redirect_to = self.get_authenticated_redirect_url()
response = HttpResponseRedirect(redirect_to)
return _ajax_response(request, response)
else:
response = super(RedirectAuthenticatedUserMixin, self).dispatch(
request, *args, **kwargs
)
return response
def get_authenticated_redirect_url(self):
redirect_field_name = self.redirect_field_name
return get_login_redirect_url(
self.request,
url=self.get_success_url(),
redirect_field_name=redirect_field_name,
)
the AUTHENTICATED_LOGIN_REDIRECTS is set by default to True according to documentation
using breakpoints to debug, I have found out that request.user.is_authenticated and app_settings.AUTHENTICATED_LOGIN_REDIRECTS is always returning false, changing the condition to request.user.is_authenticated and settings.AUTHENTICATED_LOGIN_REDIRECTS
the condition was corrected and it returns True
and the user was redirected from /login/ URL to the one set in settings: LOGIN_REDIRECT_URL = "/"
my question why the app_settings was not able to fetch the variables in settings?
and How do I fix this problem?
It is clear that the Django settings variable is ACCOUNT_AUTHENTICATED_LOGIN_REDIRECTS, not AUTHENTICATED_LOGIN_REDIRECTS (Ref the doc)
So, you should set the value for ACCOUNT_AUTHENTICATED_LOGIN_REDIRECTS in your settings.py
My view file has:
def is_authorised(user):
return user.groups.filter(name='bookkeepers').exists()
#login_required
def unauthorised(request):
context = {'user': request.user}
return render(request, 'order_book/unauthorised.html', context)
#login_required
#user_passes_test(is_authorised,
login_url='/order_book/unauthorised/',
redirect_field_name=None)
def book(request):
return render(request, 'order_book/book.html', {})
I want to write a test asserting that a logged in user who is not authorised does get redirected correctly, so far I have this:
class RestrictedViewsTest(TestCase):
#classmethod
def setUpTestData(cls): # noqa
"""Set up data for the whole TestCase."""
User.objects.create_user(username='JaneDoe',
email='jane.doe#example.com',
password='s3kr3t')
def setUp(self):
auth = self.client.login(username='JaneDoe', password='s3kr3t')
self.assertTrue(auth)
def test_book(self):
response = self.client.get('/order_book/book')
self.assertEqual(response.status_code, 301, response)
self.assertTrue(isinstance(response, HttpResponsePermanentRedirect))
def tearDown(self):
self.client.logout()
This works fine as it is but I cannot fathom where to get the redirected to url. Trying to get response['Location'] gives me '/order_book/book' which is not right.
What am I doing wrong?
You can use the assertRedirects method.
def test_book(self):
response = self.client.get('/order_book/book/')
self.assertRedirects(response, '/order_book/unauthorised/', status_code=302, target_status_code=200)
I have a view that looks like this:
#login_required
#active_required()
def myView(request):
print 'in my view'
The active_required customer decorator looks like this:
def active_required():
def decorator(func):
def inner_decorator(request, *args, **kwargs):
my_user = request.user
if my_user.active:
return func(request, *args, **kwargs)
else:
return HttpResponseRedirect(reverse('activate'))
return wraps(func)(inner_decorator)
return decorator
My test looks like this:
def test_my_view(self):
self.client.login(username='user', password='11111111')
response = self.client.post(reverse('my-view'), data, follow=True)
self.assertEqual(response.status_code, 200)
I get the following error:
#active_required
TypeError: active_required() takes no arguments (1 given)
If the database that is created in the test doesn't contain this user that is active, how do I add them? Or am I receiving this error for another reason?
Well looking at the Django login_reguired source login_required takes as first parameter the function that this one will be decorated.
So I think this code should works:
def active_required(func):
#wraps(func)
def inner_decorator(request, *args, **kwargs):
my_user = request.user
if my_user.active:
return func(request, *args, **kwargs)
else:
return HttpResponseRedirect(reverse('activate'))
return inner_decorator
return active_required
If this code does not work (Has not been tested yet) you can use user_passes_test decorator :
def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
"""
Decorator for views that checks that the user passes the given test,
redirecting to the log-in page if necessary. The test should be a callable
that takes the user object and returns True if the user passes.
"""
def decorator(view_func):
#wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view(request, *args, **kwargs):
if test_func(request.user):
return view_func(request, *args, **kwargs)
path = request.build_absolute_uri()
# urlparse chokes on lazy objects in Python 3, force to str
resolved_login_url = force_str(
resolve_url(login_url or settings.LOGIN_URL))
# If the login url is the same scheme and net location then just
# use the path as the "next" url.
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
current_scheme, current_netloc = urlparse(path)[:2]
if ((not login_scheme or login_scheme == current_scheme) and
(not login_netloc or login_netloc == current_netloc)):
path = request.get_full_path()
from django.contrib.auth.views import redirect_to_login
return redirect_to_login(
path, resolved_login_url, redirect_field_name)
return _wrapped_view
return decorator
This one is on the link above and the first parameter this one takes it's a function and that function must accept an parameter that parameter is the user
So doing this I'm sure your code must works:
from django.contrib.auth.decorators import user_passes_test, login_required
#login_required
#user_passes_test(lambda user: user.is_active())
def myView(request):
print 'in my view'
What are the options when you want to return the user to the same page in Django and what are the pros/cons of each?
Methods I know:
HTTP_REFERER
GET parameter containing the previous URL
Session data to store the previous URL
Are there any other?
One of the way is using HTTP_REFERER header like as below:
from django.http import HttpResponseRedirect
def someview(request):
...
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
Not sure of cons of this!
100% working Example
For Class Based View and Function:
from django.http import HttpResponseRedirect
...
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
or
from django.http import HttpResponseRedirect
...
return HttpResponseRedirect(self.request.META.get('HTTP_REFERER'))
Example -
class TaskNotificationReadAllView(generic.View):
def get(self, request, *args, **kwargs):
TaskNotification.objects.filter(assigned_to=request.user).update(read=True)
print(request.META.get('HTTP_REFERER'))
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
While the question and answer is old, I think it's lacking a few options. I have not find any cons with the methods, I would be happy to know if there are any?
request.path_info
request.get_full_path()
request.build_absolute_uri()
from django.shortcuts import redirect
redirect(request.path_info) # No query parameters
redirect(request.build_absolute_uri()) # Keeps query parameters
redirect(request.get_full_path()) # Keeps query parameters
In django view suppose you are not logged in but click on some content that content trigger some url like /board/2/new_topic then #login_required will redirect you to login page with this url
http://localhost:8000/signin/?next=/boards/2/new_topic/
so our aim is redirect to http://localhost:8000/boards/2/new_topic/ page after successful login so one line we will have to add
if 'next' in request.GET:
return redirect(request.GET['next'])
then if it next is there then it will redirect according to that other normal redirect .
Views.py :
def signin(request):
if request.method == "POST":
user_login_form = UserLoginForm(request.POST)
email = request.POST['email']
password = request.POST['password']
user = authenticate(request, email=email, password=password)
if user and user.is_active:
login(request, user)
if 'next' in request.GET:
return redirect(request.GET['next'])
else:
return redirect('home')
else:
return render(request, 'signin.html', context={'form': user_login_form})
else:
user_login_form = UserLoginForm()
return render(request, 'signin.html', context={'form': user_login_form})
I am using the built-in auth_views.password_reset(_confirm, _done, _complete)functionalities, and I would like to limit access to these views only to non-logged-in (anonymous) users, because it doesn't make sense for logged-in users to reset their password.
I found the opposite of #login_required at this link: http://passingcuriosity.com/2009/writing-view-decorators-for-django/
The decorator works for auth_views.password_reset. I use it in my urls as such
url(r'^password/reset/$',
anonymous_required(auth_views.password_reset),
name='auth_password_reset'),
For some reason it does not work with the other 3 views. For example the following url:
url(r'^password/reset/done/$',
anonymous_required(auth_views.password_reset_done),
name='auth_password_reset_done'),
gives me the following error:
Exception Value:
Reverse for 'django.contrib.auth.views.password_reset_done' with arguments '()' and keyword arguments '{}' not found.
Can anyone tell me why?
The decorator code given is:
def anonymous_required(function=None, home_url=None, redirect_field_name=None):
"""Check that the user is NOT logged in.
This decorator ensures that the view functions it is called on can be
accessed only by anonymous users. When an authenticated user accesses
such a protected view, they are redirected to the address specified in
the field named in `next_field` or, lacking such a value, the URL in
`home_url`, or the `USER_HOME_URL` setting.
"""
if home_url is None:
home_url = settings.USER_HOME_URL
def _dec(view_func):
def _view(request, *args, **kwargs):
if request.user.is_authenticated():
url = None
if redirect_field_name and redirect_field_name in request.REQUEST:
url = request.REQUEST[redirect_field_name]
if not url:
url = home_url
if not url:
url = "/"
return HttpResponseRedirect(url)
else:
return view_func(request, *args, **kwargs)
_view.__name__ = view_func.__name__
_view.__dict__ = view_func.__dict__
_view.__doc__ = view_func.__doc__
return _view
if function is None:
return _dec
else:
return _dec(function)
I see password_reset includes a reverse of the routine mentioned in the error message. Is it trying to do this reverse but you have overridden it, or not included it in the urls.conf?
django/contrib/auth/views.py:141-142 (v1.3)
if post_reset_redirect is None:
post_reset_redirect = reverse('django.contrib.auth.views.password_reset_done')