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

40 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-02-20 10:03 +0000

1from enum import Enum, unique 

2from typing import Any, Self 

3 

4from django.conf import settings 

5from django.db import models 

6 

7# from django.http import HttpRequest 

8from django.utils.translation import gettext_lazy as _ 

9 

10from .base_models import BaseChangeTrackingModel 

11 

12 

13@unique 

14class LogType(Enum): 

15 # Message automatically created following user actions. 

16 GENERATED = "generated" 

17 # Message explicitely entered by an user. 

18 MANUAL = "manual" 

19 

20 

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

22LOG_TYPE_CHOICES = [ 

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

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

25] 

26 

27 

28class ModelLog(BaseChangeTrackingModel): 

29 """ 

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

31 

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

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

34 """ 

35 

36 attached_to: None | models.ForeignKey = None 

37 # Log content (French) 

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

39 # Translated content (English) 

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

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

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

43 # Log type - Automatically generated or explicite user entry 

44 type = models.CharField( 

45 verbose_name=_("Message type"), 

46 max_length=32, 

47 choices=LOG_TYPE_CHOICES, 

48 default=LogType.GENERATED.value, 

49 ) 

50 impersonated_by = models.ForeignKey( 

51 settings.AUTH_USER_MODEL, 

52 on_delete=models.SET_NULL, 

53 null=True, 

54 editable=False, 

55 related_name="+", 

56 ) 

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

58 

59 class Meta: 

60 abstract = True 

61 

62 @classmethod 

63 def add_message( 

64 cls, 

65 attached_to: models.Model, 

66 content: str | None = None, 

67 content_en: str | None = None, 

68 user=None, 

69 # impersonate_data=None, 

70 # request: HttpRequest | None = None, 

71 type: str = "", 

72 code: str = "", 

73 significant: bool = False, 

74 ) -> Self: 

75 """ 

76 Adds an entry to the model log. 

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

78 """ 

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

80 if content: 

81 kwargs["content"] = content 

82 if content_en: 

83 kwargs["content_en"] = content_en 

84 if type and type in LOG_TYPES: 

85 kwargs["type"] = type 

86 if code: 

87 kwargs["code"] = code 

88 

89 new_message = cls(**kwargs) 

90 

91 if user.impersonate_data: 

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

93 

94 if user.is_authenticated: 

95 new_message._user = user # type: ignore 

96 # if request.user.is_authenticated: 

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

98 

99 new_message.save() 

100 

101 return new_message