How to create table during Django tests with managed = False - python

I have a model with managed = False.
class SampleModel(models.Model):
apple = models.CharField(max_length=30)
orange = models.CharField(max_length=30)
class Meta:
managed = False
I have a unit test which creates a SampleModel, however when I run the test I get:
DatabaseError: no such table: SAMPLE_SAMPLE_MODEL
The django docs - https://docs.djangoproject.com/en/dev/ref/models/options/#managed documents the following:
For tests involving models with managed=False, it's up to you to
ensure the correct tables are created as part of the test setup.
How can I actually "create" the tables during the test setup? Or alternatively, how can I make it so that when I am running tests, this model has "managed = True" for the duration of the test?
In the real application, this model is actually backed by a view in the database. However for the during of the test, I would like to treat this as a table and be able to insert test data in there.

You can use SchemaEditor in TestCase.setUp method to explicitly create models with managed = False.
# models.py
from django.db import models
class Unmanaged(models.Model):
foo = models.TextField()
class Meta:
# This model is not managed by Django
managed = False
db_table = 'unmanaged_table'
And in your tests:
# tests.py
from django.db import connection
from django.test import TestCase
from myapp.models import Unmanaged
class ModelsTestCase(TestCase):
def setUp(self):
super().setUp()
with connection.schema_editor() as schema_editor:
schema_editor.create_model(Unmanaged)
if (
Unmanaged._meta.db_table
not in connection.introspection.table_names()
):
raise ValueError(
"Table `{table_name}` is missing in test database.".format(
table_name=Unmanaged._meta.db_table
)
)
def tearDown(self):
super().tearDown()
with connection.schema_editor() as schema_editor:
schema_editor.delete_model(Unmanaged)
def test_unmanaged_model(self):
with self.assertNumQueries(num=3):
self.assertEqual(0, Unmanaged.objects.all().count())
Unmanaged.objects.create()
self.assertEqual(1, Unmanaged.objects.all().count())

Check out this blog post: http://www.caktusgroup.com/blog/2010/09/24/simplifying-the-testing-of-unmanaged-database-models-in-django/ It describes in detail the creation of a test runner for unmanaged models.
from django.test.simple import DjangoTestSuiteRunner
class ManagedModelTestRunner(DjangoTestSuiteRunner):
"""
Test runner that automatically makes all unmanaged models in your Django
project managed for the duration of the test run, so that one doesn't need
to execute the SQL manually to create them.
"""
def setup_test_environment(self, *args, **kwargs):
from django.db.models.loading import get_models
self.unmanaged_models = [m for m in get_models()
if not m._meta.managed]
for m in self.unmanaged_models:
m._meta.managed = True
super(ManagedModelTestRunner, self).setup_test_environment(*args,
**kwargs)
def teardown_test_environment(self, *args, **kwargs):
super(ManagedModelTestRunner, self).teardown_test_environment(*args,
**kwargs)
# reset unmanaged models
for m in self.unmanaged_models:
m._meta.managed = False

Execute raw SQL to create the table in the test setup:
from django.db import connection
class MyTest(unittest.TestCase):
def setUp(self):
connection.cursor().execute("CREATE TABLE ...")
def tearDown(self):
connection.cursor().execute("DROP TABLE ...")

Nice plug and play solution. Just paste this before your test class definition. (note: django 1.8 used)
from django.db.models.loading import get_models
def change_managed_settings_just_for_tests():
"""django model managed bit needs to be switched for tests."""
unmanaged_models = [m for m in get_models() if not m._meta.managed]
for m in unmanaged_models:
m._meta.managed = True
change_managed_settings_just_for_tests()

A quick fix if you don't have many unmanaged tables:
First add a new variable to the settings.
# settings.py
import sys
UNDER_TEST = (len(sys.argv) > 1 and sys.argv[1] == 'test')
then in the models
# models.py
from django.conf import settings
class SampleModel(models.Model):
apple = models.CharField(max_length=30)
orange = models.CharField(max_length=30)
class Meta:
managed = getattr(settings, 'UNDER_TEST', False)

Create your own test runner using this:
from django.test.simple import DjangoTestSuiteRunner
class NoDbTestRunner(DjangoTestSuiteRunner):
""" A test runner to test without database creation """
def setup_databases(self, **kwargs):
""" Override the database creation defined in parent class """
#set manage=True for that specific database on here
Then on your settings add this class to TEST_RUNNER.

