diff --git a/docs/helpers.rst b/docs/helpers.rst index 47e1ad76a..ad062fe5a 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -94,6 +94,32 @@ when trying to access the database. 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=ModelClass) + + :type model: django model or list of django models, as kwarg + :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(model=Unmanaged) + def test_unmanaged(): + assert Unmanaged.objects.count() >= 0 + + Fixtures -------- diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 1585c6983..fa4069f8b 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -184,6 +184,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=ModelClass): force model creation, ' + 'even for unmanaged models. Model(s) are deleted at the end of scope') options = parser.parse_known_args(args) @@ -387,6 +391,48 @@ def _django_db_marker(request): getfixturevalue(request, '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=ModelClass) + + :model: ModelClass, one or many + """ + marker = request.keywords.get('django_use_model', None) + if not marker: + return + from django.db import connection + + model = marker.kwargs['model'] + + if isinstance(model, (list, tuple)): + models = model + else: + models = (model,) + + with 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 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_db_blocker): """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 717c02048..86c1889ee 100644 --- a/pytest_django_test/app/models.py +++ b/pytest_django_test/app/models.py @@ -9,3 +9,10 @@ def __unicode__(self): def __str__(self): return self.name + + +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 ee3912c76..3b22e60a6 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -1,11 +1,11 @@ from __future__ import with_statement import pytest -from django.db import connection +from django.db import connection, DatabaseError from django.test.testcases import connections_support_transactions from pytest_django.pytest_compat import getfixturevalue -from pytest_django_test.app.models import Item +from pytest_django_test.app.models import Item, Unmanaged def test_noaccess(): @@ -141,6 +141,30 @@ def test_transactions_enabled(self): assert not connection.in_atomic_block +@pytest.mark.skipif(not hasattr(connection, 'schema_editor'), + reason="This Django version does not support SchemaEditor") +@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(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."