diff --git a/docs/helpers.rst b/docs/helpers.rst index 7c60f9005..cdfec3534 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -94,6 +94,32 @@ on what marks are and for notes on using_ them. client('some-url-with-invalid-template-vars') +``pytest.mark.django_use_model`` - force model creation for unmanaged models +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. py:function:: pytest.mark.django_use_model(model) + + :type model: django model or list of django models + :param model: + Model or models to be created, should be used only with models that + have ``Meta.managed = False`` + + This will create requested model(s) for the scope of the marker. + Allows testing of unmanaged models that are normally not created. + + .. note:: + + To access database you still have to request access by using + ``pytest.mark.django_db`` + + Example usage:: + + @pytest.mark.django_db + @pytest.mark.django_use_model(Unmanaged) + def test_unmanaged(): + assert Unmanaged.objects.count() >= 0 + + Fixtures -------- diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 2ac672433..60cb030ef 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -168,6 +168,10 @@ def pytest_load_initial_conftests(early_config, parser, args): 'the `urls` attribute of Django `TestCase` objects. *modstr* is ' 'a string specifying the module of a URL config, e.g. ' '"my_app.test_urls".') + early_config.addinivalue_line( + 'markers', + 'django_use_model(model): force model creation, ' + 'even for unmanaged models. Model(s) are deleted at the end of scope') options = parser.parse_known_args(args) @@ -297,6 +301,48 @@ def _django_db_marker(request): request.getfuncargvalue('db') +@pytest.fixture(autouse=True) +def _django_use_model(request): + """Implement ``django_use_model`` marker. + + Marker creates unmanaged models that normally aren't created. + Destroys it at the end of marked scope. + + Note that you still need to use ``django_db`` marker before this one. + The test unit should be decorated: + + @pytest.mark.django_db + @pytest.mark.django_use_model(model) + + :model: ModelClass, one or many + """ + marker = request.keywords.get('django_use_model', None) + if not marker: + return + from django.db import connection + + model = request.getfuncargvalue('model') + + if isinstance(model, (list, tuple)): + models = model + else: + models = (model,) + + with contextlib.closing(connection.schema_editor()) as schema: + schema.deferred_sql = [] + for model_class in models: + if not hasattr(model, '_meta'): + raise ValueError('"model" must be a valid model class') + schema.create_model(model_class) + + def drop(): + with contextlib.closing(connection.schema_editor()) as schema: + for model_class in models: + schema.delete_model(model_class) + + request.addfinalizer(drop) + + @pytest.fixture(autouse=True, scope='class') def _django_setup_unittest(request, _django_cursor_wrapper): """Setup a django unittest, internal to pytest-django.""" diff --git a/pytest_django_test/app/models.py b/pytest_django_test/app/models.py index 381ce30aa..d974e2395 100644 --- a/pytest_django_test/app/models.py +++ b/pytest_django_test/app/models.py @@ -3,3 +3,10 @@ class Item(models.Model): name = models.CharField(max_length=100) + + +class Unmanaged(models.Model): + name = models.CharField(max_length=100) + + class Meta: + managed = False diff --git a/tests/test_database.py b/tests/test_database.py index 0a2449cd1..04f97ba54 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -1,10 +1,10 @@ from __future__ import with_statement import pytest -from django.db import connection, transaction +from django.db import connection, transaction, DatabaseError from django.test.testcases import connections_support_transactions -from pytest_django_test.app.models import Item +from pytest_django_test.app.models import Item, Unmanaged def noop_transactions(): @@ -164,6 +164,28 @@ def test_transactions_enabled(self): assert not noop_transactions() +@pytest.mark.django_db +class TestUseModel: + """Tests for django_use_model marker""" + + def test_unmanaged_missing(self): + """Test that Unmanaged model is not created by default""" + with pytest.raises(DatabaseError): + # If table does not exists, django will raise DatabaseError + # but the message will depend on the backend. + # Probably nothing else can be asserted here. + Unmanaged.objects.exists() + + @pytest.mark.django_use_model(Unmanaged) + def test_unmanaged_created(self): + """Make sure unmanaged models are created""" + assert Unmanaged.objects.count() == 0 + + def test_unmanaged_destroyed(self): + """Test that Unmanaged model was destroyed after last use""" + self.test_unmanaged_missing() + + def test_unittest_interaction(django_testdir): "Test that (non-Django) unittests cannot access the DB."