Just to add :django.db.models.loading.get_models will be removed in Django 1.9 (see https://github.com/BertrandBordage/django-cachalot/issues/33).
Below is an updated one for Django 1.10:
class UnManagedModelTestRunner(DiscoverRunner):
'''
Test runner that automatically makes all unmanaged models in your Django
project managed for the duration of the test run.
Many thanks to the Caktus Group
'''
def setup_test_environment(self, *args, **kwargs):
from django.apps import apps
self.unmanaged_models = [m for m in apps.get_models() if not m._meta.managed]
for m in self.unmanaged_models:
m._meta.managed = True
super(UnManagedModelTestRunner, self).setup_test_environment(*args, **kwargs)
def teardown_test_environment(self, *args, **kwargs):
super(UnManagedModelTestRunner, self).teardown_test_environment(*args, **kwargs)
# reset unmanaged models
for m in self.unmanaged_models:
m._meta.managed = False
Note you also need to take care migrations(see Testing django application with several legacy databases)
MIGRATION_MODULES = {
'news': 'news.test_migrations',
'economist': 'economist.test_migrations'
}

Using pytest and pytest-django
To make this work (has been tested with django 3.0.2, pytest 5.3.5 and pytest-django 3.8.0):
Run your pytest with the additional argument --no-migrations.
Put the following code in your conftest.py. This has an unfortunate amount of copypasta, but I could not figure out how to first make my models unmanaged and then call the original django_db_setup. The issue of not being able to call pytest fixtures directly is discussed here: https://github.com/pytest-dev/pytest/issues/3950
conftest.py
# example file
import pytest
from pytest_django.fixtures import _disable_native_migrations
#pytest.fixture(scope="session")
def django_db_setup(
request,
django_test_environment,
django_db_blocker,
django_db_use_migrations,
django_db_keepdb,
django_db_createdb,
django_db_modify_db_settings,
):
# make unmanaged models managed
from django.apps import apps
unmanaged_models = []
for app in apps.get_app_configs():
unmanaged_models = [m for m in app.get_models()
if not m._meta.managed]
for m in unmanaged_models:
m._meta.managed = True
# copypasta fixture code
"""Top level fixture to ensure test databases are available"""
from pytest_django.compat import setup_databases, teardown_databases
setup_databases_args = {}
if not django_db_use_migrations:
_disable_native_migrations()
if django_db_keepdb and not django_db_createdb:
setup_databases_args["keepdb"] = True
with django_db_blocker.unblock():
db_cfg = setup_databases(
verbosity=request.config.option.verbose,
interactive=False,
**setup_databases_args
)
def teardown_database():
with django_db_blocker.unblock():
try:
teardown_databases(db_cfg, verbosity=request.config.option.verbose)
except Exception as exc:
request.node.warn(
pytest.PytestWarning(
"Error when trying to teardown test databases: %r" % exc
)
)
if not django_db_keepdb:
request.addfinalizer(teardown_database)

After spending a few hours testing and researching ways to test my django unmanaged models, I finally came up with a solution that worked for me.
My implementation is in this below snippet. It's working great with local tests using db.sqlite3.
# Example classes Implementation
from django.db import models, connection
from django.db.models.base import ModelBase as DjangoModelBase
from django.db.utils import OperationalError
from django.test import TransactionTestCase
class AbstractModel(models.Model):
name = models.TextField(db_column="FIELD_NAME", blank=True, null=True)
class Meta:
managed = False
abstract = True
class TestModel(AbstractModel):
test_field = models.TextField(db_column="TEST_FIELD", blank=True, null=True)
def test_method(self):
print("just testing")
class Meta(AbstractModel.Meta):
db_table = "MY_UNMANAGED_TABLE_NAME"
# My Custom Django TestCases Implementation for my tests
def create_database(model):
with connection.schema_editor() as schema_editor:
try:
schema_editor.create_model(model)
except OperationalError:
pass
def drop_database(model):
with connection.schema_editor() as schema_editor:
try:
schema_editor.delete_model(model)
except OperationalError:
pass
class BaseModelTestCase(TransactionTestCase):
"""Custom TestCase for testing models not managed by django."""
Model = DjangoModelBase
def setUp(self):
super().setUp()
create_database(self.Model)
def tearDown(self):
super().tearDown()
drop_database(self.Model)
class AbstractBaseModelTestCase(TransactionTestCase):
"""Custom TestCase for testing abstract django models."""
Model = DjangoModelBase
def setUp(self):
# this is necessary for testing an abstract class
self.Model = DjangoModelBase(
"__TestModel__" + self.Model.__name__,
(self.Model,),
{"__module__": self.Model.__module__},
)
create_database(self.Model)
def tearDown(self):
drop_database(self.Model)
# Example of usage
class TestModelTestCase(BaseModelTestCase):
Model = TestModel
def setUp(self):
super().setUp()
self.instance = TestModel.objects.create()
self.assertIsInstance(self.instance, TestModel)
class AbstractModelTestCase(AbstractBaseModelTestCase):
Model = AbstractModel
def setUp(self):
super().setUp()
self.instance = AbstractModel.objects.create()
self.assertIsInstance(self.instance, AbstractModel)
p.s. I am not using any Django migrations as soon as my models are managed=False. So this was not necessary for me.

Simply move your unmanaged models to a dedicated app and delete migrations folder. Details in my answer in Multi db and unmanged models - Test case are failing

Related

Can I init multi database on Peewee, from main.py without proxy

I want to be able to create multiple files of same model database.
To use each file with it own connection.
Without the need to initialize the database each time I want to use a different one.
Something like:
sqlite_1.add_user(name="Jerry")
sqlite_2.add_user(name="Jerry")
Solution with proxy with initialize
main.py
# main.py
import peewee as pw
import database as db
sqlite_1 = pw.SqliteDatabase('sqlite_1.db')
sqlite_2 = pw.SqliteDatabase('sqlite_2.db')
db.proxy.initialize(sqlite_1)
sqlite_1.create_tables([db.User], safe=True)
db.add_user(name="Tom")
db.proxy.initialize(sqlite_2)
sqlite_2.create_tables([db.User], safe=True)
db.add_user(name="Jerry")
database.py:
# database.py
import peewee as pw
proxy = pw.Proxy()
class BaseModel(pw.Model):
class Meta:
database = proxy
class User(BaseModel):
name = pw.CharField()
def add_user(name):
with proxy.atomic() as txn:
User.create(name=name).save()
def get_user(name):
with proxy.atomic() as txn:
return User.get(User.name == name)
Can I do multiple databased with same model without proxy ?
You probably want to use the bind() and bind_ctx() methods to swap a model between databases at runtime:
http://docs.peewee-orm.com/en/latest/peewee/api.html#Database.bind_ctx
MODELS = (User, Account, Note)
# Bind the given models to the db for the duration of wrapped block.
def use_test_database(fn):
#wraps(fn)
def inner(self):
with test_db.bind_ctx(MODELS):
test_db.create_tables(MODELS)
try:
fn(self)
finally:
test_db.drop_tables(MODELS)
return inner
class TestSomething(TestCase):
#use_test_database
def test_something(self):
# ... models are bound to test database ...
pass

How to manage a peewee database in a separate module?

I want to have my database implementation in a separate module or class. But I am struggling with a few details. A simple example:
from peewee import *
db = SqliteDatabase(':memory:')
class BaseModel(Model):
class Meta:
database = db
class User(BaseModel):
name = CharField()
db.connect()
db.create_tables([User,])
db.commit()
#db.atomic()
def add_user(name):
User.create(name=name).save()
#db.atomic()
def get_user(name):
return User.get(User.name == name)
So far this is working fine. I can implement my interface to the database here and import this as a module.
Now I want to be able to choose the database file at runtime. So I need a way to define the Model classes without defining SqliteDatabase('somefile') before. I tried to encapsulate everything in a new Database class, which I can later import and create an instance from:
from peewee import *
class Database:
def __init__(self, dbfile):
self.db = SqliteDatabase(dbfile)
class BaseModel(Model):
class Meta:
database = self.db
class User(BaseModel):
name = CharField()
self.User = User
self.db.connect()
self.db.create_tables([User,])
self.db.commit()
#self.db.atomic() # Error as self is not known on this level
def add_user(self, name):
self.User.create(name=name).save()
#self.db.atomic() # Error as self is not known on this level
def get_user(self, name):
return self.User.get(self.User.name == name)
Now I can call for example database = Database('database.db') or choose any other file name. I can even use multiple database instance in the same program, each with its own file.
However, there are two problems with this approach:
I still need to specify the database driver (SqliteDatabase) before defining the Model classes. To solve this I define the Model classes within the __init__() method, and then create an alias to with self.User = User. I don't really like this approach (it just doesn't feel like neat code), but at least it works.
I cannot use the #db.atomic() decorator since self is not known at class level, I would an instance here.
So this class approach does not seem to work very well. Is there some better way to define the Model classes without having to choose where you want to store your database first?
If you need to change database driver at the runtime, then Proxy is a way to go
# database.py
import peewee as pw
proxy = pw.Proxy()
class BaseModel(pw.Model):
class Meta:
database = proxy
class User(BaseModel):
name = pw.CharField()
def add_user(name):
with proxy.atomic() as txn:
User.create(name=name).save()
def get_user(name):
with proxy.atomic() as txn:
return User.get(User.name == name)
From now on each time you load the module, it won't need a database to be initialized. Instead, you can initialize it at the runtime and switch between multiple as follows
# main.py
import peewee as pw
import database as db
sqlite_1 = pw.SqliteDatabase('sqlite_1.db')
sqlite_2 = pw.PostgresqlDatabase('sqlite_2.db')
db.proxy.initialize(sqlite_1)
sqlite_1.create_tables([db.User], safe=True)
db.add_user(name="Tom")
db.proxy.initialize(sqlite_2)
sqlite_2.create_tables([db.User], safe=True)
db.add_user(name="Jerry")
But if the connection is the only thing that matters, then init() method will be enough.
Now I want to be able to choose the database file at runtime. So I
need a way to define the Model classes without defining
SqliteDatabase('somefile') before. I tried to encapsulate everything
in a new Database class, which I can later import and create an
instance from
Peewee uses the meta class to define the name of the table (Model.Meta.db_table) and database( Model.Meta.database)
set these attribute before calling a Model specific code ( either to create a table or to DML statements)
'Allow to define database dynamically'
Question: I cannot use the #db.atomic() decorator since self is not known at class level
Do it, as you do it with self.User.
I wonder about atomic() instead of atomic, but you tell is working fine.
class Database:
def __init__(self, dbfile):
self.db = SqliteDatabase(dbfile)
...
#self.db.atomic()
def __add_user(self, name):
self.User.create(name=name).save()
self.add_user = __add_user
#self.db.atomic()
def __get_user(self, name):
return self.User.get(self.User.name == name)
self.get_user = __get_user
Related: Define models separately from Database() initialization

