问题描述
我有一个存储父-子记录的表。今天,我在编写单元测试时注意到一个问题。即使请求者不是孩子的父母,他们也可以删除记录。但是在视图方面,如果用户不是记录的所有者,则删除按钮是不活动的。所以它在视图端工作。如果我使用邮递员提出请求,则无论谁拥有该记录都会被删除。
如何检查用户是否是记录的所有者?我正在关注一个示例项目,该项目的创建者已经获得了类似的许可。
子列表模型
class ChildList(models.Model):
parent = models.ForeignKey(
"account.ParentProfile",on_delete=models.CASCADE,related_name="parent_children",verbose_name=AccountStrings.ChildListString.parent_verbose_name)
child = models.OnetoOneField(
"account.ChildProfile",related_name="child_list",verbose_name=AccountStrings.ChildListString.child_verbose_name)
def __str__(self):
return f"{self.parent.user.first_name} {self.parent.user.last_name} - {self.child.user.first_name} {self.child.user.last_name}"
class Meta:
verbose_name = AccountStrings.ChildListString.Meta_verbose_name
verbose_name_plural = AccountStrings.ChildListString.Meta_verbose_name_plural
子列表销毁视图
class ChildListItemDestroyAPIView(RetrieveDestroyAPIView):
"""
Returns a destroy view by child id value.
"""
queryset = ChildList.objects.all()
serializer_class = ChildListSerializer
permission_classes = [IsAuthenticated,IsParent,IsOwnChild]
def get_object(self):
queryset = self.get_queryset()
obj = get_object_or_404(ChildList,child = self.kwargs["child_id"])
return obj
权限
class IsOwnChild(BasePermission):
"""
To edit or destroy a child list record,the user must be owner of that record.
"""
def has_permission(self,request,view):
return request.user.user_parent and request.user.is_authenticated
def has_object_permission(self,view,obj):
return (obj.parent == request.user.user_parent) or request.user.is_superuser
message = AccountStrings.PermissionStrings.is_own_child_message
单元测试
class ChildListItemDestroyTests(APITestCase):
login_url = reverse("token_obtain_pair")
def setUp(self):
self.username = "johndoe"
self.password = "test1234"
self.user_parent = User.objects.create_user(username=self.username,password=self.password,email = "email1@example.com",identity_number = "12345678910",user_type = 3)
self.user_parent2 = User.objects.create_user(username= "davedoe",email = "email3@example.com",identity_number = "12345678912",user_type = 3)
self.user_child = User.objects.create_user(username="janedoe",email = "email2@example.com",identity_number = "12345678911",user_type = 2)
self.child_list_item = ChildList.objects.create(parent = self.user_parent.user_parent,child = self.user_child.user_child)
self.test_jwt_authentication()
def test_jwt_authentication(self,username="johndoe",password="test1234"):
response = self.client.post(self.login_url,data={"username": username,"password": password})
self.assertEqual(200,response.status_code)
self.assertTrue("access" in json.loads(response.content))
self.token = response.data["access"]
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + self.token)
def test_child_list_item_delete(self):
url = reverse("account:child_list_item_destroy",kwargs={"child_id": self.user_child.user_child.user_id})
response = self.client.delete(url)
self.assertEqual(204,response.status_code)
def test_child_list_item_delete_is_own_child(self):
self.test_jwt_authentication(username = "davedoe")
url = reverse("account:child_list_item_destroy",kwargs={"child_id": self.user_child.user_child.user_id})
response = self.client.delete(url)
self.assertEqual(403,response.status_code)
解决方法
我认为这是因为您覆盖了 get_object
方法。在 get_object
类的 GenericApiView
方法中,有以下内容:
# May raise a permission denied
self.check_object_permissions(self.request,obj)
所以您添加了新权限,但删除了权限检查机制。添加回这一行应该可以解决您的问题。
,如何检查用户是否是记录的所有者? 您的 ChildList 类有一个指向配置文件的 pk。所以这里记录的onwer 是account.ParentProfile。你在 test_child_list_item_delete_is_own_child 中得到 203 而不是 403 的原因
是因为在视图 permission_classes 中您传递的是 IsAuthenticated,这意味着任何具有有效访问令牌的用户都可以与此视图交互。
因此解决方案是自定义删除并获得这样的响应
class ChildListItemDestroyAPIView(RetrieveDestroyAPIView):
"""
Returns a destroy view by child id value.
"""
queryset = ChildList.objects.all()
serializer_class = ChildListSerializer
permission_classes = [IsAuthenticated,IsParent,IsOwnChild]
def get_object(self):
queryset = self.get_queryset()
obj = get_object_or_404(ChildList,child = self.kwargs["child_id"])
return obj
def delete(self,*args,**kwargs):
pass
# business logic goes here
def get(self,**kwargs):
pass
# business logic goes here