Coverage for website/mailinglists/models.py: 69.00%
70 statements
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1from django.core import validators
2from django.core.exceptions import ValidationError
3from django.db import models
4from django.utils import timezone
5from django.utils.translation import gettext_lazy as _
7from activemembers.models import Board, MemberGroup
8from mailinglists.services import get_member_email_addresses
9from members.models import Member
10from utils.snippets import datetime_to_lectureyear
13def get_automatic_mailinglists():
14 """Return mailing list names that should be generated automatically."""
15 lectureyear = datetime_to_lectureyear(timezone.now())
16 list_names = [
17 "leden",
18 "members",
19 "begunstigers",
20 "benefactors",
21 "ereleden",
22 "honorary",
23 "mentors",
24 "activemembers",
25 "commissievoorzitters",
26 "committeechairs",
27 "optin",
28 "oldboards",
29 "oudbesturen",
30 "oldmembers",
31 ]
32 if Board.objects.exists(): 32 ↛ 33line 32 didn't jump to line 33 because the condition on line 32 was never true
33 for year in range(Board.objects.earliest("since").since.year, lectureyear):
34 board = Board.objects.filter(since__year=year).first()
35 if board is not None:
36 years = str(board.since.year)[-2:] + str(board.until.year)[-2:]
37 list_names += [f"bestuur{years}", f"board{years}"]
38 return list_names
41class MailingList(models.Model):
42 """Model describing mailing lists."""
44 name_validators = [
45 validators.RegexValidator(
46 regex=r"^[a-zA-Z0-9-]+$", message=_("Enter a simpler name")
47 ),
48 validators.RegexValidator(
49 regex=r"^(?!(abuse|admin|administrator|hostmaster|majordomo|postmaster|root|ssl-admin|webmaster)$)",
50 message=_("The entered name is a reserved value"),
51 ),
52 ]
54 name = models.CharField(
55 verbose_name=_("Name"),
56 max_length=60,
57 validators=name_validators,
58 unique=True,
59 help_text=_("Enter the name for the list (i.e. name@thalia.nu)."),
60 )
62 active_gsuite_name = models.CharField(
63 verbose_name=_("Active GSuite name"),
64 max_length=60,
65 validators=name_validators,
66 blank=True,
67 null=True,
68 unique=True,
69 )
71 description = models.TextField(
72 verbose_name=_("Description"),
73 help_text=_("Write a description for the mailinglist."),
74 )
76 moderated = models.BooleanField(
77 verbose_name=_("Moderated"),
78 default=False,
79 help_text=_("Indicate whether emails to the list require approval."),
80 )
82 members = models.ManyToManyField(
83 Member,
84 verbose_name=_("Members"),
85 blank=True,
86 help_text=_("Select individual members to include in the list."),
87 )
89 member_groups = models.ManyToManyField(
90 MemberGroup,
91 verbose_name=_("Member groups"),
92 help_text=_("Select entire groups to include in the list."),
93 blank=True,
94 )
96 def all_addresses(self):
97 """Return all addresses subscribed to this mailing list."""
98 for member in self.members.all(): 98 ↛ 99line 98 didn't jump to line 99 because the loop on line 98 never started
99 for email in get_member_email_addresses(member):
100 if email:
101 yield email
103 for group in self.member_groups.all().prefetch_related("members"): 103 ↛ 104line 103 didn't jump to line 104 because the loop on line 103 never started
104 for member in group.members.exclude(
105 membergroupmembership__until__lt=timezone.now().date()
106 ):
107 for email in get_member_email_addresses(member):
108 if email:
109 yield email
111 for verbatimaddress in self.addresses.all():
112 if verbatimaddress.address: 112 ↛ 111line 112 didn't jump to line 111 because the condition on line 112 was always true
113 yield verbatimaddress.address
115 def save(self, **kwargs):
116 if not self.active_gsuite_name: 116 ↛ 118line 116 didn't jump to line 118 because the condition on line 116 was always true
117 self.active_gsuite_name = self.name
118 super().save(**kwargs)
120 def clean(self):
121 """Validate the mailing list."""
122 super().clean()
123 if (
124 ListAlias.objects.filter(alias=self.name).exists()
125 or self.name in get_automatic_mailinglists()
126 ):
127 raise ValidationError(
128 {
129 "name": _(
130 "%(model_name)s with this %(field_label)s already exists."
131 )
132 % {"model_name": _("Mailing list"), "field_label": _("List alias")}
133 }
134 )
136 def __str__(self):
137 """Return the name of the mailing list."""
138 return self.name
141class VerbatimAddress(models.Model):
142 """Model that describes an email address subscribed to a mailing list."""
144 address = models.EmailField(
145 verbose_name=_("Email address"),
146 help_text=_("Enter an explicit email address to include in the list."),
147 )
149 mailinglist = models.ForeignKey(
150 MailingList,
151 verbose_name=_("Mailing list"),
152 on_delete=models.CASCADE,
153 related_name="addresses",
154 )
156 def __str__(self):
157 """Return the address."""
158 return self.address
160 class Meta:
161 """Meta class for VerbatimAddress."""
163 verbose_name = _("Verbatim address")
164 verbose_name_plural = _("Verbatim addresses")
167class ListAlias(models.Model):
168 """Model describing an alias of a mailing list."""
170 alias = models.CharField(
171 verbose_name=_("Alternative name"),
172 max_length=100,
173 validators=[
174 validators.RegexValidator(
175 regex=r"^[a-zA-Z0-9]+$", message=_("Enter a simpler name")
176 )
177 ],
178 unique=True,
179 help_text=_("Enter an alternative name for the list."),
180 )
181 mailinglist = models.ForeignKey(
182 MailingList,
183 verbose_name=_("Mailing list"),
184 on_delete=models.CASCADE,
185 related_name="aliases",
186 )
188 def clean(self):
189 """Validate the alias."""
190 super().clean()
191 if (
192 MailingList.objects.filter(name=self.alias).exists()
193 or self.alias in get_automatic_mailinglists()
194 ):
195 raise ValidationError(
196 {
197 "alias": _(
198 "%(model_name)s with this %(field_label)s already exists."
199 )
200 % {"model_name": _("Mailing list"), "field_label": _("Name")}
201 }
202 )
204 def __str__(self):
205 """Return a string representation of the alias and mailing list."""
206 return _("List alias {alias} for {list}").format(
207 alias=self.alias, list=self.mailinglist.name
208 )
210 class Meta:
211 """Meta class for ListAlias."""
213 verbose_name = _("List alias")
214 verbose_name_plural = _("List aliases")