即使我为模型声明了主键,Django 也会在单元测试期间自动添加 ID 字段

问题描述 投票:0回答:1

我有一个包含多个表的 django 项目,负责为所述 django 项目的 api 编写单元测试。其中一个表具有不同表的外键,但是当我尝试在单元测试中创建模型对象时,在成功查询外键引用的模型对象后,尽管能够将数据发布到此表,但我收到以下 IntegrityError服务器运行时的表:

Traceback (most recent call last):
  File "C:\Python312\Lib\site-packages\django\db\backends\utils.py", line 105, in _execute
    return self.cursor.execute(sql, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python312\Lib\site-packages\django\db\backends\sqlite3\base.py", line 328, in execute
    return super().execute(query, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
sqlite3.IntegrityError: NOT NULL constraint failed: app_examplemodel.location_id

The above exception was the direct cause of the following exception:

...

Traceback (most recent call last):
  File "~\tests\test_example_model.py", line 19, in test_post_example_model_valid       
    response = self.client.post(reverse("examplemodelurl"), EXAMPLE_MODEL_DICT, content_type="application/json")

django.db.utils.IntegrityError: NOT NULL constraint failed: app_examplemodel.location_id

这是单元测试(在我有机会断言任何内容之前它会抛出错误)

class TestPostExampleModel(TestCase):
    def test_post_example_model_valid(self):
        self.client.post(reverse("locations"), {"name": "location name"}, content_type="application/json")
        response = self.client.post(reverse("examplemodelurl"), {"name": "example name", "location": "location name"}, content_type="application/json")

主要令人困惑的事情是,在 django 官方网站上,它对模型自动字段说了以下内容:

如果您想指定自定义主键,请在其中一个字段上指定primary_key=True。如果 Django 发现您已显式设置 Field.primary_key,它不会添加自动 id 列。

以下是相关模型定义

from django.db import models

class Location(models.Model):
    name = models.CharField(primary_key=True, max_length=64, unique=True)
    location_type = models.CharField(default='other', max_length=10)
    address = models.CharField(max_length=128, blank=True)

    def __str__(self) -> str:
        return f'{self.name}'

class BaseExampleModel(models.Model):
    name = models.CharField(max_length=63, primary_key=True, unique=True)

    def __str__(self):
        return self.name
    
    class Meta:
        abstract = True

ExampleModel(BaseExampleModel):
    location = models.ForeignKey(Location, on_delete=models.PROTECT)

这是我用来验证这些模型的序列化器:

from rest_framework import serializers
from app.models import *

class LocationSerializer(serializers.ModelSerializer):
    class Meta:
        model = Location
        fields = "__all__"

class ExampleModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = ExampleModel
        fields = "__all__"
        depth = 1

如果 django 文档所说的是正确的,则不应为位置模型创建 ID 字段,但是在获取位置对象的查询完成后,NOT NULL 约束在 app_examplemodel.location_id 上失败。如果我将

null=True
放入外键定义中,错误就会消失,但不会允许该值为空,而是最终总是为空。我在这个项目中还有其他带有外键的模型,它们的行为符合预期。

我的猜测是,这与 examplemodel 作为抽象模型的子级有关,但这并不能解释为什么我可以在服务器运行时成功地将数据发布到该模型,但不能在单元测试期间成功。我的理智正在慢慢耗尽,我一直找不到任何论坛帖子或文档来解释为什么会发生这种情况,我现在真的很想用我的时间做其他事情。

请发送帮助

django sqlite django-models django-rest-framework
1个回答
0
投票

给出以下代码:

ExampleModel(BaseExampleModel):
    location = models.ForeignKey(Location, on_delete=models.PROTECT)

它要求您在调用帖子时为

ForeignKey
对象指定实际的
Location

location_id
是 Django 在 Django 模型对象中创建的字段。它保存对您已分配的
id
对象的实际
Location
的引用。

创建

ExampleModel
对象的正确方法如下:

location = Location.objects.create(name='foo', adress='foo')
example_model = ExampleModel.objects.create(name='bar', location=location)

在你的测试中看起来像这样

class TestPostExampleModel(TestCase):
    def test_post_example_model_valid(self):

        # You need to have a location object to create an examplemodel
        location = Location.objects.create(name='foo', adress='foo')
  
        # Here you see "location": location.id
        response = self.client.post(reverse("examplemodelurl"), {"name": "example name", "location": location.id}, content_type="application/json")

当您设置

null=True
时它会起作用,因为
null=True
允许
location
为空。您仍然可以通过为其分配位置对象并在
.save()
对象上调用
ExampleModel
来编辑位置。

希望这对您有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.