Python AttributeError: 'Class' object has no attribute 'name' - SetUpClass

I'm writing tests for a model in a Django app, but cannot get them to run. I've tried all I can think of to solve and searched but cannot find the solution.
The error I receive when I run the test is AttributeError: 'UserModelTest' object has no attribute 'firstUser', which would suggest I haven't defined firstUser correctly.
Here are the relevant bits of code.
tests.py
from django.test import TestCase
from .models import ContactForm
from datetime import datetime, timedelta
class UserModelTest(TestCase):
#classmethod
def setUpClass(cls):
cls.firstUser = ContactForm(
email="first#user.com",
name="first",
timestamp=datetime.today() + timedelta(days=2)
)
cls.firstUser.save()
def test_contactform_str_returns_email(self):
self.assertEqual("first#user.com", str(self.firstUser))
models.py
from django.db import models
import datetime
class ContactForm(models.Model):
name = models.CharField(max_length=150)
email = models.EmailField(max_length=250)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.email
class Meta:
ordering = ['-timestamp']
I'm using Python version 3.5 and Django version 1.10.
setUpClass has been deprecated for Django (I believe since 1.8). In order to set up the class now, I needed to use setUpTestData. The remaining code still works. setUpTestData is then run only once before all test methods in the class (not once before each test).
i.e. the code should be:
class UserModelTest(TestCase):
#classmethod
def setUpTestData(cls):
cls.firstUser = ContactForm(
email="first#user.com",
name="first",
timestamp=datetime.today() + timedelta(days=2)
)
cls.firstUser.save()
def test_contactform_str_returns_email(self):
self.assertEqual("first#user.com", str(self.firstUser))
More information on setUpTestData can be found here.

