Skip to content

Denormalized pgh_context does not get written to event table #241

Description

@gary-pinata

Thank you for this library and for pgtrigger. Both excellent libraries!

I am currently have a multi-database setup using a "default" db router and another db router (which we'll call "other" for the purposes of this issue). I have the trigger functions installed in my second database.

My pghistory related settings are as follows:

INSTALL_ON_MIGRATE = True
PGHISTORY_ADMIN_ALL_EVENTS = False
PGHISTORY_APPEND_ONLY = True

# See https://django-pghistory.readthedocs.io/en/stable/event_models/#denormalizing-context
# Note: Currently `pgh_obj` field and `pgh_context_id` field have to be set in pghistory.track decorator if
#       you want it to be None
# PGHISTORY_CONTEXT_FIELD = None
# PGHISTORY_OBJ_FIELD = None

My routers are along these lines ...

Default database:

class DefaultRouter:
    """
    A router to control all database operations on all models in all applications _except_ other.
    """

    db = "default"
    route_everything_but_these_app_labels = {
        "other",
    }

    def db_for_read(self, model, **hints):
        """
        Attempts to read all models, _except_ other models, go to default db.
        """
        if model._meta.app_label not in self.route_everything_but_these_app_labels:
            return self.db
        return None

    def db_for_write(self, model, **hints):
        """
        Attempts to write all models, _except_ other models, go to default db.
        """
        if model._meta.app_label not in self.route_everything_but_these_app_labels:
            return self.db
        return None

    def allow_relation(self, obj1, obj2, **hints):
        """
        Allow relations if a model in other is _not_ involved.
        """
        if (
            obj1._meta.app_label not in self.route_everything_but_these_app_labels
            and obj2._meta.app_label not in self.route_everything_but_these_app_labels
        ):
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        Make sure all apps, _except_ other, only appear in the default database.
        """
        if app_label not in self.route_everything_but_these_app_labels:
            return db == self.db
        return None

"other" database:

class OtherRouter:
    """
    A router to control all database operations on models in the other application.
    """

    db = "other"
    route_app_labels = {
        "other",
        # "pghistory",
        # # Note: "pghistory" is installed as part of the default database
    }

    def db_for_read(self, model, **hints):
        """
        Attempts to read from other models go to other db.
        """
        if model._meta.app_label in self.route_app_labels:
            return self.db
        return None

    def db_for_write(self, model, **hints):
        """
        Attempts to write to other models go to other db.
        """
        if model._meta.app_label in self.route_app_labels:
            return self.db
        return None

    def allow_relation(self, obj1, obj2, **hints):
        """
        Allow relations if both models are in the `route_app_labels`.
        """
        if obj1._meta.app_label in self.route_app_labels and obj2._meta.app_label in self.route_app_labels:
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        Make sure the other apps appear in the other db.
        """
        if app_label in self.route_app_labels:
            return db == self.db
        return None

These are both added to the DATABASES settings, with "default" first.

My model is along these lines ...

@pghistory.track(
    pghistory.InsertEvent(),
    pghistory.UpdateEvent(),
    pghistory.DeleteEvent(),
    context_field=pghistory.ContextJSONField(),
    context_id_field=None,
    obj_field=None,
)
class Capability(AbstractBareBonesModel, PubIdMixin, EnabledMixin, NameMixin):
    pass

When I insert using SQL or through the Python shell, the corresponding event record is inserted into the proper event table.

When I do the following via the Django shell, it is inserted, but the pgh_context field is not set (it remains null).

import pghistory
import ulid
from django.utils import timezone
from blah.apps.other.models.blah import Capability

capability = None

with pghistory.context(test_key0="test value 0"):
    capability = Capability.objects.create(
        enabled=False,
        start_at=timezone.now(),
        internal_name="TEST",
        display_name="testing 123",
        pub_id=ulid.new(),
    )

capability.__dict__
# everything is correct in the db _except_ `pgh_context`

with pghistory.context(test_key1="test value 1"):
    capability.display_name = "Testing 123"
    capability.save()

capability.refresh_from_db()
capability.__dict__
# everything is correct in the db _except_ `pgh_context`

with pghistory.context(test_key2="test value 2"):
    capability.enabled = True
    capability.save()

capability.refresh_from_db()
capability.__dict__
# everything is correct in the db _except_ `pgh_context`

From an initial investigation (by overriding pghistory.context), it appears that the with clauses are being executed with the expected key/value combination. It further appears that _inject_history_context is not being invoked (or, if it is, is not having the intended effect).

What am I doing wrong?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions