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="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)

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!

J. Hesters
  • 13,117
  • 31
  • 133
  • 249
  • 1
    it would be less confusing if you didn't reassign self.model in `setUp`.. – thebjorn May 30 '18 at 07:55
  • 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 :) – J. Hesters May 30 '18 at 08:36
  • 1
    `mixin` would make it clearer.. – thebjorn May 30 '18 at 09:07
  • I edited it, thank you for your suggestion. Do you have any idea how to solve this problem? – J. Hesters May 30 '18 at 09:20
  • Unfortunately no, I'm interested in the answer too though.. – thebjorn May 30 '18 at 11:08
  • Possible duplicate of [Django-DB-Migrations: cannot ALTER TABLE because it has pending trigger events](https://stackoverflow.com/questions/12838111/django-db-migrations-cannot-alter-table-because-it-has-pending-trigger-events) – maazza May 17 '19 at 11:00

2 Answers2

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="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)

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
Sardorbek Imomaliev
  • 14,861
  • 2
  • 51
  • 63
  • 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`. – J. Hesters Jun 04 '18 at 21:36
  • @J.Hesters how do you run your tests? – Sardorbek Imomaliev Jun 05 '18 at 00:11
  • `python manage.py test` and then the name of the app I'm testing. – J. Hesters Jun 06 '18 at 06:49
  • 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 :( – J. Hesters Jun 06 '18 at 06:50
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="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)
anjaneyulubatta505
  • 10,713
  • 1
  • 52
  • 62
  • 1
    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. – J. Hesters May 31 '18 at 07:02
  • try sql something like `UPDATE foo SET bar = '' WHERE bar IS NULL` on your database table `` with column `` – anjaneyulubatta505 May 31 '18 at 07:44
  • This unforuntely doesn't work, since Userable is abstract and therefore has no corresponding SQL migration. – J. Hesters Jun 04 '18 at 21:35