django-cms child plugin not registering

In cms_plugins.py I have a parent plugin and child plugin
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from banner_plugin.models import BannerPluginModel, BannerItem
from banner_plugin.admin import BannerInlineAdmin, BannerLinksInlineAdmin
from django.utils.translation import ugettext as _
class CMSBannerParentPlugin(CMSPluginBase):
model = BannerPluginModel # model where plugin data are saved
module = _("Banners")
name = _("Banner Parent Plugin") # name of the plugin in the interface
render_template = "djangocms_banner/banner_parent_plugin.html"
allow_chldren = True
child_classes = ['CMSBannerChildPlugin']
def render(self, context, instance, placeholder):
context.update({'instance': instance})
return context
plugin_pool.register_plugin(CMSBannerParentPlugin)
class CMSBannerChildPlugin(CMSPluginBase):
model = BannerItem # model where plugin data are saved
module = _("Banners")
name = _("Banner Child Plugin") # name of the plugin in the interface
render_template = "djangocms_banner/banner_child_plugin.html"
parent_classes = ['CMSBannerParentPlugin']
inlines = (BannerLinksInlineAdmin,)
def render(self, context, instance, placeholder):
context.update({'instance': instance})
return context
# register the plugin
plugin_pool.register_plugin(CMSBannerChildPlugin)
When I runs the project only the Banner Parent Plugin appears in the module Banners. what could be the error. Both banner_parent_plugin.html and banner_child_plugin.html exists.
In models.py I have
class BannerPluginModel(CMSPlugin):
// no fields here
def __unicode__(self):
return "Banners"
class BannerItem(CMSPlugin):
// fields goes here
def __unicode__(self):
return self.heading
class BannerLinks(models.Model):
// fields goes here
banner_item = models.ForeignKey(to=BannerItem, related_name="links", null=True, blank=True)
Take a look in your code to fix a small issue:
allow_chldren = True should be allow_children = True
As you are working with classes, this does not through an exception.
Not sure if your code have any more issue, try this and keep us updated.
Good luck

