Nhiều view trong dự án Django — trang hồ sơ, giỏ hàng, bảng điều khiển — chỉ nên mở cho người đã đăng nhập, nhưng viết tay if not request.user.is_authenticated ở đầu mỗi view vừa lặp lại vừa dễ sót. Django đã có sẵn giải pháp: decorator @login_required cho function view và mixin LoginRequiredMixin cho class-based view, cả hai đều chuyển hướng người chưa đăng nhập về trang login và đưa họ quay lại đúng trang ban đầu sau khi đăng nhập. Dùng chúng ngay từ đầu cho mọi view cần xác thực, thay vì kiểm tra thủ công trong từng hàm.
Vấn đề
Giả sử có trang hồ sơ chỉ dành cho thành viên. Cách viết tay thường thấy là kiểm tra request.user.is_authenticated trong từng view rồi chuyển hướng về trang đăng nhập nếu chưa đăng nhập. Với một view thì không sao, nhưng khi dự án có chục view cần bảo vệ, đoạn kiểm tra lặp lại khắp nơi, và chỉ cần quên một chỗ là lộ dữ liệu. Tệ hơn, nếu tự viết redirect mà quên giữ lại địa chỉ trang đích, người dùng đăng nhập xong lại rơi về trang chủ thay vì trang họ đang muốn xem.
# views.py — cách viết tay, lặp lại ở mọi viewfrom django.shortcuts import redirect, renderdef profile(request): if not request.user.is_authenticated: return redirect("login") return render(request, "profile.html", {"user": request.user})
Giải pháp
Decorator @login_required làm đúng việc đó trong một dòng: chưa đăng nhập thì chuyển về trang đăng nhập, kèm tham số next chứa địa chỉ trang gốc để sau khi đăng nhập Django đưa người dùng quay lại. Với class-based view, dùng LoginRequiredMixin đặt trước class view trong danh sách kế thừa — thứ tự này bắt buộc vì mixin phải chạy trước. Nếu trang đăng nhập của bạn không nằm ở /accounts/login/ mặc định, chỉ cần khai báo LOGIN_URL trong settings hoặc truyền login_url cho decorator.
# views.pyfrom django.contrib.auth.decorators import login_requiredfrom django.contrib.auth.mixins import LoginRequiredMixinfrom django.shortcuts import renderfrom django.views.generic import DetailViewfrom .models import Order@login_requireddef profile(request): return render(request, "profile.html", {"user": request.user})class OrderDetailView(LoginRequiredMixin, DetailView): model = Order template_name = "order_detail.html"
# settings.pyLOGIN_URL = "login" # tên URL pattern của trang đăng nhậpLOGIN_REDIRECT_URL = "home" # trang mặc định sau khi đăng nhập
Áp dụng vào dự án của bạn
Rà soát lại toàn bộ view trong dự án và gắn @login_required hoặc LoginRequiredMixin cho mọi view hiển thị dữ liệu riêng tư — hồ sơ, đơn hàng, cài đặt tài khoản, trang quản trị nội bộ. Với API dùng session authentication, cùng decorator này vẫn hoạt động, còn API dùng token thì chuyển sang permission của DRF. Một lưu ý nhỏ: đừng gắn decorator cho trang đăng nhập hay trang đăng ký, vì người chưa đăng nhập sẽ bị chuyển hướng vòng tròn không vào được.
Bài viết liên quan
- Django: tạo custom User model ngay từ đầu — đừng để sau này phải migrate đau đầu
- Django signals: tự động tạo Profile cho user mới bằng post_save trong 5 phút
- Django admin: phân quyền theo nhóm — nhân viên chỉ xem, quản lý mới được sửa trong 5 phút
Câu hỏi thường gặp
Người chưa đăng nhập bị chuyển đi đâu?
Về trang ở LOGIN_URL, mặc định là /accounts/login/, kèm tham số ?next=/duong/dan/goc để sau khi đăng nhập Django đưa người dùng quay lại đúng trang họ muốn xem.
LoginRequiredMixin phải đặt ở đâu trong class?
Đặt đầu tiên trong danh sách kế thừa, trước View, DetailView hay ListView. Đặt sau thì mixin không chạy và view vẫn mở cho khách chưa đăng nhập.
Muốn trang công khai vẫn hiện nhưng ẩn nút cho khách thì sao?
Không dùng decorator. Trong template, kiểm tra {% if user.is_authenticated %} để ẩn hoặc hiện từng phần, ví dụ nút “Đặt hàng” chỉ hiện với thành viên.
Dùng chung với phân quyền theo vai trò được không?
Được. Kết hợp LoginRequiredMixin với UserPassesTestMixin hoặc PermissionRequiredMixin, và luôn để mixin xác thực đứng trước mixin phân quyền trong danh sách kế thừa.
💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.
Nguồn tham khảo
- Django documentation — The login_required decorator: https://docs.djangoproject.com/en/6.1/topics/auth/default/#the-login-required-decorator
- Django documentation — The LoginRequiredMixin mixin: https://docs.djangoproject.com/en/6.1/topics/auth/default/#the-loginrequiredmixin-mixin
