Problems with postgres migration operations (like HStoreExtension)

I have two models in separate apps and I’d like to add an HStoreField for each of them.

Should I:

a) Add an HStoreExtension operation to both migrations
b) Add the extension to only one of them (and make sure the one with the operation runs first)
c) not use HStoreExtension at all and manage my database extensions separately

With option a), I run into an issue when I try to revert one of the migrations because Django tries to delete the extension from the database but it’s being used in the other app.

With option b) the same problem is still there but it only manifests if I try to revert the migration for the one app that declares the operation.

With option c) I can apply/revert migrations to my heart’s content but I’m now faced with managing extensions manually for all my databases (locally, in production, on staging servers and more annoyingly for the test database as well).

No matter which option I choose it seems I run into problems. Is that just unavoidable or is there a fourth option I haven’t considered?

Thanks :sparkles:

If you do (b) and ensure dependencies in the second migration points to the first one in the other app, that should work fine. If you try reverse the first one, it should automatically try reverse the second one too.

Here are a couple of other options, assuming its harmless to leave the extension loaded when no field uses it:

d) Subclass HStoreExtension and override its “reverse” method to be a no-op.
e) Load the extension in a custom database backend prepare_database method instead, à la postgis.

HTH!

I ended up going with option e) which seems like the least bad to me and was the easiest to turn on and off in my settings (extension creation was only really a problem for me when running tests).

Here’s the code I ended up with:

from django.contrib.postgres.operations import (
    HStoreExtension,
    TrigramExtension,
    UnaccentExtension,
)
from django.db.backends.postgresql.base import DatabaseWrapper as PgDatabaseWrapper


class DatabaseWrapper(PgDatabaseWrapper):
    """
    A postgres database wrapper that auto-installs extension just after
    the database has been created.

    It's a bit of a hack, but it's the least bad solution I found at the time
    of writing.
    """
    AUTOINSTALLED_EXTENSIONS = (
        HStoreExtension,
        TrigramExtension,
        UnaccentExtension,
    )

    def prepare_database(self):
        super().prepare_database()
        for ext in self.AUTOINSTALLED_EXTENSIONS:
            self._install_extension(ext)

    def _install_extension(self, extension):
        with self.schema_editor() as editor:
            extension().database_forwards(
                app_label=None,
                schema_editor=editor,
                from_state=None,
                to_state=None,
            )

I still think it’s weird that Django tries to delete the extension as a reverse operation, but at least now I have a workaround.

Thanks for your help! :rocket: