Skip to content

Commit eb801c8

Browse files
committed
feat(accounts): add default notification configuration to settings
1 parent 4b55951 commit eb801c8

7 files changed

Lines changed: 210 additions & 72 deletions

File tree

docs/admin/config.rst

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,106 @@ DEFAULT_MERGE_STYLE
723723
* :ref:`component`
724724
* :ref:`component-merge_style`
725725

726+
.. setting:: DEFAULT_NOTIFICATIONS
727+
728+
DEFAULT_NOTIFICATIONS
729+
---------------------
730+
731+
Default notification settings for every created user.
732+
733+
A list of 3-tuples consisting of notification scope, notification frequency and notification handler.
734+
735+
Scopes:
736+
737+
``0``
738+
:guilabel:`All`
739+
``10``
740+
:guilabel:`Watched`
741+
``20``
742+
:guilabel:`Administered`
743+
``30``
744+
:guilabel:`Project`
745+
``40``
746+
:guilabel:`Component`
747+
748+
Frequencies:
749+
750+
``0``
751+
:guilabel:`No notification`
752+
``1``
753+
:guilabel:`Instant notification`
754+
``2``
755+
:guilabel:`Daily digest`
756+
``3``
757+
:guilabel:`Weekly digest`
758+
``4``
759+
:guilabel:`Monthly digest`
760+
761+
Handlers:
762+
763+
``RepositoryNotification``
764+
:guilabel:`Operation was performed in the repository`
765+
``LockNotification``
766+
:guilabel:`Component was locked or unlocked`
767+
``LicenseNotification``
768+
:guilabel:`License was changed`
769+
``ParseErrorNotification``
770+
:guilabel:`Parse error occurred`
771+
``NewStringNotificaton``
772+
:guilabel:`String is available for translation`
773+
``NewContributorNotificaton``
774+
:guilabel:`Contributor made their first translation`
775+
``NewSuggestionNotificaton``
776+
:guilabel:`Suggestion was added`
777+
``LanguageTranslatedNotificaton``
778+
:guilabel:`Language was translated`
779+
``ComponentTranslatedNotificaton``
780+
:guilabel:`Component was translated`
781+
``NewCommentNotificaton``
782+
:guilabel:`Comment was added`
783+
``MentionCommentNotificaton``
784+
:guilabel:`You were mentioned in a comment`
785+
``LastAuthorCommentNotificaton``
786+
:guilabel:`Your translation received a comment`
787+
``TranslatedStringNotificaton``
788+
:guilabel:`String was edited by user`
789+
``ApprovedStringNotificaton``
790+
:guilabel:`String was approved`
791+
``ChangedStringNotificaton``
792+
:guilabel:`String was changed`
793+
``NewTranslationNotificaton``
794+
:guilabel:`New language was added or requested`
795+
``NewComponentNotificaton``
796+
:guilabel:`New translation component was created`
797+
``NewAnnouncementNotificaton``
798+
:guilabel:`Announcement was published`
799+
``NewAlertNotificaton``
800+
:guilabel:`New alert emerged in a component`
801+
``MergeFailureNotification``
802+
:guilabel:`Repository operation failed`
803+
``PendingSuggestionsNotification``
804+
:guilabel:`Pending suggestions exist`
805+
``ToDoStringsNotification``
806+
:guilabel:`Unfinished strings exist`
807+
808+
.. note::
809+
810+
This setting affects only newly created users.
811+
812+
Example:
813+
814+
.. code-block:: python
815+
816+
DEFAULT_NOTIFICATIONS = [
817+
(0, 1, "MentionCommentNotificaton"),
818+
(10, 1, "LastAuthorCommentNotificaton"),
819+
]
820+
821+
.. seealso::
822+
823+
* :ref:`notifications`
824+
825+
726826
.. setting:: DEFAULT_SHARED_TM
727827

728828
DEFAULT_SHARED_TM

docs/admin/install/docker.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,17 @@ Generic settings
10321032

10331033
Configures :setting:`DEFAULT_AUTO_WATCH`.
10341034

1035+
.. envvar:: WEBLATE_DEFAULT_NOTIFICATIONS
1036+
1037+
Configures :setting:`DEFAULT_NOTIFICATIONS`.
1038+
1039+
**Example:**
1040+
1041+
.. code-block:: yaml
1042+
1043+
environment:
1044+
WEBLATE_DEFAULT_NOTIFICATIONS: 0:1:MentionCommentNotificaton,10:1:LastAuthorCommentNotificaton
1045+
10351046
.. envvar:: WEBLATE_RATELIMIT_ATTEMPTS
10361047
.. envvar:: WEBLATE_RATELIMIT_LOCKOUT
10371048
.. envvar:: WEBLATE_RATELIMIT_WINDOW

weblate/accounts/data.py

Lines changed: 0 additions & 71 deletions
This file was deleted.

weblate/accounts/models.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
from unidecode import unidecode
3737

3838
from weblate.accounts.avatar import get_user_display
39-
from weblate.accounts.data import create_default_notifications
4039
from weblate.accounts.notifications import (
4140
NOTIFICATIONS,
4241
NotificationFrequency,
@@ -157,6 +156,61 @@ class WeblateAccountsConf(AppConf):
157156
r"{URL_PREFIX}/site.webmanifest$", # The request for the manifest is made without credentials
158157
)
159158

