Coverage for common / base_model.py: 100%
33 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-03-10 14:10 +0000
« prev ^ index » next coverage.py v7.13.2, created at 2026-03-10 14:10 +0000
1'''
2Healthy Meals Web Site
3Copyright (C) 2025 David A. Taylor of Taylored Web Sites (tayloredwebsites.com)
4Licensed under AGPL-3.0-only. See https://opensource.org/license/agpl-v3/
6https://github.com/tayloredwebsites/healthy-meals - healthy_meals/base_model.py
7'''
9from django.db import models
10from safedelete.models import SafeDeleteModel
11from safedelete.models import SOFT_DELETE_CASCADE
12from safedelete.managers import SafeDeleteManager
13from auditlog.registry import auditlog
14from auditlog.models import AuditlogHistoryField
15from django.utils import timezone
18class BaseModel(SafeDeleteModel):
19 """ BaseModel is abstract class to base all models in this project
21 - has soft delete functionality included through django-safedelete
22 - https://django-safedelete.readthedocs.io/en/latest/index.html
23 - has record history / versioning through django-auditlog
24 - https://github.com/jazzband/django-auditlog
26 SOFT DELETE FUNCTIONALITY
27 Note: The customized functions for soft deletion are only found in model manager classes
28 Thus??: to use the methods found in 'objects', their models must declare their custom manager based off of SafeDeleteManager (This should be validated!!!)
29 See: accounts/models.py for an example
31 - all_with_deleted() # Show all model records including the soft deleted models.
32 - deleted_only() # Only show the soft deleted model records.
33 - all(**kwargs) -> django.db.models.query.QuerySet # Show deleted model records. (default: {None})
34 - update_or_create(defaults=None, **kwargs) -> Tuple[django.db.models.base.Model, bool] # https://django-safedelete.readthedocs.io/en/latest/managers.html#safedelete.managers.SafeDeleteManager.update_or_create
36 AUDITLOG VERSIONING HISTORY FUNCTIONALITY
37 - has record history / versioning through django-auditlog (https://github.com/jazzband/django-auditlog)
38 - this provides the ability to see all of the changes to fields (except fields excluded when registered in the model)
39 - see: https://django-auditlog.readthedocs.io/en/latest/usage.html
40 Note: To register auditlog to automatically log all changes to a model, it must be registered in the model
41 To register auditlog, the last line of the model should have the auditlog.register statement.
42 For Example (as can be seen in accounts/models.py):
43 auditlog.register(CustomUser, exclude_fields=[
44 'password', # protect this field for security reasons
45 'last_login', # do not update audit log for each login
46 ]
47 """
48 created_at = models.DateTimeField(default=timezone.now)
49 updated_at = models.DateTimeField(auto_now=True)
50 history = AuditlogHistoryField() # audit log to maintain record history
51 _safedelete_policy = SOFT_DELETE_CASCADE # cascade soft deletes of records as well as child records.
53 class Meta:
54 abstract = True
56 def rec_history_count(self):
57 '''Return the count of all of the history records for this user.'''
58 return self.history.all().count()
60 def rec_history_field_was(self, user_rec, field_name):
61 '''Return a dictionary of the previous values for this field, for this record.'''
62 rec = self.history.all()[user_rec]
63 return self.__get_field_changes(rec, field_name)[0]
65 def rec_history_field_is_now(self, user_rec, field_name):
66 '''Return the latest history record value for this field (should be identical to current field value)'''
67 rec = self.history.all()[user_rec]
68 return self.__get_field_changes(rec, field_name)[1]
70 def rec_history_field_changed(self, user_rec, field_name):
71 '''Return the number of records that are maintained in CustomUser's history table.'''
72 rec = self.history.all()[user_rec]
73 changes = self.__get_field_changes(rec, field_name)
74 # print(f'changes: {changes}')
75 return changes[0] != changes[1]
77 def __get_field_changes(self, hist_rec, field_name):
78 '''Return a dictionary of the history for this record's field values.'''
79 try:
80 changes = hist_rec.changes_dict[field_name]
81 # print(f'changes: {changes}')
82 return changes
83 except KeyError as e:
84 # there was no change, audit log does not log values that do not change, so return array of None strings
85 print(f'expected key error auditlog - no changes for field: {e}')
86 return ['None', 'None']