Skip to content

Commit 272ecf7

Browse files
committed
Add django application in tests that can be used to run django ORM in pytests tests seamlessly.
Other drivers use contrived setups most that most likely stem from the django reusable apps testing strategy. The way we do it allows for total pytest compatability, we also added two fixtures to automatically delete any row added in test and if necessary, to drop the tables that the migrations created. The commands `makemigrations` and `migrate` are automatically called in every test run.
1 parent 76f8eef commit 272ecf7

File tree

6 files changed

+202
-58
lines changed

6 files changed

+202
-58
lines changed

tests/conftest.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import logging
2+
3+
import pytest
4+
import os, django
5+
from django.db import connection
6+
7+
8+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings")
9+
django.setup()
10+
11+
12+
try:
13+
from django.core.management import execute_from_command_line
14+
except ImportError as exc:
15+
raise ImportError(
16+
"Couldn't import Django. Are you sure it's installed and "
17+
"available on your PYTHONPATH environment variable? Did you "
18+
"forget to activate a virtual environment?"
19+
) from exc
20+
21+
# Runs migrations at the beginning of every full test run, to check that migrations can run.
22+
execute_from_command_line(["manage.py", "makemigrations"])
23+
execute_from_command_line(["manage.py", "migrate"])
24+
25+
logging.info("All migrations run successfully")
26+
27+
28+
@pytest.fixture(scope="function", autouse=True)
29+
def clean_database(request):
30+
"""After every test removes all the rows inserted in the model."""
31+
32+
yield
33+
34+
models = [
35+
model
36+
for model in request.module.__dict__.values()
37+
if isinstance(model, django.db.models.base.ModelBase)
38+
]
39+
for model in models:
40+
with connection.cursor() as cursor:
41+
cursor.execute(f"DELETE FROM {model._meta.db_table}")
42+
cursor.execute(f"REFRESH TABLE {model._meta.db_table}")
43+
44+
45+
@pytest.fixture(scope="session", autouse=False)
46+
def cleanup_migrations():
47+
"""
48+
Removes every table created from the migrations, not necessary in CI but very useful
49+
in local while debugging migrations and model field creations, set autouse=True to use it.
50+
:return:
51+
"""
52+
yield
53+
54+
models = [
55+
model
56+
for model in request.module.__dict__.values()
57+
if isinstance(model, django.db.models.base.ModelBase)
58+
]
59+
for model in models:
60+
with connection.cursor() as cursor:
61+
cursor.execute(f"DROP TABLE IF EXISTS {model._meta.db_table}")

tests/settings.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Default configuration, in the test we might change them to test logic.
2+
DATABASES = {
3+
"default": {
4+
"ENGINE": "cratedb_django",
5+
"SERVERS": ["192.168.88.251:4200"],
6+
}
7+
}
8+
9+
10+
# Below are all necessary settings to load typical django migrations,
11+
# which we use to test migrations
12+
13+
SECRET_KEY = "fake-key"
14+
15+
INSTALLED_APPS = [
16+
"tests.test_app",
17+
"django.contrib.admin",
18+
"django.contrib.auth",
19+
"django.contrib.contenttypes",
20+
"django.contrib.sessions",
21+
"django.contrib.messages",
22+
"django.contrib.staticfiles",
23+
]
24+
25+
MIDDLEWARE = [
26+
"django.middleware.security.SecurityMiddleware",
27+
"django.contrib.sessions.middleware.SessionMiddleware",
28+
"django.middleware.common.CommonMiddleware",
29+
"django.middleware.csrf.CsrfViewMiddleware",
30+
"django.contrib.auth.middleware.AuthenticationMiddleware",
31+
"django.contrib.messages.middleware.MessageMiddleware",
32+
"django.middleware.clickjacking.XFrameOptionsMiddleware",
33+
]
34+
35+
TEMPLATES = [
36+
{
37+
"BACKEND": "django.template.backends.django.DjangoTemplates",
38+
"DIRS": [],
39+
"APP_DIRS": True,
40+
"OPTIONS": {
41+
"context_processors": [
42+
"django.template.context_processors.debug",
43+
"django.template.context_processors.request",
44+
"django.contrib.auth.context_processors.auth",
45+
"django.contrib.messages.context_processors.messages",
46+
],
47+
},
48+
},
49+
]
50+
51+
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

tests/test_app/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"""
2+
This module is a Django application that can contain models, views and urls, we only
3+
use it to register models and test the ORM.
4+
"""

tests/test_app/migrations/__init__.py

Whitespace-only changes.

