Django abstract model + DB migrations: tests throw "cannot ALTER TABLE because it has pending trigger events"
Asked Answered
T

2

8

I want to write an abstract model mixin, that I can use to make OneToOne - relations to the user model. Here is my code:

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

I've written the following test for this model:

class TestUserable(TestCase):

    mixin = Userable

    def setUp(self):
        user = User.objects.create_user(
            email="[email protected]",
            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)

My problem is, that this test in it's tearDown() method throws the follwing error:

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

What could be the cause of this? I did run python manage.py makemigrations and python manage.py migrate, but there are no pending migrations (as is expected, since this is an abstract model).

EDIT: It seems to have something to do with OneToOneFields or ForeignKeys (relations). If I use this code altered for regular fields, like CharFields or IntegerFields, it works.

EDIT2: If you have another better way of testing abstract base model that use ForeignKeys, please let me know!

Tamie answered 27/5, 2018 at 14:17 Comment(6)
it would be less confusing if you didn't reassign self.model in setUp..Doretheadoretta
What do you mean with that? that first model gets set to Userable and then set with self.model? I could replace the first model=Userable with mixin or something :)Tamie
mixin would make it clearer..Doretheadoretta
I edited it, thank you for your suggestion. Do you have any idea how to solve this problem?Tamie
Unfortunately no, I'm interested in the answer too though..Doretheadoretta
Possible duplicate of Django-DB-Migrations: cannot ALTER TABLE because it has pending trigger eventsAutopsy
L
1

Common practice for testing abstract models is to create actual models just for tests

here is example in model-utils project 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="[email protected]",
            name="Test User",
            password="test1234test"
        )
        self.user = user

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

In this project they have separate settings DJANGO_SETTINGS_MODULE = tests.settings https://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'

And models are described in https://github.com/jazzband/django-model-utils/blob/master/tests/models.py

from myapp.models import Userable

class UserableTest(Userable):
    pass
Leavenworth answered 31/5, 2018 at 11:57 Comment(4)
I tried this solution. I feel like this could work. Here is what I did: I created a testapp in core/tests. So core/tests/testapp. I then added a settings.py which inhertis from base.py and local.py. I tried makemigrations, but it didn't migrate anything. I also get the error: psycopg2.ProgrammingError: relation "core_userabletest" does not exist.Tamie
@J.Hesters how do you run your tests?Leavenworth
python manage.py test and then the name of the app I'm testing.Tamie
Gave you the bounty, because it feels like it's the closest to be the correct one, but I still couldn't solve my problem with it :(Tamie
H
0

Try below code. I've tested it it's working.

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="[email protected]",
            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)
Holle answered 30/5, 2018 at 13:2 Comment(3)
This does not work for me, as I get the a version of the same error: django.db.utils.OperationalError: cannot DROP TABLE "core_userable" because it has pending trigger events . I'm using a custom user model, I don't know if that changes anything.Tamie
try sql something like UPDATE foo SET bar = '' WHERE bar IS NULL on your database table <your table instead foo> with column <your column instead bar>Holle
This unforuntely doesn't work, since Userable is abstract and therefore has no corresponding SQL migration.Tamie

© 2022 - 2024 — McMap. All rights reserved.