Django:如何限制未授权用户访问CreateView创建里程碑页面
问题描述
需要限制未授权用户访问添加新里程碑的页面,仅允许项目所有者和项目经理(存储在Project的外键与多对多字段中)访问,其他用户应看到访问拒绝提示。当前使用CreateView的get_queryset方法无法实现拦截,因为该方法主要用于查询现有数据,不影响创建操作的权限校验流程。
现有代码
models.py
class Project(models.Model): owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="projects_as_owner", null=True) project_managers = models.ManyToManyField(User, related_name="projects_as_pm", blank=True) name = models.CharField(max_length=200) description = models.TextField(blank=True) date_of_insert = models.DateTimeField(auto_now_add=True) def __str__(self): return self.name class Milestone(models.Model): project_fk = models.ForeignKey(Project, related_name="milestones", on_delete=models.CASCADE) name = models.CharField(max_length=200) description = models.TextField(blank=True) date_of_insert = models.DateTimeField(auto_now_add=True) def __str__(self): return self.name
views.py(存在问题的类)
class NewMilestone(LoginRequiredMixin, generic.CreateView): model = Milestone fields = ['name', 'description'] lookup_url_kwarg = 'p_id' template_name = 'main/new_milestone.html' # ... 两个可正常工作的函数,此处省略 ... def get_queryset(self): qs = super(NewMilestone, self).get_queryset() project = Project.objects.get(id=self.kwargs['p_id']) if(qs.filter(project_fk__owner=self.request.user).exists() or User.objects.filter(id=self.request.user.id).filter(projects_as_pm__id=project.id).exists()): return qs else: return Http404("You are not authenticated to this action")
解决方案
CreateView的get_queryset方法不适合做创建操作的权限校验,它仅负责返回用于展示的查询集,无法拦截创建请求。可以通过以下两种方式实现需求:
方法一:重写dispatch方法
dispatch是视图处理请求的入口方法,在此处做权限校验可直接拦截所有未授权请求:
from django.http import HttpResponseForbidden class NewMilestone(LoginRequiredMixin, generic.CreateView): model = Milestone fields = ['name', 'description'] lookup_url_kwarg = 'p_id' template_name = 'main/new_milestone.html' def dispatch(self, request, *args, **kwargs): # 获取当前请求对应的项目 project = Project.objects.get(id=self.kwargs['p_id']) # 校验用户身份:项目所有者或项目经理 is_owner = project.owner == request.user is_pm = request.user in project.project_managers.all() if not (is_owner or is_pm): return HttpResponseForbidden("您没有权限执行此操作") return super().dispatch(request, *args, **kwargs) # 重写form_valid,将里程碑关联到目标项目 def form_valid(self, form): project = Project.objects.get(id=self.kwargs['p_id']) form.instance.project_fk = project return super().form_valid(form)
方法二:自定义权限Mixin(复用场景)
如果需要在多个视图中复用该权限逻辑,可以自定义权限Mixin:
from django.http import HttpResponseForbidden class ProjectPermissionMixin: def has_permission(self): project = Project.objects.get(id=self.kwargs['p_id']) return project.owner == self.request.user or self.request.user in project.project_managers.all() def dispatch(self, request, *args, **kwargs): if not self.has_permission(): return HttpResponseForbidden("您没有权限执行此操作") return super().dispatch(request, *args, **kwargs) # 视图继承自定义Mixin class NewMilestone(LoginRequiredMixin, ProjectPermissionMixin, generic.CreateView): model = Milestone fields = ['name', 'description'] lookup_url_kwarg = 'p_id' template_name = 'main/new_milestone.html' def form_valid(self, form): project = Project.objects.get(id=self.kwargs['p_id']) form.instance.project_fk = project return super().form_valid(form)
注意事项
- 必须重写
form_valid方法,将创建的里程碑关联到目标项目,否则会因project_fk字段为空触发数据库错误。 - 可根据需求将
HttpResponseForbidden替换为自定义403页面,比如使用render返回自定义模板。
内容的提问来源于stack exchange,提问作者Komnatow
相关产品推荐
相关产品推荐

