django 使用DRF中的PrimaryKeyRelatedField删除多对多关系

shstlldc  于 2022-12-20  发布在  Go
关注(0)|答案(2)|浏览(175)

我有两个这样的模型:

Tags(models.Model):
    key = models.CharField(unique=True, max_length=50)
    value = models.TextField()

Note(models.Model):
    key = models.CharField(unique=True, max_length=50)
    tags = models.ManyToManyField(Tag)

标签用于分类笔记,就像这里的StackOverflow问题一样。当客户端创建/更新笔记时,我希望他们能够发送一个标签ID列表,以便将它们关联起来。这样做效果很好。当他们获取笔记时,我希望完整的标签,而不仅仅是一个ID列表。所以我子类化了PrimaryKeyRelatedField并覆盖了to_representation

class RelatedTagsField(PrimaryKeyRelatedField):
    def to_representation(self, value):
        return {
            'id': value.id,
            'key': value.key,
            'value': value.value
        }

class NoteSerializer(ModelSerializer):
    tags = RelatedTagsField(queryset=Tag.objects.all(),
                            many=True,
                            required=False,
                            allow_empty=True)

    class Meta:
        model = Note
        fields = '__all__'

这在创建笔记和通过发送一个包含列表中的标签ID的PATCH来添加标签时很有效,但在发送一个空列表来取消笔记标签时就不起作用了。DRF根本不做任何事情(QueryDict为空,validated_data也是空)。
下面是一个单元测试来说明这个问题:

class TaggedNoteTestCase(APITestCase):
    # setUpTestData omitted
    def test_update(self):
        # create a tag
        response = self.client.post(
            f'/api/tags/',
            {
                'key': 'mytag',
                'value': 'bla'
            }
        )
        self.assertEqual(response.status_code, 201)
        tag_id = response.data['id']
        response = self.client.get('/api/tags/')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data), num_tags + 1)

        # create a note with the tag
        response = self.client.post(
            '/api/notes/',
            {
                'key': 'testkey',
                'value': 'blabla',
                'tags': [tag_id]
            })
        self.assertEqual(response.status_code, 201)
        self.assertGreater(len(response.data), 0)
        note_id = response.data['id']

        # check tag
        response = self.client.get(f'/api/notes/{note_id}/')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data['tags'][0]['id'], tag_id)

        # create another tag
        response = self.client.post(
            f'/api/tags/',
            {
                'key': 'mytag2',
                'value': 'bla'
            }
        )
        self.assertEqual(response.status_code, 201)
        self.assertGreater(len(response.data), 0)
        tag2_id = response.data['id']

        # update note to add 2nd tag
        response = self.client.patch(
            f'/api/notes/{note_id}/',
            {
                'tags': [tag_id, tag2_id]
            }
        )
        self.assertEqual(response.status_code, 200)
        self.assertGreater(len(response.data), 0)
        self.assertEqual(len(response.data['tags']), 2)

        # remove tags
        response = self.client.patch(
            f'/api/notes/{note_id}/',
            data={
                'tags': []
            }
        )
        self.assertEqual(response.status_code, 200)
        self.assertGreater(len(response.data), 0)
        self.assertEqual(len(response.data['tags']), 0)
        # ^^^^^^^^^ this fails, number of tags is still 2!

我希望DRF使用ID列表来设置与便笺关联的标记。这在使用POST创建带有标记的便笺时有效,在通过发送带有PATCH的ID列表来添加或删除现有便笺的标记关联时也有效。但是,当我想通过发送带有PATCH的空标记ID列表来删除所有标记关联时,这不起作用。
我做错了什么?为什么我可以使用ID列表来设置ManyToMany关系,以添加和更改关系,但不能通过传递一个空列表来完全删除它们?

zzzyeukh

zzzyeukh1#

您的问题解决了,但是我想补充一下,当通过python请求发送请求时,要解决一个类似的问题

response = requests.patch(url="http://exmpl/smth/", data={"subscribers": []})

如果以这种方式发送请求并且遇到了此问题,请将data参数替换为json参数

response = requests.patch(url="http://exmpl/smth/", json={"subscribers": []})
bqf10yzr

bqf10yzr2#

解决方案是在API测试客户端设置format='json',感谢Håken Lid解决了这个问题。

相关问题