自定义对象级权限 – Django REST框架
在这篇文章中,我们将讨论如何在Django REST框架中自定义对象级权限。要在Django REST框架中定制权限类,我们应该继承rest_framework.permissions.BasePermission类,并实现以下任一或两个方法。
.has_permission(self, request, view)
.has_object_permission(self, request, view, obj)
如果我们看一下Django REST框架中的Browsable API中提到的机器人模型,我们可以注意到,即使在我们的RESTFul网络服务中设置了权限策略,任何认证的用户都可以删除机器人。这里就涉及到了定制对象级权限的重要性,以便只有机器人所有者可以更新或删除现有机器人。
创建自定义权限类
进入有views.py文件的robots文件夹,创建一个名为custompermission.py的新文件。你可以在这个新文件中写下下面的代码。
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
IsCurrentUserOwnerOrReadOnly_继承自BasePermission类,并重写了has_object_permission方法。该方法返回一个bool值,表示该权限是否应该被授予。has_object_permission区分了安全和不安全的方法,只有所有者被授予不安全方法的权限。
让我们在 robots/models.py 文件中添加所有者字段。
owner = models.ForeignKey(
'auth.User',
related_name= 'robots',
on_delete=models.CASCADE
)
机器人类看起来如下。
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值,这样,每当我们删除一个用户,与这个用户相关的机器人也会被删除。
现在让我们把所有者字段添加到robots/serializers.py文件中提到的RobotSerializer类中。你可以添加下面的代码
owner = serializers.ReadOnlyField(source='owner.username')
RobotSerializer类看起来如下。
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类。你可以添加下面提到的代码。
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类声明了一个’机器人’属性,作为UserRobotSerializer类的一个实例。
接下来,我们需要保存发出请求的用户的信息。为了达到这个目的,我们需要在views.py文件中声明的RobotList类中重写perform_create方法。新的RobotList类看起来如下
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方法将所有者信息传递给创建方法。
在这里,我们在机器人表中添加了一个新的所有者字段。你可以执行迁移来反映数据库的变化。记住,我们需要给表中现有的机器人分配一个默认的所有者。让我们注意到一个现有用户的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
新的代码如下。
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 :8000/robot/
输出结果如下。
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命令如下。
http -a “admin”:”admin@123″ DELETE :8000/robot/1/
输出:
让我们试试通过提供所有者凭证来进行删除操作。该命令如下。
http -a “sonu”:”sn@pswrd” DELETE :8000/robot/1/
输出:
你可以注意到,该机器人已被成功地从数据库中删除。