Skip to content

第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权限和装饰器提供了强大的访问控制机制:

  1. ✅ login_required装饰器保护视图访问
  2. ✅ permission_required装饰器实现细粒度权限控制
  3. ✅ 用户组机制简化权限管理
  4. ✅ 自定义权限满足特殊业务需求
  5. ✅ 对象级权限检查提供更精确的控制

合理使用权限系统能够构建安全可靠的Web应用。

下一篇

我们将学习Django管理后台的使用。

10.1 Admin基础 →

目录

返回课程目录

Released under the Apache 2.0 License.