You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django REST Framework用户权限问题:仅允许访问自身创建内容

问题描述

我正在用REST API开发Web应用,现有4个用户(其中3个是农民用户),想要实现权限控制,让每个用户仅能查看和删除自己添加到后端的内容,但目前没能成功实现。以下是我的代码:

models.py

User = get_user_model()

# Create your models here.

class Tender(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=255)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    description = models.TextField()
    date_due = models.DateField(default=datetime.date.today)
    location = models.CharField(max_length=255, null=False)
    contact = models.PositiveIntegerField(null=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

class Input(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=255)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    description = models.TextField()
    price = models.DecimalField(max_digits=10, decimal_places=2)
    quantity = models.DecimalField(max_digits=10, decimal_places=2)
    contact = models.PositiveIntegerField(null=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

class Investor(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=255, unique=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    description = models.TextField()
    location = models.CharField(max_length=255, null=False)
    contact = models.PositiveIntegerField(null=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name 

permissions.py

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        if request.user == obj.author:
            return True
        return False

class IsOwnerOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
    
        if request.user == obj.user :
            return True
        return False

views.py

from requests import request
from rest_framework.viewsets import ModelViewSet
from rest_framework import viewsets, status, permissions
from django.http import Http404, JsonResponse
from rest_framework.views import Response
from .models import Tender, Input , Investor
from .serializers import TenderViewSerializer, InputViewSerializer, InvestViewSerializer
from user.models import User
from user.permissions import IsOwnerOnly, IsOwnerOrReadOnly


# Create your views here.

class TenderViewSet(viewsets.ModelViewSet):
    serializer_class = TenderViewSerializer
    permission_classes = (permissions.IsAuthenticated,)

    def get_queryset(self):
        if self.request.user.is_tender_holder:
            queryset = Tender.objects.all()
            return  queryset
        else:
            return Response({"NO_ACCESS": "Access Denied"}, status=401)
    
    def create(self, request, *args, **kwargs):
        if self.request.user.is_tender_holder:
            new_tender = Tender.objects.create(
                name=request.data["name"],
                description=request.data["description"],
                date_due=request.data["date_due"],
                location=request.data["location"],
                contact=request.data['contact']
                )
            
            new_tender.save()
            
            serializer = TenderViewSerializer(new_tender)

            return Response(serializer.data)
        else:
            return Response({"NO_ACCESS": "Access Denied"}, status=401)

    def destroy(self, request, *args, **kwargs):
        if self.request.user.is_tender_holder:
            try:
                instance =self.get_object()
                self.perform_destroy(instance)
            except Http404:
                pass

            return Response({"message": "Tender deleted successfully"})
        else:
            return Response({"NO_ACCESS": "Access Denied"}, status=401)

    # def update(self, request, *args, **kwargs):
  



class InvestorViewSet(ModelViewSet):
    serializer_class = InvestViewSerializer
    permission_classes = (permissions.IsAuthenticated,)
    #queryset = Investor.objects.all()

    def get_queryset(self):
       if self.request.user.is_investor:
            queryset = Investor.objects.all()
            return  queryset
       else: 
            return Response({"NO_ACCESS": "Access Denied"}, status=401)

    def create(self, request, *args, **kwargs):
        if self.request.user.is_investor:
            new_investment = Investor.objects.create(
                name=request.data["name"],
                description=request.data["description"],
                location=request.data["location"],
                contact=request.data['contact']
               )
        
            new_investment.save()
        
            serializer = InvestViewSerializer(new_investment)

            return Response(serializer.data)
        else:
            return Response({"NO_ACCESS": "Access Denied"}, status=401)

    def destroy(self, request, *args, **kwargs):
        if self.request.user.is_investor:
            try:
                instance =self.get_object()
                self.perform_destroy(instance)
            except Http404:
                pass

            return Response({"message": "Investment deleted successfully"})
        else:
            return Response({"NO_ACCESS": "Access Denied"}, status=401)



class InputViewSet(ModelViewSet):
    serializer_class = InputViewSerializer
    permission_classes = (permissions.IsAuthenticated, IsOwnerOrReadOnly)
    #queryset = Input.objects.all()

    def get_queryset(self, **kwargs):
        if self.request.user.is_input_holder or self.request.user.is_superuser:
            user = self.request.user
            return  Input.objects.filter(id=user.id)
        else:
            return Response({"NO_ACCESS": "Access Denied"}, status=401)



    def create(self, request, *args, **kwargs):
        if self.request.user.is_input_holder or self.request.user.is_superuser:
            new_input = Input.objects.create(
                name=request.data["name"],
                description=request.data["description"],
                price=request.data["price"],
                quantity=request.data["quantity"],
                contact=request.data['contact']
              )
        
            new_input.save()
        
            serializer = InputViewSerializer(new_input)

            return Response(serializer.data)
        else:
            return Response({"NO_ACCESS": "Access Denied"}, status=401)


    def destroy(self, request, *args, **kwargs):
        if self.request.user.is_input_holder or self.request.user.is_superuser:
            try:
                instance =self.get_object()
                self.perform_destroy(instance)
            except Http404:
                pass

            return Response({"message": "Input deleted successfully"})
        else:
            return Response({"NO_ACCESS": "Access Denied"}, status=401)

问题排查与修正方案

核心问题点

  1. get_queryset返回类型错误:该方法要求返回QuerySet对象,但你在else分支返回了Response,会直接抛出异常,正确做法是返回空QuerySet或抛出权限异常。
  2. 创建内容未关联作者:创建Tender、Investor、Input时,没有将当前登录用户赋值给author字段,导致后续无法通过author判断内容归属。
  3. 权限类未正确应用:TenderViewSet和InvestorViewSet未添加IsOwnerOrReadOnly,导致同类型用户可以操作所有内容。
  4. Input查询条件错误:Input.objects.filter(id=user.id)是用Input的ID匹配用户ID,逻辑完全错误,应该用author=request.user过滤用户自己的内容。
  5. 销毁方法跳过权限校验:自定义的destroy方法没有校验用户是否为内容作者,导致同类型用户可删除他人内容。
  6. IsOwnerOnly类字段错误:模型中关联用户的字段是author,但代码里写的是obj.user,字段名不匹配。

修正后的代码

permissions.py

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        # 安全方法仅允许查看自己的内容
        if request.method in permissions.SAFE_METHODS:
            return request.user == obj.author
        # 写入/删除仅允许作者本人
        return request.user == obj.author

class IsOwnerOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        return request.user == obj.author

views.py

from rest_framework.viewsets import ModelViewSet
from rest_framework import permissions, status
from rest_framework.response import Response
from .models import Tender, Input, Investor
from .serializers import TenderViewSerializer, InputViewSerializer, InvestViewSerializer
from user.permissions import IsOwnerOrReadOnly


class TenderViewSet(ModelViewSet):
    serializer_class = TenderViewSerializer
    permission_classes = (permissions.IsAuthenticated, IsOwnerOrReadOnly)

    def get_queryset(self):
        # 仅返回当前用户创建的Tender
        return Tender.objects.filter(author=self.request.user)
    
    def create(self, request, *args, **kwargs):
        if not request.user.is_tender_holder:
            return Response({"NO_ACCESS": "Access Denied"}, status=status.HTTP_401_UNAUTHORIZED)
        
        # 关联当前用户为作者
        request.data['author'] = request.user.id
        return super().create(request, *args, **kwargs)


class InvestorViewSet(ModelViewSet):
    serializer_class = InvestViewSerializer
    permission_classes = (permissions.IsAuthenticated, IsOwnerOrReadOnly)

    def get_queryset(self):
        # 仅返回当前用户创建的Investor
        return Investor.objects.filter(author=self.request.user)
    
    def create(self, request, *args, **kwargs):
        if not request.user.is_investor:
            return Response({"NO_ACCESS": "Access Denied"}, status=status.HTTP_401_UNAUTHORIZED)
        
        request.data['author'] = request.user.id
        return super().create(request, *args, **kwargs)


class InputViewSet(ModelViewSet):
    serializer_class = InputViewSerializer
    permission_classes = (permissions.IsAuthenticated, IsOwnerOrReadOnly)

    def get_queryset(self):
        # 仅返回当前用户创建的Input
        return Input.objects.filter(author=self.request.user)
    
    def create(self, request, *args, **kwargs):
        if not (request.user.is_input_holder or request.user.is_superuser):
            return Response({"NO_ACCESS": "Access Denied"}, status=status.HTTP_401_UNAUTHORIZED)
        
        request.data['author'] = request.user.id
        return super().create(request, *args, **kwargs)

关键优化说明

  1. 简化create逻辑:不再手动创建对象,通过父类super().create()处理,同时将当前用户ID传入请求数据,让Serializer自动关联作者。
  2. 精准过滤查询集:所有ViewSet的get_queryset仅返回当前用户创建的内容,确保用户只能看到自己的数据。
  3. 权限类全局生效:所有需要权限控制的ViewSet都添加IsOwnerOrReadOnly,确保只有作者能修改/删除内容。
  4. 移除自定义destroy:ModelViewSet默认的destroy方法会自动调用权限类校验,无需自定义,避免跳过权限检查。

内容的提问来源于stack exchange,提问作者Tush

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 12:20:25