Coverage for src / mesh / models / orm / log_models.py: 95%

44 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-05-04 12:41 +0000

1from enum import Enum, unique 

2from typing import TYPE_CHECKING, Any, Self 

3 

4from django.db import models 

5 

6# from django.http import HttpRequest 

7from django.utils.translation import gettext_lazy as _ 

8 

9from mesh.models.orm.base_models import BaseChangeTrackingModel 

10from mesh.models.orm.user_models import User 

11 

12if TYPE_CHECKING: 

13 from django.utils import timezone 

14 

15 

16@unique 

17class LogType(Enum): 

18 # Message automatically created following user actions. 

19 GENERATED = "generated" 

20 # Message explicitely entered by an user. 

21 MANUAL = "manual" 

22 

23 

24LOG_TYPES = [lt.value for lt in LogType] 

25LOG_TYPE_CHOICES = [ 

26 (LogType.GENERATED.value, _("Auto generated")), 

27 (LogType.MANUAL.value, _("Manual entry")), 

28] 

29 

30 

31class ModelLog(BaseChangeTrackingModel): 

32 """ 

33 Base class to create a simple log table attached to an entity. 

34 

35 TODO: Always store the message text ? For auto generated messages (ex: new version), 

36 store a unique message code which could enable message lookup somewhere else ? 

37 """ 

38 

39 attached_to: None | models.ForeignKey = None 

40 # Log content (French) 

41 content = models.TextField(blank=True, null=True) 

42 # Translated content (English) 

43 content_en = models.TextField(blank=True, null=True) 

44 # Log message code (little helper that can fasten message browsing) - Optional 

45 code = models.CharField(max_length=128, null=True, blank=True) 

46 # Log type - Automatically generated or explicite user entry 

47 type = models.CharField( 

48 verbose_name=_("Message type"), 

49 max_length=32, 

50 choices=LOG_TYPE_CHOICES, 

51 default=LogType.GENERATED.value, 

52 ) 

53 impersonated_by = models.ForeignKey( 

54 User, 

55 on_delete=models.SET_NULL, 

56 null=True, 

57 editable=False, 

58 related_name="+", 

59 ) 

60 significant = models.BooleanField(verbose_name=_("Significant log"), default=False) 

61 

62 class Meta: 

63 abstract = True 

64 ordering = ["-date_created"] 

65 

66 @classmethod 

67 def add_message( 

68 cls, 

69 attached_to: models.Model, 

70 content: str | None = None, 

71 content_en: str | None = None, 

72 user=None, 

73 # impersonate_data=None, 

74 # request: HttpRequest | None = None, 

75 type: str = "", 

76 code: str = "", 

77 significant: bool = False, 

78 date: "timezone.datetime | None" = None, 

79 ) -> Self: 

80 """ 

81 Adds an entry to the model log. 

82 Automatically checks if the request is performed with active impersonate mode. 

83 """ 

84 kwargs: dict[str, Any] = {"attached_to": attached_to, "significant": significant} 

85 if content: 

86 kwargs["content"] = content 

87 if content_en: 

88 kwargs["content_en"] = content_en 

89 if type and type in LOG_TYPES: 

90 kwargs["type"] = type 

91 if code: 

92 kwargs["code"] = code 

93 

94 new_message = cls(**kwargs) 

95 

96 if user: 

97 if user.impersonate_data: 

98 new_message.impersonated_by_id = user.impersonate_data.source.pk # type: ignore 

99 

100 if user.is_authenticated: 

101 new_message._user = user # type: ignore 

102 # if request.user.is_authenticated: 

103 # new_message._user = request.user # type: ignore 

104 

105 if date: 

106 new_message.override_saved_date( 

107 date_created=date, 

108 date_last_modified=date, 

109 last_modified_by_user=user, 

110 created_by_user=user, 

111 ) 

112 new_message.save() 

113 

114 return new_message