📜  自定义对象级权限 – Django REST 框架

📅  最后修改于: 2022-05-13 01:55:15.779000             🧑  作者: Mango

自定义对象级权限 – Django REST 框架

在本文中,我们将讨论如何在 Django REST Framework 中自定义对象级别权限。要在 Django REST Framework 中自定义权限类,我们应该继承rest_framework.permissions.BasePermission类并实现以下一种或两种方法:

  • .has_permission(self, request, view)
  • .has_object_permission(self, request, view, obj)

如果我们查看 Django REST Framework 中 Browsable API 中提到的机器人模型,我们会注意到,即使在我们的 RESTFul Web 服务中设置了权限策略后,任何经过身份验证的用户也可以删除机器人。自定义对象级权限的重要性在于,只有机器人所有者才能更新或删除现有机器人。

创建自定义权限类

转到包含views.py文件的 robots 文件夹并创建一个名为custompermission.py的新文件。您可以在新文件中编写以下代码。

Python3
from rest_framework import permissions
  
  
class IsCurrentUserOwnerOrReadOnly(permissions.BasePermission):
    
    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            
            # The method is a safe method
            return True
            
        else:
            # The method isn't a safe method
            # Only owners are granted permissions for unsafe methods
            return obj.owner == request.user


Python3
class Robot(models.Model):
    CURRENCY_CHOICES = (
        ('INR', 'Indian Rupee'),
        ('USD', 'US Dollar'),
        ('EUR', 'Euro'),
    )
  
    name = models.CharField(max_length=150, unique=True)
      
    robot_category = models.ForeignKey(
        RobotCategory,
        related_name='robots',
        on_delete=models.CASCADE)
      
    manufacturer = models.ForeignKey(
        Manufacturer,
        related_name='robots',
        on_delete=models.CASCADE)
      
    currency = models.CharField(
        max_length=3,
        choices=CURRENCY_CHOICES,
        default='INR')
      
    price = models.IntegerField()
      
    manufacturing_date = models.DateTimeField()
      
    owner = models.ForeignKey(
        'auth.User',
        related_name='robots',
        on_delete=models.CASCADE
    )
  
    class Meta:
        ordering = ('name',)
  
    def __str__(self):
        return self.name


Python3
class RobotSerializer(serializers.HyperlinkedModelSerializer):
    
    robot_category = serializers.SlugRelatedField(
        queryset=RobotCategory.objects.all(), slug_field='name')
      
    manufacturer = serializers.SlugRelatedField(
        queryset=Manufacturer.objects.all(), slug_field='name')
      
    currency = serializers.ChoiceField(
        choices=Robot.CURRENCY_CHOICES)
      
    currency_name = serializers.CharField(
        source='get_currency_display',
        read_only=True)
  
    # Display the owner's username (read-only)
    owner = serializers.ReadOnlyField(source='owner.username')
  
    class Meta:
        model = Robot
        fields = '__all__'


Python3
class UserRobotSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Robot
        fields = (
            'url',
            'name')
  
class UserSerializer(serializers.HyperlinkedModelSerializer):
    robots = UserRobotSerializer(
        many=True,
        read_only=True)
  
    class Meta:
        model = User
        fields = (
            'url',
            'pk',
            'username',
            'robots')


Python3
class RobotList(generics.ListCreateAPIView):
    
    queryset = Robot.objects.all()
    serializer_class = RobotSerializer
    name = 'robot-list'
  
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)


Python3
class RobotList(generics.ListCreateAPIView):
    permission_classes = (
        permissions.IsAuthenticatedOrReadOnly,
        custompermission.IsCurrentUserOwnerOrReadOnly,
    )
    queryset = Robot.objects.all()
    serializer_class = RobotSerializer
    name = 'robot-list'
  
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)
  
  
class RobotDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (
        permissions.IsAuthenticatedOrReadOnly,
        custompermission.IsCurrentUserOwnerOrReadOnly,
    )
    queryset = Robot.objects.all()
    serializer_class = RobotSerializer
    name = 'robot-detail'


IsCurrentUserOwnerOrReadOnly继承自BasePermission类并覆盖has_object_permission方法。该方法返回一个 bool 值,该值指示是否应授予权限。 has_object_permission区分了安全和不安全的方法,只有所有者才被授予不安全方法的权限。



让我们将 owner 字段添加到 robots/models.py 文件中。

owner = models.ForeignKey(
        'auth.User',
        related_name= 'robots',
        on_delete=models.CASCADE
    )

Robot 类如下所示:

蟒蛇3

class Robot(models.Model):
    CURRENCY_CHOICES = (
        ('INR', 'Indian Rupee'),
        ('USD', 'US Dollar'),
        ('EUR', 'Euro'),
    )
  
    name = models.CharField(max_length=150, unique=True)
      
    robot_category = models.ForeignKey(
        RobotCategory,
        related_name='robots',
        on_delete=models.CASCADE)
      
    manufacturer = models.ForeignKey(
        Manufacturer,
        related_name='robots',
        on_delete=models.CASCADE)
      
    currency = models.CharField(
        max_length=3,
        choices=CURRENCY_CHOICES,
        default='INR')
      
    price = models.IntegerField()
      
    manufacturing_date = models.DateTimeField()
      
    owner = models.ForeignKey(
        'auth.User',
        related_name='robots',
        on_delete=models.CASCADE
    )
  
    class Meta:
        ordering = ('name',)
  
    def __str__(self):
        return self.name

