In this post, we would like to see how we can limit user accesses to our Django views.
Login Required & Permission Required Decorators
If you have worked with Django, you probably have used the login_required
decorator already. Adding the decorator to a view limits access only to the logged in users. If the user is not logged in, s/he is redirected to the default login page. Or we can pass a custom login url to the decorator for that purpose.
Let’s see an example:
1 2 3 4 5 |
from django.contrib.auth.decorators import login_required @login_required def secret_page(request): return render_to_response("secret_page.html") |
There’s another nice decorator – permission_required
which works in a similar fashion:
1 2 3 4 5 |
from django.contrib.auth.decorators import permission_required @permission_required('entity.can_delete', login_url='/loginpage/') def my_view(request): return render_to_response("entity/delete.html") |
Awesome but let’s learn how do they work internally.
How do they work?
We saw the magic of the login_required
and permission_required
decorators. But we’re the men of science and we don’t like to believe in magic. So let’s unravel the mystery of these useful decorators.
Here’s the code for the login_required
decorator:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None): """ Decorator for views that checks that the user is logged in, redirecting to the log-in page if necessary. """ actual_decorator = user_passes_test( lambda u: u.is_authenticated(), login_url=login_url, redirect_field_name=redirect_field_name ) if function: return actual_decorator(function) return actual_decorator |
By reading the code, we can see that the login_required
decorator uses another decorator – user_passes_test
which takes/uses a callable to determine whether the user should have access to this view. The callable must accept an user instance and return a boolean value. user_passes_test
returns a decorator which is applied to our view.
If we see the source of permission_required
, we would see something quite similar. It also uses the same user_passes_test
decorator.
Building Our Own Decorators
Now that we know how to limit access to a view based on whether the logged in user passes a test, it’s quite simple for us to build our own decorators for various purposes. Let’s say we want to allow access only to those users who have verified their emails.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth import REDIRECT_FIELD_NAME def check_email_verification(user): return EmailVerification.objects.all().filter(user=user, verified=True) def check_email(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None): """ Decorator for views that checks that the user is logged in, redirecting to the log-in page if necessary. """ actual_decorator = user_passes_test( check_email_verification, login_url=login_url, redirect_field_name=redirect_field_name ) if function: return actual_decorator(function) return actual_decorator |
Now we can use the decorator to a view like:
1 2 3 4 |
@login_required @check_email(login_url="/redirect/login/?reason=verify_email") def verified_users_only(request): return render_to_response("awesome/offers.html") |
Users who have verified their email addresses will be able to access this view. And if they didn’t, they will be redirected to the login view. Using the reason
query string, we can display a nice message explaining what’s happening.
Please note, we have used two decorators on the same view. We can use multiple decorators like this to make sure the user passes all the tests we require them to.