tests/test_app/models.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import datetime, uuid
2+
3+
from django.db import models
4+
5+
from cratedb_django import CrateModel
6+
from cratedb_django.models import ObjectField
7+
8+
9+
class AllFieldsModel(CrateModel):
10+
field_int = models.IntegerField(unique=False)
11+
field_int_unique = models.IntegerField(unique=True)
12+
field_int_not_indexed = models.IntegerField(db_index=False)
13+
field_int_not_null = models.IntegerField(null=False)
14+
field_int_null = models.IntegerField(null=True)
15+
field_int_default = models.IntegerField(default=54321)
16+
field_float = models.FloatField()
17+
field_char = models.CharField(max_length=100)
18+
field_bool = models.BooleanField()
19+
# field_date = models.DateField()
20+
# field_datetime = models.DateTimeField()
21+
field_json = ObjectField(default=dict)
22+
field_uuid = models.UUIDField()
23+
24+
@classmethod
25+
def create_test(cls):
26+
return cls.objects.create(
27+
field_int=1,
28+
field_int_unique=2,
29+
field_int_not_indexed=3,
30+
field_int_not_null=4,
31+
field_int_null=5,
32+
field_int_default=6,
33+
field_float=0.1,
34+
field_char="somechar",
35+
field_bool=True,
36+
field_date=datetime.datetime.today().date(),
37+
field_datetime=datetime.datetime.today(),
38+
field_json={"hello": "world"},
39+
field_uuid=uuid.uuid4(),
40+
)
41+
42+
class Meta:
43+
app_label = "test_app"
44+
45+
46+
class SimpleModel(CrateModel):
47+
field = models.TextField()
48+
49+
class Meta:
50+
app_label = "test_app"

tests/test_model.py

Lines changed: 36 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,37 @@
1-
import datetime
2-
31
from django.forms.models import model_to_dict
4-
5-
try:
6-
from example.apps.simple_auth.models import MigrationTest
7-
except:
8-
from apps.simple_auth.models import MigrationTest
9-
10-
from tests import NoTransactionTestCase
11-
12-
13-
class MigrationTestTests(NoTransactionTestCase):
14-
def tearDown(self):
15-
# Clean up after each test
16-
MigrationTest.objects.all().delete()
17-
MigrationTest.refresh()
18-
# pass
19-
20-
def test_simple_create(self):
21-
"""Test that an object is created and accounted for with all fields"""
22-
assert MigrationTest.objects.count() == 0
23-
expected = {
24-
"id": 29147646,
25-
"field_int": 1,
26-
"field_int_unique": 2,
27-
"field_int_not_indexed": 3,
28-
"field_int_not_null": 4,
29-
"field_int_null": 5,
30-
"field_int_default": 6,
31-
"field_float": 0.1,
32-
"field_char": "somechar",
33-
"field_bool": True,
34-
"field_date": datetime.datetime(2025, 4, 23, 0, 0),
35-
"field_datetime": datetime.datetime(2025, 4, 23, 14, 20, 15, 833000),
36-
"field_json": {"hello": "world"},
37-
"field_uuid": "00bde3702f844402b750c1b37d589084",
38-
}
39-
MigrationTest.objects.create(**expected)
40-
MigrationTest.refresh()
41-
assert MigrationTest.objects.count() == 1
42-
43-
obj = MigrationTest.objects.get()
44-
assert model_to_dict(obj) == expected
45-
46-
def test_unique_duplicates(self):
47-
# CrateDB does not support the unique constraint, creating two test objects which
48-
# has static values should not error.
49-
MigrationTest.create_test()
50-
MigrationTest.create_test()
51-
MigrationTest.refresh()
52-
assert MigrationTest.objects.count() == 2
53-
54-
def test_random_id(self):
55-
MigrationTest.create_test()
56-
MigrationTest.refresh()
57-
obj = MigrationTest.objects.get()
58-
assert hasattr(obj, 'id')
59-
assert isinstance(obj.id, int)
2+
from tests.test_app.models import AllFieldsModel, SimpleModel
3+
4+
5+
def test_insert_model_field():
6+
"""Test that we can insert a model and refresh it"""
7+
assert SimpleModel.objects.count() == 0
8+
SimpleModel.objects.create(field="text")
9+
SimpleModel.refresh()
10+
assert SimpleModel.objects.count() == 1
11+
12+
13+
def test_insert_all_fields():
14+
"""Test that an object is created and accounted for with all fields"""
15+
16+
expected = {
17+
"id": 29147646,
18+
"field_int": 1,
19+
"field_int_unique": 2,
20+
"field_int_not_indexed": 3,
21+
"field_int_not_null": 4,
22+
"field_int_null": 5,
23+
"field_int_default": 6,
24+
"field_float": 0.1,
25+
"field_char": "somechar",
26+
"field_bool": True,
27+
# "field_date": datetime.datetime(2025, 4, 22, 0, 0, tzinfo=datetime.timezone.utc),
28+
# "field_datetime": datetime.datetime(1, 1, 1, 1, 1, 1, 1),
29+
"field_json": {"hello": "world"},
30+
"field_uuid": "00bde3702f844402b750c1b37d589084",
31+
}
32+
AllFieldsModel.objects.create(**expected)
33+
AllFieldsModel.refresh()
34+
assert AllFieldsModel.objects.count() == 1
35+
36+
obj = AllFieldsModel.objects.get()
37+
assert model_to_dict(obj) == expected

0 commit comments

Comments
 (0)