Django - 在设置外键对象确实存在的 ManyToMany 属性时,序列化程序抛出 "Invalid pk - object does not exist"

Django - Serializer throwing "Invalid pk - object does not exist" when setting ManyToMany attribute where foreign keyed object does exist

所以下面我有一些代码来测试某人创建 post 并且 post 有一个 hash_tag 的功能,在这种情况下是“#video”。该代码采用 Post body 并使用正则表达式查找以“#”开头的任何单词。如果是,则它从 HashTag table 创建或获取 HashTag。然后将 HashTag 的列表设置为 Post.

下的 hash_tags 属性

出于某种原因,CreatePostSerializer 序列化程序抛出了一个没有意义的异常。序列化程序抛出异常 ValidationError({'hash_tags': [ErrorDetail(string='Invalid pk "[\'video\']" - object does not exist.', code='does_not_exist')]})。这没有意义的原因是因为当我调试并在 views.py 下的 except Exception as e 之后设置断点时,这就是我得到的

>>>e
ValidationError({'hash_tags': [ErrorDetail(string='Invalid pk "[\'video\']" - object does not exist.', code='does_not_exist')]})
>>>HashTag.objects.get(pk='video')
<HashTag: HashTag object (video)>
>>>request.data['hash_tags']
['video']

所以 >>> 代表我输入调试器的内容。我基本上停在 return Response... 行,我们可以看到 e 是我提到的 ValidationError,但我们可以看到它声称不存在的对象确实存在。 为什么序列化程序抛出“ValidationError - 对象不存在”?

注意: 我有另一个测试做完全相同的事情并通过,除了没有视频文件被传递这让我相信 Django 在这种情况下做了不同的事情传入的正文是 multi-part。我还在实例中尝试了只有一个散列标签来设置 hash_tags= 而不是列表并且它有效。这是一个 hack,更干净的解决方案是首选。

helpers.py

import re

def extract_hashtags(text):
    regex = "#(\w+)"
    return re.findall(regex, text)

test.py

def test_real_image_upload_w_hash_tag(self):
    image_file = retrieve_test_image_upload_file()
    hash_tag = 'video'
    response = self.client.post(reverse('post'),
                                data={'body': f'Some text and an image #{hash_tag}',
                                      'images': [image_file]},
                                **{'HTTP_AUTHORIZATION': f'bearer {self.access_token}'})
    self.assertEqual(response.status_code, status.HTTP_201_CREATED)

views.py

def set_request_data_for_post(request, user_uuid: str):
    request.data['creator'] = user_uuid
    post_text = request.data['body']
    hash_tags_list = extract_hashtags(post_text)
    hash_tags = [HashTag.objects.get_or_create(hash_tag=ht)[0].hash_tag for ht in hash_tags_list]

    if len(hash_tags) > 0:
        request.data['hash_tags'] = hash_tags

    return request

def create_post(request):
    user_uuid = str(request.user.uuid)
    request = set_request_data_for_post(request=request, user_uuid=user_uuid)

    try:
        serializer = CreatePostSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            post_obj = serializer.save()
    except Exception as e:
        return Response(dict(error=str(e),
                             user_message=error_message_generic),
                        status=status.HTTP_400_BAD_REQUEST)

    return Response(serializer.data, status=status.HTTP_201_CREATED)

serializer.py

from rest_framework import serializers
from cheers.models import Post

class CreatePostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ('creator', 'body', 'uuid', 'created', 'updated_at', 'hash_tags')

model.py

class Post(models.Model):
    # ulid does ordered uuid creation
    uuid = models.UUIDField(primary_key=True, default=generate_ulid_as_uuid, editable=False)
    created = models.DateTimeField('Created at', auto_now_add=True)
    updated_at = models.DateTimeField('Last updated at', auto_now=True, blank=True, null=True)
    creator = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name="post_creator")
    body = models.CharField(max_length=POST_MAX_LEN, validators=[MinLengthValidator(POST_MIN_LEN)])
    hash_tags = models.ManyToManyField(HashTag, blank=True)

class HashTag(models.Model):
    hash_tag = models.CharField(max_length=HASH_TAG_MAX_LEN, primary_key=True, validators=[
        MinLengthValidator(HASH_TAG_MIN_LEN)])

在您的 test/__init__.py 下,您必须添加这些行

from django.db.backends.postgresql.features import DatabaseFeatures

DatabaseFeatures.can_defer_constraint_checks = False

有一些奇怪的内部错误,如果你在一个 table 上操作很多,有很多不同的 TestCase 类 然后它会在结束后进行数据库检查它已被拆除,它会导致错误。

我也在使用 factory boy (https://factoryboy.readthedocs.io/en/stable/orms.html) 来生成我的测试数据库,这是出现此问题的主要原因。我相信这是因为我只使用 <model>.objects.create() 就关闭了 factory boy 并且我的测试不再失败。