Introduction
In most Django projects, not every page should be public. Some pages should only be visible to logged-in users, and some actions should only be allowed for specific users such as admins, editors, teachers, or managers.
For example:
- a dashboard should be accessible only after login
- an admin page should be restricted to staff users
- an edit page should be available only to the author of a post
- some users may view data, while others may add, edit, or delete it
This is where login protection and permissions become essential.
In this tutorial, you will learn how to:
- protect pages so only authenticated users can access them
- restrict views based on user roles
- use Django’s built-in permission system
- work with groups and permissions
- control access in both function-based views and class-based views
By the end, you will know how to secure your Django pages properly and build applications with controlled access.
What You Will Learn
By the end of this tutorial, you will understand:
- how to use
login_required - how to redirect unauthenticated users
- how to protect class-based views
- how Django permissions work
- how to use
permission_required - how to check permissions in templates
- how to use groups
- how to restrict pages by ownership or role
Prerequisites
Before starting, you should already know:
- Django authentication basics
- users, login, and logout
- views and templates
- class-based views basics
- Django models
1. Why Protect Pages?
In a real application, some information should not be public.
Examples:
- user profile dashboard
- order history
- teacher area
- management pages
- content editing pages
- private documents
If you do not protect these pages, anyone can access them just by opening the URL.
So Django provides tools to control:
- who can enter a page
- what they are allowed to do
2. login_required — Protect Function-Based Views
The simplest way to protect a page is with login_required.
Example
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
@login_required
def dashboard(request):
return render(request, 'dashboard.html')What it does
- if the user is logged in, the page is shown
- if the user is not logged in, Django redirects them to the login page
3. Set the Login URL
Django needs to know where to send unauthenticated users.
settings.py
LOGIN_URL = 'login'You can also use a direct path:
LOGIN_URL = '/login/'If a user tries to open a protected page while not logged in, Django redirects them there.
4. Redirect After Login
You can also define where users go after logging in.
settings.py
LOGIN_REDIRECT_URL = 'dashboard'
LOGOUT_REDIRECT_URL = 'login'This means:
- after login → go to dashboard
- after logout → go to login page
5. Example with URLs
urls.py
from django.urls import path
from .views import dashboard
urlpatterns = [
path('dashboard/', dashboard, name='dashboard'),
]Now dashboard/ is protected.
6. What Happens When a User Is Not Logged In?
Suppose a visitor tries to access:
/dashboard/If they are not logged in, Django redirects them to:
/login/?next=/dashboard/The next parameter tells Django where to return the user after successful login.
This is very useful because the user returns to the page they originally wanted.
7. Protecting Class-Based Views
For class-based views, Django uses LoginRequiredMixin.
Example
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
class DashboardView(LoginRequiredMixin, TemplateView):
template_name = 'dashboard.html'Important Rule
Put LoginRequiredMixin before the main view class.
Correct:
class DashboardView(LoginRequiredMixin, TemplateView):Not:
class DashboardView(TemplateView, LoginRequiredMixin):8. Protecting List and Detail Views
You can also protect generic views the same way.
Example
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView
from .models import Post
class PostListView(LoginRequiredMixin, ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'Now only logged-in users can see the post list.
9. Restricting Access by Staff Status
Some pages should only be visible to staff users.
Django’s User model has built-in fields:
is_authenticatedis_staffis_superuser
Example
from django.contrib.auth.decorators import user_passes_test
from django.shortcuts import render
def is_staff_user(user):
return user.is_staff
@user_passes_test(is_staff_user)
def staff_dashboard(request):
return render(request, 'staff_dashboard.html')This allows only staff users.
10. Using user_passes_test
user_passes_test lets you define your own rule.
Example: only superusers
from django.contrib.auth.decorators import user_passes_test
def is_admin(user):
return user.is_superuser
@user_passes_test(is_admin)
def admin_panel(request):
return render(request, 'admin_panel.html')11. Django’s Built-In Permission System
Django automatically creates permissions for each model:
add_modelnamechange_modelnamedelete_modelnameview_modelname
For example, for a Post model in an app called blog, Django creates permissions like:
blog.add_postblog.change_postblog.delete_postblog.view_post
These permissions can be assigned to users or groups.
12. Using permission_required
Django provides the permission_required decorator.
Example
from django.contrib.auth.decorators import permission_required
@permission_required('blog.add_post')
def add_post(request):
return render(request, 'blog/add_post.html')This means only users with the permission blog.add_post can access the page.
If they do not have permission, access is denied.
13. Handling Exceptions with permission_required
By default, permission_required redirects to login if the user is not logged in.
You can also raise an error instead:
@permission_required('blog.add_post', raise_exception=True)
def add_post(request):
return render(request, 'blog/add_post.html')If the user lacks permission, Django returns a 403 Forbidden error.
14. Checking Permissions Manually in a View
Sometimes you want more custom control.
Example
from django.http import HttpResponseForbidden
from django.shortcuts import render
def add_post(request):
if not request.user.has_perm('blog.add_post'):
return HttpResponseForbidden("You do not have permission to add posts.")
return render(request, 'blog/add_post.html')
This is useful when you want custom messages or custom logic.
15. Using Permissions in Class-Based Views
For CBVs, use PermissionRequiredMixin.
Example
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import TemplateView
class AddPostView(PermissionRequiredMixin, TemplateView):
template_name = 'blog/add_post.html'
permission_required = 'blog.add_post'Only users with that permission can access the page.
16. Combine Login and Permission Mixins
Often you want both login and permission protection.
Example
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import TemplateView
class AddPostView(LoginRequiredMixin, PermissionRequiredMixin, TemplateView):
template_name = 'blog/add_post.html'
permission_required = 'blog.add_post'This means:
- user must be logged in
- user must have the
blog.add_postpermission
17. Groups in Django
Django groups let you assign permissions to several users at once.
For example, you may create groups like:
- Editors
- Teachers
- Managers
- Moderators
Instead of assigning permissions user by user, you assign permissions to the group, then add users to the group.
18. Creating Groups in the Admin Panel
Go to Django admin:
- open Groups
- create a new group, for example
Editors - assign permissions such as:
blog.add_postblog.change_postblog.delete_post
- add users to the group
Now all users in the Editors group inherit those permissions.
This is much easier for real projects.
19. Checking Group Membership
You can check whether a user belongs to a group.
Example
def is_editor(user):
return user.groups.filter(name='Editors').exists()Then use it with user_passes_test:
from django.contrib.auth.decorators import user_passes_test
@user_passes_test(is_editor)
def editor_dashboard(request):
return render(request, 'editor_dashboard.html')20. Permissions in Templates
Django lets you check permissions directly in templates.
Example
{% if perms.blog.add_post %}
<a href="{% url 'add_post' %}">Add Post</a>
{% endif %}This means:
- if the user has the permission
blog.add_post - show the “Add Post” link
You can also check authentication:
{% if user.is_authenticated %}
<a href="{% url 'dashboard' %}">Dashboard</a>
{% endif %}21. Hiding vs Protecting
This is important:
- hiding a button in the template is not enough
- you must also protect the view itself
For example, even if a user does not see the “Delete Post” button, they could still manually type the delete URL into the browser.
So always protect both:
- the template display
- the view access
22. Restricting by Ownership
Sometimes the rule is not based on staff or permissions, but ownership.
For example:
- only the author can edit their own post
- only the owner can view their private file
- only the student can view their own grades
Function-Based View Example
from django.http import HttpResponseForbidden
from django.shortcuts import get_object_or_404, render
from .models import Post
def edit_post(request, post_id):
post = get_object_or_404(Post, id=post_id)
if request.user != post.author:
return HttpResponseForbidden("You can only edit your own posts.")
return render(request, 'blog/edit_post.html', {'post': post})23. Ownership Restriction in Class-Based Views
For CBVs, you can override dispatch() or get_object().
Example
from django.http import HttpResponseForbidden
from django.views.generic import UpdateView
from .models import Post
class PostUpdateView(UpdateView):
model = Post
fields = ['title', 'content']
template_name = 'blog/post_form.html'
def dispatch(self, request, *args, **kwargs):
post = self.get_object()
if request.user != post.author:
return HttpResponseForbidden("You can only edit your own posts.")
return super().dispatch(request, *args, **kwargs)This protects the update page so only the author can edit the post.
24. Full Example: Protected Blog Management
Let us imagine a blog with these rules:
- everyone can view public posts
- only logged-in users can access dashboard
- only editors can add posts
- only the author can edit their own posts
views.py
from django.contrib.auth.decorators import login_required, permission_required
from django.http import HttpResponseForbidden
from django.shortcuts import render, get_object_or_404
from .models import Post
@login_required
def dashboard(request):
return render(request, 'dashboard.html')
@permission_required('blog.add_post', raise_exception=True)
def add_post(request):
return render(request, 'blog/add_post.html')
@login_required
def edit_post(request, post_id):
post = get_object_or_404(Post, id=post_id)
if request.user != post.author:
return HttpResponseForbidden("You can only edit your own posts.")
return render(request, 'blog/edit_post.html', {'post': post})This is a realistic access-control structure.
25. Custom Permissions
You can define your own permissions inside a model.
Example
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
class Meta:
permissions = [
('can_publish_post', 'Can publish post'),
]After migrations, Django creates this custom permission.
You can assign it to users or groups just like built-in permissions.
Then use it like this:
@permission_required('blog.can_publish_post', raise_exception=True)
def publish_post(request):
...26. Common Mistakes
Forgetting to protect the view
Showing or hiding links in templates is not enough.
Using login_required but not setting LOGIN_URL
This may cause redirect confusion.
Mixing authentication and permission logic
Being logged in does not automatically mean the user has permission.
Not using groups
For bigger projects, groups make permission management much easier.
Forgetting ownership checks
A logged-in user should not automatically be able to edit everyone’s content.
27. Best Practices
- protect private pages with
login_required - use
PermissionRequiredMixinorpermission_requiredfor restricted actions - use groups for role-based access
- check ownership when needed
- hide unauthorized buttons in templates, but also secure the view
- keep permission logic clear and simple
- use Django admin to manage permissions and groups easily
28. Mini Project Example
Imagine a school platform:
- students can only see their own dashboard
- teachers can add grades
- managers can access reports
- admins can manage all data
Possible rules:
login_requiredfor student dashboardpermission_required('school.add_grade')for teachers- group
Managersfor reports - ownership checks for personal data
This is exactly the kind of access control real Django applications need.
29. Summary
In this tutorial, you learned that:
login_requiredprotects pages for logged-in usersLoginRequiredMixinprotects class-based views- Django has a built-in permission system
permission_requiredandPermissionRequiredMixinrestrict access by permission- groups make it easier to manage permissions
- template checks help hide UI elements
- ownership checks protect personal content
- proper access control is essential in real projects
31. Mini Quiz
1. Which decorator protects a function-based view for logged-in users only?
A. user_passes_test
B. permission_required
C. login_required
D. auth_required
2. Which mixin protects a class-based view for logged-in users?
A. UserRequiredMixin
B. LoginRequiredMixin
C. AuthMixin
D. SecureViewMixin
3. Which decorator checks a specific permission?
A. group_required
B. login_required
C. permission_required
D. staff_required
4. What does user.has_perm() check?
A. Whether the user is active
B. Whether the user has a specific permission
C. Whether the user belongs to a group
D. Whether the user is logged out
5. Why are groups useful?
A. They replace models
B. They speed up templates
C. They let you assign permissions to multiple users easily
D. They automatically create dashboards
32. What Comes Next?
The next ideal tutorial is:
Tutorial: Django Sessions and Cookies
Subject: how Django remembers users, stores session data, and manages cookies.
💬 Comments
No comments yet. Be the first to comment!
Login to comment.