I need to install citext extension to my postgresql database for django project. For the project itself it went smoothly and works great via migrations, but my pytest is configured with option --no-migrations
, so pytest create database without running migrations. How can i make pytest to install citext
postgres extension before tables are created? Currently i'm getting
- django.db.utils.ProgrammingError: type "citext" does not exist
while pytest trying to create table auth_users
sql = 'CREATE TABLE "auth_user" ("id" serial NOT NULL PRIMARY KEY, "password" varchar(128) NOT NULL, "last_login" timestamp ...T NULL, "is_active" boolean NOT NULL, "date_joined" timestamp with time zone NOT NULL, "email" citext NOT NULL UNIQUE)', params = None
ignored_wrapper_args = (False, {'connection': <django.contrib.gis.db.backends.postgis.base.DatabaseWrapper object at 0x7fb313bb0100>, 'cursor': <django.db.backends.utils.CursorWrapper object at 0x7fb30d9f8580>})
I tried to use django_db_setup
fixture, but i did not figure out how to change it, because something like this
@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,
):
"""Top level fixture to ensure test databases are available"""
from django.test.utils import setup_databases, teardown_databases
setup_databases_args = {}
if not django_db_use_migrations:
from pytest_django.fixtures import _disable_native_migrations
_disable_native_migrations()
if django_db_keepdb and not django_db_createdb:
setup_databases_args["keepdb"] = True
with django_db_blocker.unblock():
from django.db import connection
cursor = connection.cursor()
cursor.execute("CREATE EXTENSION IF NOT EXISTS citext;")
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)
did not help me
Annoyingly, there are no appropriate hooks between setting up the database and loading the appropriate postgresql extensions. You can work around the issue by copying/modifying the pytest-django code that disables migrations and running your code instead of the upstream code.
@pytest.fixture(scope="session")
def django_migration_disabler() -> None:
"""Disable migrations when running django tests.
This copies/alters the behavior of pytest_django.fixtures._disable_migrations,
which is called when pytest is invoked with --no-migrations.
See: https://github.com/pytest-dev/pytest-django/blob/v4.5.2/pytest_django/fixtures.py#L260
We do this instead of invoking with --no-migrations because constructing the
database without migrations fails to create necessary postgres extensions like
citext and uuid-ossp.
We then override the django_db_setup fixture and ensure that this
fixture is called before the parent django_db_setup fixture.
"""
from django.conf import settings
from django.core.management.commands import migrate
from django.db import connections
class DisableMigrations:
def __contains__(self, item: str) -> bool:
return True
def __getitem__(self, item: str) -> None:
return None
settings.MIGRATION_MODULES = DisableMigrations()
class MigrateSilentCommand(migrate.Command):
def handle(self, *args, **options):
options["verbosity"] = 0
database = options["database"]
connection = connections[database]
with connection.cursor() as cursor:
cursor.execute('CREATE EXTENSION IF NOT EXISTS "citext";')
return super().handle(*args, **options)
migrate.Command = MigrateSilentCommand # type: ignore
@pytest.fixture(scope="session")
def django_db_use_migrations() -> bool:
"""Force pytest-django to use migrations.
This is necessary because we disable migrations in the django_migration_disabler
and the existing pytest-django mechanisms would override our disabling mechanism
with their own, which would fail to create the necessary extensions in postgres.
"""
return True
@pytest.fixture(scope="session")
def django_db_setup(django_migration_disabler, django_db_setup, django_db_blocker):
"""Override django_db_setup ensuring django_migration_disabler is loaded."""
pass