Coverage for app/venv/lib/python3.14/site-packages/weblate/trans/models/comment.py: 68%

59 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 07:15 +0000

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4 

5from __future__ import annotations 

6 

7from typing import TYPE_CHECKING 

8 

9from django.conf import settings 

10from django.db import models 

11from django.utils import timezone 

12from weblate_language_data.utils import gettext_noop 

13 

14from weblate.trans.actions import ActionEvents 

15from weblate.trans.mixins import UserDisplayMixin 

16from weblate.utils.antispam import report_spam 

17from weblate.utils.request import get_ip_address, get_user_agent_raw 

18from weblate.utils.state import STATE_FUZZY 

19 

20if TYPE_CHECKING: 20 ↛ 21line 20 didn't jump to line 21 because the condition on line 20 was never true

21 from datetime import datetime 

22 

23 from weblate.auth.models import AuthenticatedHttpRequest, Unit, User 

24 

25 

26class CommentManager(models.Manager): 

27 def add( 

28 self, 

29 request: AuthenticatedHttpRequest, 

30 unit: Unit, 

31 text: str, 

32 scope: str, 

33 *, 

34 user: User | None = None, 

35 timestamp: datetime | None = None, 

36 ) -> Comment: 

37 """Add comment to this unit.""" 

38 # Is this source or target comment? 

39 unit_scope = unit.source_unit if scope in {"global", "report"} else unit 

40 

41 if user is None: 41 ↛ 42line 41 didn't jump to line 42 because the condition on line 41 was never true

42 user = request.user 

43 kwargs = { 

44 "user": user, 

45 "unit": unit_scope, 

46 "comment": text, 

47 "userdetails": { 

48 "address": get_ip_address(request), 

49 "agent": get_user_agent_raw(request), 

50 }, 

51 } 

52 if timestamp is not None: 

53 kwargs["timestamp"] = timestamp 

54 

55 new_comment = self.create(**kwargs) 

56 user.profile.increase_count("commented") 

57 unit_scope.change_set.create( 

58 comment=new_comment, 

59 action=ActionEvents.COMMENT, 

60 user=user, 

61 author=user, 

62 details={"comment": text}, 

63 ) 

64 

65 component = unit_scope.translation.component 

66 

67 # Add review label/flag 

68 if scope == "report": 68 ↛ 69line 68 didn't jump to line 69 because the condition on line 68 was never true

69 if component.has_template(): 

70 if unit_scope.translated and not unit_scope.readonly: 

71 unit_scope.translate( 

72 user, 

73 unit_scope.target, 

74 STATE_FUZZY, 

75 change_action=ActionEvents.MARKED_EDIT, 

76 ) 

77 else: 

78 label = component.project.label_set.get_or_create( 

79 name=gettext_noop("Source needs review"), defaults={"color": "red"} 

80 )[0] 

81 unit_scope.labels.add(label) 

82 

83 return new_comment 

84 

85 

86class CommentQuerySet(models.QuerySet): 

87 def order(self): 

88 return self.order_by("timestamp") 

89 

90 

91class Comment(models.Model, UserDisplayMixin): 

92 unit = models.ForeignKey("trans.Unit", on_delete=models.deletion.CASCADE) 

93 comment = models.TextField() 

94 user = models.ForeignKey( 

95 settings.AUTH_USER_MODEL, 

96 null=True, 

97 blank=True, 

98 on_delete=models.deletion.CASCADE, 

99 ) 

100 timestamp = models.DateTimeField(default=timezone.now, db_index=True) 

101 resolved = models.BooleanField(default=False, db_index=True) 

102 userdetails = models.JSONField(default=dict) 

103 

104 objects = CommentManager.from_queryset(CommentQuerySet)() 

105 

106 class Meta: 

107 app_label = "trans" 

108 verbose_name = "string comment" 

109 verbose_name_plural = "string comments" 

110 

111 def __str__(self) -> str: 

112 return "comment for {} by {}".format( 

113 self.unit, self.user.username if self.user else "unknown" 

114 ) 

115 

116 def report_spam(self) -> None: 

117 report_spam( 

118 self.userdetails["address"], self.userdetails["agent"], self.comment 

119 ) 

120 

121 def resolve(self, user: User) -> None: 

122 self.unit.change_set.create( 

123 comment=self, 

124 action=ActionEvents.COMMENT_RESOLVE, 

125 user=user, 

126 author=self.user, 

127 details={"comment": self.comment}, 

128 ) 

129 self.resolved = True 

130 self.save(update_fields=["resolved"]) 

131 

132 def delete(self, user=None, using=None, keep_parents=False) -> None: 

133 self.unit.change_set.create( 

134 action=ActionEvents.COMMENT_DELETE, 

135 user=user, 

136 author=self.user, 

137 details={"comment": self.comment}, 

138 ) 

139 super().delete(using=using, keep_parents=keep_parents)