159+
DEFAULT_NOTIFICATIONS: ClassVar[
160+
list[tuple[NotificationScope, NotificationFrequency, str]]
161+
] = [
162+
(
163+
NotificationScope.SCOPE_ALL,
164+
NotificationFrequency.FREQ_INSTANT,
165+
"MentionCommentNotificaton",
166+
),
167+
(
168+
NotificationScope.SCOPE_WATCHED,
169+
NotificationFrequency.FREQ_INSTANT,
170+
"LastAuthorCommentNotificaton",
171+
),
172+
(
173+
NotificationScope.SCOPE_WATCHED,
174+
NotificationFrequency.FREQ_INSTANT,
175+
"MentionCommentNotificaton",
176+
),
177+
(
178+
NotificationScope.SCOPE_WATCHED,
179+
NotificationFrequency.FREQ_INSTANT,
180+
"NewAnnouncementNotificaton",
181+
),
182+
(
183+
NotificationScope.SCOPE_WATCHED,
184+
NotificationFrequency.FREQ_WEEKLY,
185+
"NewStringNotificaton",
186+
),
187+
(
188+
NotificationScope.SCOPE_ADMIN,
189+
NotificationFrequency.FREQ_INSTANT,
190+
"MergeFailureNotification",
191+
),
192+
(
193+
NotificationScope.SCOPE_ADMIN,
194+
NotificationFrequency.FREQ_INSTANT,
195+
"ParseErrorNotification",
196+
),
197+
(
198+
NotificationScope.SCOPE_ADMIN,
199+
NotificationFrequency.FREQ_INSTANT,
200+
"NewTranslationNotificaton",
201+
),
202+
(
203+
NotificationScope.SCOPE_ADMIN,
204+
NotificationFrequency.FREQ_INSTANT,
205+
"NewAlertNotificaton",
206+
),
207+
(
208+
NotificationScope.SCOPE_ADMIN,
209+
NotificationFrequency.FREQ_INSTANT,
210+
"NewAnnouncementNotificaton",
211+
),
212+
]
213+
160214
# Multi-level rate limiting for email notifications
161215
# Each tuple contains (max_emails, time_window_seconds)
162216
RATELIMIT_NOTIFICATION_LIMITS: ClassVar[list[tuple[int, int]]] = [
@@ -1329,3 +1383,13 @@ def create_profile_callback(sender, instance, created=False, **kwargs) -> None:
13291383
# Create subscriptions
13301384
if not instance.is_anonymous and not instance.is_bot:
13311385
create_default_notifications(instance)
1386+
1387+
1388+
def create_default_notifications(user: User) -> None:
1389+
if settings.DEFAULT_NOTIFICATIONS:
1390+
for scope, frequency, notification in settings.DEFAULT_NOTIFICATIONS:
1391+
user.subscription_set.get_or_create(
1392+
scope=scope,
1393+
notification=notification,
1394+
defaults={"frequency": frequency},
1395+
)

weblate/settings_docker.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
get_env_ratelimit,
3131
get_env_redis_url,
3232
get_env_str,
33+
get_env_tuples_or_none,
3334
get_saml_idp,
3435
modify_env_list,
3536
)
@@ -1464,6 +1465,11 @@
14641465

14651466
DEFAULT_AUTO_WATCH = get_env_bool("WEBLATE_DEFAULT_AUTO_WATCH", True)
14661467

1468+
default_notifications = get_env_tuples_or_none("WEBLATE_DEFAULT_NOTIFICATIONS")
1469+
if default_notifications is not None:
1470+
DEFAULT_NOTIFICATIONS = default_notifications
1471+
del default_notifications
1472+
14671473
DEFAULT_SHARED_TM = get_env_bool("WEBLATE_DEFAULT_SHARED_TM", True)
14681474

14691475
DEFAULT_AUTOCLEAN_TM = get_env_bool("WEBLATE_AUTOCLEAN_TM", False)

weblate/utils/environment.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,20 @@ def get_env_map(name: str, default: dict[str, str] | None = None) -> dict[str, s
119119
return default or {}
120120

121121

122+
def get_env_tuples_or_none(name: str) -> list[tuple] | None:
123+
"""
124+
Get list of tuples from environment.
125+
126+
parses 'a:b:c,x:y:z' into [('a','b','c'), ('x','y','z')]
127+
"""
128+
parsed_list = get_env_list_or_none(name)
129+
if parsed_list is not None:
130+
if parsed_list == [""]:
131+
return []
132+
return [tuple(e.split(":")) for e in parsed_list]
133+
return None
134+
135+
122136
def get_env_int_or_none(name: str) -> int | None:
123137
"""Get integer value from environment."""
124138
string_value = get_env_str(name)

weblate/utils/tests/test_environment.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
get_env_ratelimit,
2323
get_env_redis_url,
2424
get_env_str,
25+
get_env_tuples_or_none,
2526
get_saml_idp,
2627
modify_env_list,
2728
)
@@ -181,6 +182,19 @@ def test_map_or_none(self) -> None:
181182
del os.environ["TEST_DATA"]
182183
self.assertIsNone(get_env_map_or_none("TEST_DATA"))
183184

185+
def test_tuples_or_none(self) -> None:
186+
os.environ["TEST_DATA"] = "foo:bar:baz,bag:bak:bam"
187+
self.assertEqual(
188+
get_env_tuples_or_none("TEST_DATA"),
189+
[("foo", "bar", "baz"), ("bag", "bak", "bam")],
190+
)
191+
os.environ["TEST_DATA"] = "foo:bar"
192+
self.assertEqual(get_env_tuples_or_none("TEST_DATA"), [("foo", "bar")])
193+
os.environ["TEST_DATA"] = ""
194+
self.assertEqual(get_env_tuples_or_none("TEST_DATA"), [])
195+
del os.environ["TEST_DATA"]
196+
self.assertIsNone(get_env_tuples_or_none("TEST_DATA"))
197+
184198
def test_bool(self) -> None:
185199
os.environ["TEST_DATA"] = "1"
186200
self.assertTrue(get_env_bool("TEST_DATA"))

0 commit comments

Comments
 (0)