在上面的代码中,我们指定了 models.CASCADE 值,这样每当我们删除用户时,与该用户关联的机器人也将被删除。

现在让我们将 owner 字段添加到 robots/serializers.py 文件中提到的 RobotSerializer 类中。您可以添加以下代码

owner = serializers.ReadOnlyField(source='owner.username')

RobotSerializer 类如下所示:

蟒蛇3

class RobotSerializer(serializers.HyperlinkedModelSerializer):
    
    robot_category = serializers.SlugRelatedField(
        queryset=RobotCategory.objects.all(), slug_field='name')
      
    manufacturer = serializers.SlugRelatedField(
        queryset=Manufacturer.objects.all(), slug_field='name')
      
    currency = serializers.ChoiceField(
        choices=Robot.CURRENCY_CHOICES)
      
    currency_name = serializers.CharField(
        source='get_currency_display',
        read_only=True)
  
    # Display the owner's username (read-only)
    owner = serializers.ReadOnlyField(source='owner.username')
  
    class Meta:
        model = Robot
        fields = '__all__'

让我们创建两个名为 UserRobotSerializer 类和 UserSerializer 类的新序列化器类。您可以添加以下代码:



蟒蛇3

class UserRobotSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Robot
        fields = (
            'url',
            'name')
  
class UserSerializer(serializers.HyperlinkedModelSerializer):
    robots = UserRobotSerializer(
        many=True,
        read_only=True)
  
    class Meta:
        model = User
        fields = (
            'url',
            'pk',
            'username',
            'robots')

UserRobotSerializer 类序列化与用户相关的无人机。这里我们没有使用 RobotSerializer 因为我们只需要序列化较少的字段。 UserSerializer 类将“robots”属性声明为 UserRobotSerializer 类的实例。

接下来,我们需要保存有关发出请求的用户的信息。为此,我们需要覆盖在views.py文件中声明的RobotList类中的perform_create方法。新的 RobotList 类如下所示

蟒蛇3

class RobotList(generics.ListCreateAPIView):
    
    queryset = Robot.objects.all()
    serializer_class = RobotSerializer
    name = 'robot-list'
  
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

perform_create方法使用 serializer.save 方法将所有者信息传递给 create 方法。

在这里,我们向机器人表添加了一个新的所有者字段。您可以执行迁移以反映对数据库的更改。请记住,我们需要为表中现有的机器人分配一个默认所有者。让我们记下现有用户的 id 并在迁移过程中提供它。您可以使用 Django shell 获取 id。分享截图以供参考:

现在让我们进行迁移过程。在这里,Django 将显示以下消息:

现在运行“ Python manage.py migrate ”命令来应用生成的迁移。



设置权限策略

您可以在 settings.py 文件中提及 BasicAuthentication 类。

REST_FRAMEWORK = {

    'DEFAULT_AUTHENTICATION_CLASSES':(
        'rest_framework.authentication.BasicAuthentication',

        )
}

现在,让我们为 RobotList 和 RobotDetail 基于类的视图配置权限策略。您应该导入权限和自定义权限。

from rest_framework import permissions
from robots import custompermission

新代码如下:

蟒蛇3

class RobotList(generics.ListCreateAPIView):
    permission_classes = (
        permissions.IsAuthenticatedOrReadOnly,
        custompermission.IsCurrentUserOwnerOrReadOnly,
    )
    queryset = Robot.objects.all()
    serializer_class = RobotSerializer
    name = 'robot-list'
  
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)
  
  
class RobotDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (
        permissions.IsAuthenticatedOrReadOnly,
        custompermission.IsCurrentUserOwnerOrReadOnly,
    )
    queryset = Robot.objects.all()
    serializer_class = RobotSerializer
    name = 'robot-detail'

发出 HTTP 请求

让我们尝试获取机器人详细信息。由于这是一种安全的方法,我们的自定义权限将在没有任何用户凭据的情况下提供机器人详细信息。 HTTPie 命令如下:

输出如下:

HTTP/1.1 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Language: en
Content-Length: 2116
Content-Type: application/json
Date: Sun, 29 Aug 2021 07:11:39 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Accept-Language
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

[
    {
        "currency": "USD",
        "currency_name": "US Dollar",
        "manufacturer": "Fanuc",
        "manufacturing_date": "2019-10-12T00:00:00Z",
        "name": "FANUC M-710ic/50",
        "owner": "sonu",
        "price": 37000,
        "robot_category": "Articulated Robots",
        "url": "http://localhost:8000/robot/1/"
    },
    {
        "currency": "USD",
        "currency_name": "US Dollar",
        "manufacturer": "ABB",
        "manufacturing_date": "2020-05-10T00:00:00Z",
        "name": "IRB 1100",
        "owner": "sonu",
        "price": 25000,
        "robot_category": "Articulated Robots",
        "url": "http://localhost:8000/robot/7/"
    },
]

现在让我们尝试删除一个机器人。根据自定义权限等级,只有机器人所有者才能进行删除操作。让我们尝试通过提供超级用户凭据来删除机器人。 HTTPie 命令如下:

输出:



让我们通过提供所有者凭据来尝试删除操作。命令如下:

输出:

您可以注意到机器人已成功从数据库中删除。