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

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/ 

5 

6https://github.com/tayloredwebsites/healthy-meals - healthy_meals/base_model.py 

7''' 

8 

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 

16 

17 

18class BaseModel(SafeDeleteModel): 

19 """ BaseModel is abstract class to base all models in this project 

20 

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 

25 

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 

30 

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 

35 

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. 

52 

53 class Meta: 

54 abstract = True 

55 

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() 

59 

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] 

64 

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] 

69 

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] 

76 

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']