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

48 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 

5"""Announcement model.""" 

6 

7from django.core.exceptions import ValidationError 

8from django.db import models 

9from django.db.models import Q 

10from django.utils import timezone 

11from django.utils.translation import gettext, gettext_lazy 

12 

13from weblate.lang.models import Language 

14from weblate.trans.actions import ActionEvents 

15 

16 

17class AnnouncementManager(models.Manager["Announcement"]): 

18 def context_filter(self, project=None, component=None, language=None): 

19 """Filter announcements by context.""" 

20 base = self.filter( 

21 Q(expiry__isnull=True) | Q(expiry__gte=timezone.now()) 

22 ).order() 

23 

24 if language and project is None and component is None: 

25 return base.filter(project=None, component=None, language=language) 

26 

27 if component: 

28 if language: 

29 return base.filter( 

30 (Q(component=component) & Q(language=language)) 

31 | (Q(project=None) & Q(component=None) & Q(language=language)) 

32 | (Q(component=component) & Q(language=None)) 

33 | (Q(project=component.project) & Q(component=None)) 

34 ) 

35 

36 return base.filter( 

37 (Q(component=component) & Q(language=None)) 

38 | (Q(project=component.project) & Q(component=None)) 

39 ) 

40 

41 if project: 

42 return base.filter(Q(project=project) & Q(component=None)) 

43 

44 # All are None 

45 return base.filter(project=None, component=None, language=None) 

46 

47 def create(self, user=None, **kwargs): 

48 from weblate.trans.models.change import Change 

49 

50 result = super().create(**kwargs) 

51 

52 Change.objects.create( 

53 action=ActionEvents.ANNOUNCEMENT, 

54 project=result.project, 

55 category=result.category, 

56 component=result.component, 

57 language=result.language, 

58 announcement=result, 

59 target=result.message, 

60 user=user, 

61 ) 

62 return result 

63 

64 

65class AnnouncementQuerySet(models.QuerySet): 

66 def order(self): 

67 return self.order_by("id") 

68 

69 

70class Announcement(models.Model): 

71 message = models.TextField( 

72 verbose_name=gettext_lazy("Message"), 

73 help_text=gettext_lazy("You can use Markdown and mention users by @username."), 

74 ) 

75 project = models.ForeignKey( 

76 "trans.Project", 

77 verbose_name=gettext_lazy("Project"), 

78 null=True, 

79 blank=True, 

80 on_delete=models.deletion.CASCADE, 

81 ) 

82 category = models.ForeignKey( 

83 "trans.Category", 

84 verbose_name=gettext_lazy("Category"), 

85 null=True, 

86 blank=True, 

87 on_delete=models.deletion.CASCADE, 

88 ) 

89 component = models.ForeignKey( 

90 "trans.Component", 

91 verbose_name=gettext_lazy("Component"), 

92 null=True, 

93 blank=True, 

94 on_delete=models.deletion.CASCADE, 

95 ) 

96 language = models.ForeignKey( 

97 Language, 

98 verbose_name=gettext_lazy("Language"), 

99 null=True, 

100 blank=True, 

101 on_delete=models.deletion.CASCADE, 

102 ) 

103 severity = models.CharField( 

104 max_length=25, 

105 verbose_name=gettext_lazy("Severity"), 

106 help_text=gettext_lazy("Severity defines color used for the message."), 

107 choices=( 

108 ("info", gettext_lazy("Info (light blue)")), 

109 ("warning", gettext_lazy("Warning (yellow)")), 

110 ("danger", gettext_lazy("Danger (red)")), 

111 ("success", gettext_lazy("Success (green)")), 

112 ), 

113 default="info", 

114 ) 

115 expiry = models.DateField( 

116 null=True, 

117 blank=True, 

118 db_index=True, 

119 verbose_name=gettext_lazy("Expiry date"), 

120 help_text=gettext_lazy( 

121 "The message will be not shown after this date. " 

122 "Use it to announce string freeze and translation " 

123 "deadline for next release." 

124 ), 

125 ) 

126 notify = models.BooleanField( 

127 blank=True, 

128 default=False, 

129 verbose_name=gettext_lazy("Notify users"), 

130 help_text=gettext_lazy("Send notification to subscribed users."), 

131 ) 

132 

133 objects = AnnouncementManager.from_queryset(AnnouncementQuerySet)() 

134 

135 class Meta: 

136 app_label = "trans" 

137 verbose_name = "Announcement" 

138 verbose_name_plural = "Announcements" 

139 

140 def __str__(self) -> str: 

141 return self.message 

142 

143 def clean(self) -> None: 

144 if self.project and self.component and self.component.project != self.project: 

145 raise ValidationError(gettext("Do not specify both component and project!")) 

146 if not self.project and self.component: 

147 self.project = self.component.project