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
« 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
5"""Announcement model."""
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
13from weblate.lang.models import Language
14from weblate.trans.actions import ActionEvents
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()
24 if language and project is None and component is None:
25 return base.filter(project=None, component=None, language=language)
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 )
36 return base.filter(
37 (Q(component=component) & Q(language=None))
38 | (Q(project=component.project) & Q(component=None))
39 )
41 if project:
42 return base.filter(Q(project=project) & Q(component=None))
44 # All are None
45 return base.filter(project=None, component=None, language=None)
47 def create(self, user=None, **kwargs):
48 from weblate.trans.models.change import Change
50 result = super().create(**kwargs)
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
65class AnnouncementQuerySet(models.QuerySet):
66 def order(self):
67 return self.order_by("id")
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 )
133 objects = AnnouncementManager.from_queryset(AnnouncementQuerySet)()
135 class Meta:
136 app_label = "trans"
137 verbose_name = "Announcement"
138 verbose_name_plural = "Announcements"
140 def __str__(self) -> str:
141 return self.message
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