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 view
from django.shortcuts import redirect, render
def 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.py
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import render
from django.views.generic import DetailView
from .models import Order
@login_required
def profile(request):
return render(request, "profile.html", {"user": request.user})
class OrderDetailView(LoginRequiredMixin, DetailView):
model = Order
template_name = "order_detail.html"
# settings.py
LOGIN_URL = "login" # tên URL pattern của trang đăng nhập
LOGIN_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

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