第9章:用户认证系统
9.3 权限和装饰器
login_required装饰器
login_required装饰器是最常用的认证装饰器,用于确保只有已登录用户才能访问特定视图:
python
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect
# 基本用法
@login_required
def profile_view(request):
return render(request, 'profile.html', {'user': request.user})
# 指定登录URL
@login_required(login_url='/accounts/login/')
def dashboard_view(request):
return render(request, 'dashboard.html')
# 指定登录URL和重定向字段名
@login_required(login_url='/accounts/login/', redirect_field_name='redirect_to')
def protected_view(request):
return render(request, 'protected.html')
# 在类视图中使用
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
class ProfileView(LoginRequiredMixin, TemplateView):
template_name = 'profile.html'
login_url = '/accounts/login/'
redirect_field_name = 'redirect_to'自定义登录_required装饰器:
python
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.shortcuts import resolve_url
from django.conf import settings
def custom_login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
"""
自定义登录_required装饰器,添加额外的逻辑
"""
def check_user(user):
# 检查用户是否已登录且已验证邮箱
return user.is_authenticated and hasattr(user, 'emailverification') and user.emailverification.is_verified
if not login_url:
login_url = settings.LOGIN_URL
actual_decorator = user_passes_test(
check_user,
login_url=login_url,
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
return actual_decorator
# 使用自定义装饰器
@custom_login_required
def verified_user_view(request):
return render(request, 'verified_content.html')权限检查
Django提供了多种方式来检查用户权限:
python
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import PermissionDenied
from django.shortcuts import render
# 基本权限检查装饰器
@permission_required('articles.add_article')
def create_article(request):
return render(request, 'create_article.html')
# 多个权限检查
@permission_required(['articles.add_article', 'articles.change_article'])
def edit_article(request):
return render(request, 'edit_article.html')
# 权限检查失败时抛出异常而不是重定向
@permission_required('articles.delete_article', raise_exception=True)
def delete_article(request, article_id):
# 删除文章的逻辑
pass
# 在类视图中使用权限检查
class ArticleCreateView(PermissionRequiredMixin, CreateView):
permission_required = 'articles.add_article'
model = Article
template_name = 'article_form.html'
fields = ['title', 'content']
# 手动检查权限
def manual_permission_check(request):
if not request.user.has_perm('articles.add_article'):
raise PermissionDenied("您没有创建文章的权限")
return render(request, 'create_article.html')
# 检查多个权限
def multiple_permissions_check(request):
required_permissions = ['articles.add_article', 'articles.change_article']
if not request.user.has_perms(required_permissions):
raise PermissionDenied("您没有足够的权限")
return render(request, 'edit_article.html')
# 在模板中检查权限
"""
<!-- 模板中检查权限 -->
{% if perms.articles.add_article %}
<a href="{% url 'article_create' %}">创建文章</a>
{% endif %}
{% if perms.articles.change_article %}
<a href="{% url 'article_edit' article.pk %}">编辑文章</a>
{% endif %}
"""用户组权限
通过用户组来管理权限是Django推荐的做法:
python
from django.contrib.auth.models import User, Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.decorators import user_passes_test
from django.core.exceptions import PermissionDenied
# 创建和管理用户组
def setup_groups():
# 创建组
editors_group, created = Group.objects.get_or_create(name='Editors')
authors_group, created = Group.objects.get_or_create(name='Authors')
reviewers_group, created = Group.objects.get_or_create(name='Reviewers')
# 获取内容类型,ContentType是用来追踪模型(数据库表)的元数据信息。
content_type = ContentType.objects.get_for_model(Article)
# 创建权限
add_article = Permission.objects.get(
content_type=content_type,
codename='add_article'
)
change_article = Permission.objects.get(
content_type=content_type,
codename='change_article'
)
delete_article = Permission.objects.get(
content_type=content_type,
codename='delete_article'
)
publish_article = Permission.objects.get(
content_type=content_type,
codename='publish_article'
)
# 为组分配权限
# 作者组:可以创建和编辑自己的文章
authors_group.permissions.add(add_article, change_article)
# 编辑组:可以创建、编辑和删除所有文章
editors_group.permissions.add(add_article, change_article, delete_article)
# 审核组:可以发布文章
reviewers_group.permissions.add(publish_article)
# 将用户添加到组
def add_user_to_group(username, group_name):
try:
user = User.objects.get(username=username)
group = Group.objects.get(name=group_name)
user.groups.add(group)
return True
except (User.DoesNotExist, Group.DoesNotExist):
return False
# 检查用户是否属于特定组
def user_in_group(user, group_name):
return user.groups.filter(name=group_name).exists()
# 基于组的装饰器
def group_required(group_names, login_url=None):
"""
要求用户属于指定组的装饰器
"""
if not isinstance(group_names, (list, tuple)):
group_names = [group_names]
def check_group(user):
if user.is_authenticated:
return user.groups.filter(name__in=group_names).exists()
return False
return user_passes_test(check_group, login_url=login_url)
# 使用组装饰器
@group_required(['Editors', 'Authors'])
def article_management(request):
return render(request, 'article_management.html')
@group_required('Editors')
def delete_article_view(request, article_id):
# 删除文章的逻辑
pass
# 在类视图中使用组检查
from django.contrib.auth.mixins import UserPassesTestMixin
class EditorRequiredMixin(UserPassesTestMixin):
def test_func(self):
return self.request.user.groups.filter(name='Editors').exists()
class ArticleDeleteView(EditorRequiredMixin, DeleteView):
model = Article
success_url = '/articles/'
# 模板中的组检查
"""
<!-- 模板中检查用户组 -->
{% if user.groups.all %}
<p>您的组:
{% for group in user.groups.all %}
<span class="badge bg-primary">{{ group.name }}</span>
{% endfor %}
</p>
{% endif %}
{% if user.groups.filter(name='Editors').exists %}
<a href="{% url 'article_delete' article.pk %}" class="btn btn-danger">删除文章</a>
{% endif %}
"""自定义权限
除了Django内置的增删改查权限,你还可以创建自定义权限:
python
# models.py
from django.db import models
from django.contrib.auth.models import User
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
is_published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
permissions = [
("publish_article", "Can publish article"),
("moderate_article", "Can moderate article"),
("feature_article", "Can feature article"),
]
# 在views.py中使用自定义权限
from django.contrib.auth.decorators import permission_required
@permission_required('articles.publish_article')
def publish_article(request, article_id):
article = get_object_or_404(Article, id=article_id)
article.is_published = True
article.save()
return redirect('article_list')
# 创建更复杂的权限检查
def can_edit_article(user, article):
"""
检查用户是否可以编辑文章
"""
# 超级用户可以编辑所有文章
if user.is_superuser:
return True
# 文章作者可以编辑自己的文章
if user == article.author:
return True
# 编辑组用户可以编辑所有文章
if user.groups.filter(name='Editors').exists():
return True
# 用户有编辑权限
if user.has_perm('articles.change_article'):
return True
return False
# 在视图中使用复杂权限检查
def edit_article(request, article_id):
article = get_object_or_404(Article, id=article_id)
if not can_edit_article(request.user, article):
raise PermissionDenied("您没有权限编辑这篇文章")
# 编辑文章的逻辑
if request.method == 'POST':
# 处理表单提交
pass
else:
# 显示编辑表单
pass
return render(request, 'edit_article.html', {'article': article})
# 基于对象的权限装饰器
def object_permission_required(permission_checker):
"""
对象级权限检查装饰器
"""
def decorator(view_func):
def wrapper(request, *args, **kwargs):
if not permission_checker(request.user, *args, **kwargs):
raise PermissionDenied("您没有权限执行此操作")
return view_func(request, *args, **kwargs)
return wrapper
return decorator
# 使用对象级权限装饰器
@object_permission_required(can_edit_article)
def edit_article_decorated(request, article_id):
# 视图逻辑
pass
# 在模板中使用权限检查
"""
<!-- 模板中检查自定义权限 -->
{% if perms.articles.publish_article %}
<form method="post" action="{% url 'publish_article' article.pk %}">
{% csrf_token %}
<button type="submit" class="btn btn-success">发布文章</button>
</form>
{% endif %}
<!-- 检查用户是否有编辑特定文章的权限 -->
{% if article.author == user or user.is_superuser or perms.articles.change_article %}
<a href="{% url 'edit_article' article.pk %}" class="btn btn-primary">编辑</a>
{% endif %}
"""
# 管理权限的视图
from django.contrib.admin.views.decorators import staff_member_required
@staff_member_required
def manage_permissions(request):
"""
管理用户权限的视图(仅限管理员)
"""
users = User.objects.all()
groups = Group.objects.all()
if request.method == 'POST':
# 处理权限管理表单
user_id = request.POST.get('user_id')
group_id = request.POST.get('group_id')
if user_id and group_id:
user = User.objects.get(id=user_id)
group = Group.objects.get(id=group_id)
user.groups.add(group)
return render(request, 'manage_permissions.html', {
'users': users,
'groups': groups
})通过这些权限和装饰器的使用,你可以构建一个灵活且安全的用户权限管理系统,确保用户只能访问他们有权限访问的内容和功能。
小结
Django权限和装饰器提供了强大的访问控制机制:
- ✅ login_required装饰器保护视图访问
- ✅ permission_required装饰器实现细粒度权限控制
- ✅ 用户组机制简化权限管理
- ✅ 自定义权限满足特殊业务需求
- ✅ 对象级权限检查提供更精确的控制
合理使用权限系统能够构建安全可靠的Web应用。
下一篇
我们将学习Django管理后台的使用。