From 648a414f0e684096a18dbbcd43ff8735c7284467 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 17 May 2025 21:05:57 +0930 Subject: [PATCH] feat(core): Dynamic History model creation ref: #765 #758 #759 --- app/core/models/audit.py | 3 +- app/core/models/centurion.py | 31 ++++++++++ app/core/models/meta.py | 62 +++++++++++++++++++ .../centurion_erp/development/models.md | 13 +--- 4 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 app/core/models/meta.py diff --git a/app/core/models/audit.py b/app/core/models/audit.py index 277c019d..cf7911f9 100644 --- a/app/core/models/audit.py +++ b/app/core/models/audit.py @@ -143,7 +143,8 @@ class ModelHistory( TenancyObject (_type_): Centurion Tenancy Abstract model. """ - audit_enabled: bool = False + _audit_enabled: bool = False + """Don't Save audit history for audit history model""" class Meta: diff --git a/app/core/models/centurion.py b/app/core/models/centurion.py index dee6293d..fbdfd18a 100644 --- a/app/core/models/centurion.py +++ b/app/core/models/centurion.py @@ -11,6 +11,13 @@ class CenturionModel( + _audit_enabled: bool = True + """Should this model have audit history kept""" + + _is_submodel: bool = False + """This model a sub-model""" + + class Meta: abstract = True @@ -22,3 +29,27 @@ class CenturionModel( if value is None: raise ValidationError(code = 'field_value_not_none', message = 'Value can not be none.') + + + + def get_history_model_name(self) -> str: + """Get the name for the History Model + + Returns: + str: Name of the history model (`AuditHistory`) + """ + + return f'{self._meta.object_name}AuditHistory' + + +class CenturionSubModel( + CenturionModel +): + + _is_submodel: bool = True + """This model a sub-model""" + + + class Meta: + + abstract = True diff --git a/app/core/models/meta.py b/app/core/models/meta.py new file mode 100644 index 00000000..0c9db3e8 --- /dev/null +++ b/app/core/models/meta.py @@ -0,0 +1,62 @@ +import sys +import types + +from django.apps import apps +from django.db import models +from django.utils.module_loading import import_string + + + +module_path = f'centurion.models.meta' + +if module_path not in sys.modules: + + sys.modules[module_path] = types.ModuleType(module_path) + + +if any(cmd in sys.argv for cmd in ['runserver', 'makemigrations', 'migrate']): + + if apps.models_ready: + + existing_models = { m.__name__ for m in apps.get_models() } + + for model in apps.get_models(): + + if not getattr(model, '_audit_enabled', False): + continue + + name = model.__name__ + + audit_meta_name = model().get_history_model_name() + + if audit_meta_name in existing_models: + continue + + + AuditMetaModel = type( + audit_meta_name, + ( import_string("core.models.audit.AuditMetaModel"), ), + { + '__module__': module_path, + '__qualname__': audit_meta_name, + '__doc__': f'Auto-generated meta model for {name} Audit History.', + 'Meta': type('Meta', (), { + 'app_label': model._meta.app_label, + 'db_table': model._meta.db_table + '_history', + 'managed': True, + 'verbose_name': model._meta.verbose_name + ' History', + 'verbose_name_plural': model._meta.verbose_name + ' Histories', + }), + 'model': models.ForeignKey( + model, + blank = False, + help_text = 'Model this history belongs to', + null = False, + on_delete = models.CASCADE, + related_name = 'audit_history', + verbose_name = 'Model', + ) + } + ) + + setattr(sys.modules[module_path], audit_meta_name, AuditMetaModel) diff --git a/docs/projects/centurion_erp/development/models.md b/docs/projects/centurion_erp/development/models.md index 53dcf582..9f8c0ade 100644 --- a/docs/projects/centurion_erp/development/models.md +++ b/docs/projects/centurion_erp/development/models.md @@ -205,18 +205,7 @@ table_fields: list = [ ## History -Adding History to a model is a simple process. Please see the [Model History](./core/model_history.md) docs. - -In the case the model you are creating is inherited from another model, (a non-abstrct model), you may need to add the following variables to the inherited class so that the model link works: - -- `history_app_label` The application label for the model in question - -- `history_model_name` The model name for the model in question. - - -### Example - -If you created a model called employee in the human_resources app, which is a sub-model that inherits from `Contact`, `Person` then `Entity`. In this case the `Entity` model is where the history is derived, which would create link `/access/entity//history`. This link is incorrect. adding variables `history_app_label = 'human_resources'` and `history_model_name = 'Emplyee'` to the `Employee` model class; will now create a valid link, `/human_resources/employee//history`. +Adding [History](./core/model_history.md) to a model is automatic. If there is a desire not to have model history it can be disabled by adding attribute `_audit_enabled` to the model class and setting its value to `False.` ## Tests