Django LiveServerTestCase: User created in in setUpClass method not available in test_method?

I am using Django 1.4's LiveServerTestCase for Selenium testing and am having trouble with the setUpClass class method. As far as I understand MembershipTests.setUpClass is run once before the unit tests are run.
I've put code to add a user to the database in MembershipTests.setUpClass but when I run the MembershipTests.test_signup test no user has been added to the test database. What am I doing incorrectly? I expect the user I created in setUpClass would be available across all unit tests.
If I put the user creation code in MembershipTests.setUp and run MembershipTests.test_signup I can see the user, but don't want this run before every unit test as setUp is. As you can see, I use a custom LiveServerTestCase class to add basic functionality across all of my tests (test_utils.CustomLiveTestCase). I suspect this has something to do with my issue.
Thanks in advance.
test_utils.py:
from selenium.webdriver.firefox.webdriver import WebDriver
from django.test import LiveServerTestCase
class CustomLiveTestCase(LiveServerTestCase):
#classmethod
def setUpClass(cls):
cls.wd = WebDriver()
super(CustomLiveTestCase, cls).setUpClass()
#classmethod
def tearDownClass(cls):
cls.wd.quit()
super(CustomLiveTestCase, cls).tearDownClass()
tests.py:
from django.contrib.auth.models import User
from django.test.utils import override_settings
from test_utils import CustomLiveTestCase
from test_constants import *
#override_settings(STRIPE_SECRET_KEY='xxx', STRIPE_PUBLISHABLE_KEY='xxx')
class MembershipTests(CustomLiveTestCase):
fixtures = [
'account_extras/fixtures/test_socialapp_data.json',
'membership/fixtures/basic/plan.json',
]
def setUp(self):
pass
#classmethod
def setUpClass(cls):
super(MembershipTests, cls).setUpClass()
user = User.objects.create_user(
TEST_USER_USERNAME,
TEST_USER_EMAIL,
TEST_USER_PASSWORD
)
def test_signup(self):
print "users: ", User.objects.all()
The database is torn down and reloaded on every test method, not on the test class. So your user will be lost each time. Do that in setUp not setUpClass.
Since you're using LiveServerTestCase it's almost same as TransactionTestCase which creates and destroys database (truncates tables) for every testcase ran.
So you really can't do global data with LiveServerTestCase.
You should be able to use TestCase.setUpTestData as follows (slight changes to your base class):
test_utils.py:
from selenium.webdriver.firefox.webdriver import WebDriver
from django.test import LiveServerTestCase, TestCase
class CustomLiveTestCase(LiveServerTestCase, TestCase):
#classmethod
def setUpClass(cls):
cls.wd = WebDriver()
super(CustomLiveTestCase, cls).setUpClass()
#classmethod
def tearDownClass(cls):
cls.wd.quit()
super(CustomLiveTestCase, cls).tearDownClass()
tests.py:
from django.contrib.auth.models import User
from django.test.utils import override_settings
from test_utils import CustomLiveTestCase
from test_constants import *
#override_settings(STRIPE_SECRET_KEY='xxx', STRIPE_PUBLISHABLE_KEY='xxx')
class MembershipTests(CustomLiveTestCase):
fixtures = [
'account_extras/fixtures/test_socialapp_data.json',
'membership/fixtures/basic/plan.json',
]
#classmethod
def setUpTestData(cls):
super(MembershipTests, cls).setUpTestData()
user = User.objects.create_user(
TEST_USER_USERNAME,
TEST_USER_EMAIL,
TEST_USER_PASSWORD
)
def test_signup(self):
print "users: ", User.objects.all()
Instead of changing the base class, you could inherit from TestCase in MembershipTests, but you'll have to do this everytime you need test data.
Note that I've also removed the def setUp: pass, as this will break the transaction handling.
Check out this thread for further details: https://groups.google.com/forum/#!topic/django-developers/sr3gnsc8gig
Let me know if you run into any issues with this solution!

Categories

Resources