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

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 _ 

6 

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 

11 

12 

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 

39 

40 

41class MailingList(models.Model): 

42 """Model describing mailing lists.""" 

43 

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 ] 

53 

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 ) 

61 

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 ) 

70 

71 description = models.TextField( 

72 verbose_name=_("Description"), 

73 help_text=_("Write a description for the mailinglist."), 

74 ) 

75 

76 moderated = models.BooleanField( 

77 verbose_name=_("Moderated"), 

78 default=False, 

79 help_text=_("Indicate whether emails to the list require approval."), 

80 ) 

81 

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 ) 

88 

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 ) 

95 

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 

102 

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 

110 

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 

114 

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) 

119 

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 ) 

135 

136 def __str__(self): 

137 """Return the name of the mailing list.""" 

138 return self.name 

139 

140 

141class VerbatimAddress(models.Model): 

142 """Model that describes an email address subscribed to a mailing list.""" 

143 

144 address = models.EmailField( 

145 verbose_name=_("Email address"), 

146 help_text=_("Enter an explicit email address to include in the list."), 

147 ) 

148 

149 mailinglist = models.ForeignKey( 

150 MailingList, 

151 verbose_name=_("Mailing list"), 

152 on_delete=models.CASCADE, 

153 related_name="addresses", 

154 ) 

155 

156 def __str__(self): 

157 """Return the address.""" 

158 return self.address 

159 

160 class Meta: 

161 """Meta class for VerbatimAddress.""" 

162 

163 verbose_name = _("Verbatim address") 

164 verbose_name_plural = _("Verbatim addresses") 

165 

166 

167class ListAlias(models.Model): 

168 """Model describing an alias of a mailing list.""" 

169 

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 ) 

187 

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 ) 

203 

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 ) 

209 

210 class Meta: 

211 """Meta class for ListAlias.""" 

212 

213 verbose_name = _("List alias") 

214 verbose_name_plural = _("List aliases")