Django 抽象模型 + DB 迁移:测试抛出“无法更改表,因为它有待处理的触发事件”

Posted

技术标签:

【中文标题】Django 抽象模型 + DB 迁移:测试抛出“无法更改表,因为它有待处理的触发事件”【英文标题】:Django abstract model + DB migrations: tests throw "cannot ALTER TABLE because it has pending trigger events" 【发布时间】:2018-11-06 06:14:57 【问题描述】:

我想编写一个抽象模型 mixin,我可以用它来建立 OneToOne - 与用户模型的关系。这是我的代码:

from django.conf import settings
from django.db import models


class Userable(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE
    )

    class Meta:
        abstract = True

我为此模型编写了以下测试:

class TestUserable(TestCase):

    mixin = Userable

    def setUp(self):
        user = User.objects.create_user(
            email="testuser@test.com",
            name="Test User",
            password="test1234test"
        )
        self.user = user
        self.model = ModelBase(
            '__TestModel__' + self.mixin.__name__, (self.mixin,),
            '__module__': self.mixin.__module__
        )

        with connection.schema_editor() as schema_editor:
            schema_editor.create_model(self.model)

    def test_user(self):
        self.model.objects.create(user=self.user)
        self.assertEqual(self.model.objects.count(), 1)

    def tearDown(self):
        with connection.schema_editor() as schema_editor:
            schema_editor.delete_model(self.model)

我的问题是,tearDown() 方法中的这个测试会引发以下错误:

django.db.utils.OperationalError: cannot DROP TABLE "core___testmodel__userable" because it has pending trigger events

这可能是什么原因?我确实运行了 python manage.py makemigrationspython manage.py migrate,但没有待处理的迁移(正如预期的那样,因为这是一个抽象模型)。

编辑:它似乎与 OneToOneFields 或 ForeignKeys(关系)有关。如果我将此代码用于常规字段(如 CharFields 或 IntegerFields),则它可以工作。

EDIT2:如果您有另一种更好的方法来测试使用 ForeignKeys 的抽象基础模型,请告诉我!

【问题讨论】:

如果你没有在setUp..中重新分配self.model,那会不会那么混乱。 那是什么意思?第一个模型设置为Userable,然后设置为self.model?我可以用 mixin 或其他东西替换第一个 model=Userable :) mixin 会更清楚.. 我编辑了,谢谢你的建议。你知道如何解决这个问题吗? 很遗憾没有,不过我也对答案感兴趣.. 【参考方案1】:

测试抽象模型的常见做法是为测试创建实际模型

这是model-utils项目https://github.com/jazzband/django-model-utils/blob/master/tests/test_models/test_timestamped_model.py中的示例

from tests.models import UserableTest

class TestUserable(TestCase):
    def setUp(self):
        user = User.objects.create_user(
            email="testuser@test.com",
            name="Test User",
            password="test1234test"
        )
        self.user = user

    def test_user(self):
        UserableTest.objects.create(user=self.user)
        self.assertEqual(UserableTest.objects.count(), 1)

在这个项目中他们有单独的设置DJANGO_SETTINGS_MODULE = tests.settingshttps://github.com/jazzband/django-model-utils/blob/master/tests/settings.py

INSTALLED_APPS = (
    'model_utils',
    'tests',
)
DATABASES = 
    'default': 
        'ENGINE': 'django.db.backends.sqlite3'
    

SECRET_KEY = 'dummy'

并且模型在https://github.com/jazzband/django-model-utils/blob/master/tests/models.py中有描述

from myapp.models import Userable

class UserableTest(Userable):
    pass

【讨论】:

我试过这个解决方案。我觉得这可以工作。这是我所做的:我在 core/tests 中创建了一个 testapp。所以核心/测试/测试应用程序。然后我添加了一个从 base.py 和 local.py 继承的 settings.py。我尝试了makemigrations,但它没有迁移任何东西。我也收到错误:psycopg2.ProgrammingError: relation "core_userabletest" does not exist. @J.Hesters 你如何运行你的测试? python manage.py test 然后是我正在测试的应用程序的名称。 给了你赏金,因为感觉它是最接近正确的,但我仍然无法解决我的问题:(【参考方案2】:

试试下面的代码。我已经测试过它可以正常工作。

requirements.txt

Django==1.11.13
pkg-resources==0.0.0
pytz==2018.4

models.py

from django.conf import settings
from django.db import models


class Userable(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE
    )

    class Meta:
        abstract = True

tests.py

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.test import TestCase
from .models import Userable
from django.db import connection
from django.db.models.base import ModelBase
from django.contrib.auth.models import User


class TestUserable(TestCase):

    def setUp(self):
        user = User.objects.create_user(
            username="testuser@test.com",
            password="test1234test"
        )
        self.user = user
        self.model = ModelBase(
            Userable.__name__,
            (Userable,),
            '__module__': Userable.__module__
        )

        with connection.schema_editor() as schema_editor:
            schema_editor.create_model(self.model)

    def test_user(self):
        self.model.objects.create(user=self.user)
        self.assertEqual(self.model.objects.count(), 1)

    def tearDown(self):
        with connection.schema_editor() as schema_editor:
            schema_editor.delete_model(self.model)

【讨论】:

这对我不起作用,因为我得到了相同错误的版本:django.db.utils.OperationalError: cannot DROP TABLE "core_userable" because it has pending trigger events 。我正在使用自定义用户模型,我不知道这是否会改变任何东西。 在您的数据库表<your table instead foo> 上尝试使用类似UPDATE foo SET bar = '' WHERE bar IS NULL 的sql 列<your column instead bar> 不幸的是,这不起作用,因为 Userable 是抽象的,因此没有相应的 SQL 迁移。

以上是关于Django 抽象模型 + DB 迁移:测试抛出“无法更改表,因为它有待处理的触发事件”的主要内容,如果未能解决你的问题,请参考以下文章

在测试期间更改 Django 迁移应用程序的顺序

有没有一种简单的方法可以将 Django 的模型和迁移链与 db 验证一致性进行比较?

使用迁移向 Django 中的模型字段添加索引

在 Django 1.7 迁移中调用 loaddata 会抛出“‘字段列表’中的未知列‘[字段]’”

django 1.8 测试模型和迁移

Django 如何摆脱迁移错误