From e2fc83cd33e4dd665e94a4a2a29b228d6abde819 Mon Sep 17 00:00:00 2001 From: Jonas Metzener Date: Wed, 2 Apr 2025 16:35:39 +0200 Subject: [PATCH] fix(history): make history aware of potential proxy models --- caluma/caluma_core/models.py | 58 ++++++++++++++++++- .../tests/test_proxy_model_history.py | 18 ++++++ 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 caluma/caluma_workflow/tests/test_proxy_model_history.py diff --git a/caluma/caluma_core/models.py b/caluma/caluma_core/models.py index 7bbafb2bf..8e99a4914 100644 --- a/caluma/caluma_core/models.py +++ b/caluma/caluma_core/models.py @@ -1,6 +1,7 @@ import uuid_extensions +from django.apps import apps from django.db import models -from simple_history.models import HistoricalRecords +from simple_history.models import HistoricalRecords, registered_models def _history_user_getter(historical_instance): @@ -17,8 +18,61 @@ def _history_user_setter(historical_instance, user): historical_instance.history_user_id = user +class ProxyAwareHistoricalRecords(HistoricalRecords): + """Historical records with shared history for proxy models. + + This is a workaround for https://github.com/jazzband/django-simple-history/issues/544. + + Copied from https://github.com/jazzband/django-simple-history/issues/544#issuecomment-1538615799 + """ + + def _find_base_history(self, opts): + base_history = None + for parent_class in opts.parents.keys(): + if hasattr(parent_class, "history"): + base_history = parent_class.history.model + return base_history + + def create_history_model(self, model, inherited): + opts = model._meta + if opts.proxy: + base_history = self._find_base_history(opts) + if base_history: + return self.create_proxy_history_model(model, inherited, base_history) + + return super().create_history_model(model, inherited) + + def create_proxy_history_model(self, model, inherited, base_history): + opts = model._meta + attrs = { + "__module__": self.module, + "_history_excluded_fields": self.excluded_fields, + } + app_module = f"{opts.app_label}.models" + if inherited: + attrs["__module__"] = model.__module__ + elif model.__module__ != self.module: # pragma: no cover + # registered under different app + attrs["__module__"] = self.module + elif app_module != self.module: # pragma: no cover + # Abuse an internal API because the app registry is loading. + app = apps.app_configs[opts.app_label] + models_module = app.name + attrs["__module__"] = models_module + + attrs.update( + Meta=type("Meta", (), {**self.get_meta_options(model), "proxy": True}) + ) + if self.table_name is not None: # pragma: no cover + attrs["Meta"].db_table = self.table_name + + name = self.get_history_model_name(model) + registered_models[opts.db_table] = model + return type(str(name), (base_history,), attrs) + + class HistoricalModel(models.Model): - history = HistoricalRecords( + history = ProxyAwareHistoricalRecords( inherit=True, history_user_id_field=models.CharField(null=True, max_length=150), history_user_setter=_history_user_setter, diff --git a/caluma/caluma_workflow/tests/test_proxy_model_history.py b/caluma/caluma_workflow/tests/test_proxy_model_history.py new file mode 100644 index 000000000..c64ad7915 --- /dev/null +++ b/caluma/caluma_workflow/tests/test_proxy_model_history.py @@ -0,0 +1,18 @@ +from caluma.caluma_workflow.models import WorkItem + + +def test_proxy_model_history(db, work_item): + class ProxyWorkItem(WorkItem): + class Meta: + proxy = True + + proxy_work_item = ProxyWorkItem.objects.get(pk=work_item.pk) + + assert work_item.history.count() == 1 + assert proxy_work_item.history.count() == 1 + + proxy_work_item.name = "Foo" + proxy_work_item.save() + + assert work_item.history.count() == 2 + assert proxy_work_item.history.count() == 2