Coverage for website/members/admin.py: 45.39%

126 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2025-08-14 10:31 +0000

1"""Register admin pages for the models.""" 

2 

3import csv 

4import datetime 

5 

6from django.contrib import admin, messages 

7from django.contrib.auth import get_user_model 

8from django.contrib.auth.admin import UserAdmin as BaseUserAdmin 

9from django.db.models import Count, Q 

10from django.http import HttpResponse 

11from django.utils import timezone 

12from django.utils.translation import gettext_lazy as _ 

13 

14from activemembers.models import MemberGroupMembership 

15from members import services 

16from members.models import EmailChange, Member 

17 

18from . import forms, models 

19 

20 

21class ActiveMemberInline(admin.TabularInline): 

22 model = MemberGroupMembership 

23 classes = ["collapse"] 

24 extra = 0 

25 

26 def has_change_permission(self, request, obj=None): 

27 return False 

28 

29 def has_add_permission(self, request, obj=None): 

30 return False 

31 

32 def has_delete_permission(self, request, obj=None): 

33 return False 

34 

35 

36class MembershipInline(admin.StackedInline): 

37 model = models.Membership 

38 classes = ["collapse"] 

39 extra = 0 

40 

41 

42class ProfileInline(admin.StackedInline): 

43 fields = [ 

44 "starting_year", 

45 "programme", 

46 "address_street", 

47 "address_street2", 

48 "address_postal_code", 

49 "address_city", 

50 "address_country", 

51 "student_number", 

52 "phone_number", 

53 "receive_optin", 

54 "receive_registration_confirmation", 

55 "receive_newsletter", 

56 "receive_oldmembers", 

57 "birthday", 

58 "show_birthday", 

59 "initials", 

60 "nickname", 

61 "display_name_preference", 

62 "profile_description", 

63 "website", 

64 "photo", 

65 "emergency_contact", 

66 "emergency_contact_phone_number", 

67 "event_permissions", 

68 ] 

69 classes = ["collapse"] 

70 model = models.Profile 

71 can_delete = False 

72 

73 def get_fields(self, request, obj=None): 

74 fields = super().get_fields(request, obj) 

75 if obj and obj.is_staff: 

76 fields = fields + ["email_gsuite_only"] 

77 return fields 

78 

79 

80class MembershipTypeListFilter(admin.SimpleListFilter): 

81 title = _("current membership type") 

82 parameter_name = "membership" 

83 

84 def lookups(self, request, model_admin): 

85 return models.Membership.MEMBERSHIP_TYPES + (("none", _("None")),) 

86 

87 def queryset(self, request, queryset): 

88 if not self.value(): 

89 return queryset 

90 if self.value() == "none": 

91 return queryset.exclude( 

92 Q(membership__until__isnull=True) 

93 | Q(membership__until__gt=timezone.now().date()), 

94 membership__isnull=False, 

95 ) 

96 

97 return queryset.exclude(membership=None).filter( 

98 Q(membership__until__isnull=True) 

99 | Q(membership__until__gt=timezone.now().date()), 

100 membership__type=self.value(), 

101 ) 

102 

103 

104class AgeListFilter(admin.SimpleListFilter): 

105 title = _("Age") 

106 parameter_name = "birthday" 

107 

108 def lookups(self, request, model_admin): 

109 return ( 

110 ("18+", _("≥ 18")), 

111 ("18-", _("< 18")), 

112 ("unknown", _("Unknown")), 

113 ) 

114 

115 def queryset(self, request, queryset): 

116 if not self.value(): 

117 return queryset 

118 

119 today = datetime.date.today() 

120 eightteen_years_ago = today.replace(year=today.year - 18) 

121 

122 if self.value() == "unknown": 

123 return queryset.filter(profile__birthday__isnull=True) 

124 if self.value() == "18+": 

125 return queryset.filter(profile__birthday__lte=eightteen_years_ago) 

126 if self.value() == "18-": 

127 return queryset.filter(profile__birthday__gt=eightteen_years_ago) 

128 

129 return queryset 

130 

131 

132class HasPermissionsFilter(admin.SimpleListFilter): 

133 title = _("Has individual permissions") 

134 parameter_name = "permissions" 

135 

136 def lookups(self, request, model_admin): 

137 return ( 

138 ("yes", _("Yes")), 

139 ("no", _("No")), 

140 ) 

141 

142 def queryset(self, request, queryset): 

143 if not self.value(): 

144 return queryset 

145 

146 queryset = queryset.annotate(permission_count=Count("user_permissions")) 

147 

148 if self.value() == "yes": 

149 return queryset.filter(permission_count__gt=0) 

150 

151 return queryset.filter(permission_count=0) 

152 

153 

154class UserAdmin(BaseUserAdmin): 

155 form = forms.UserChangeForm 

156 add_form = forms.UserCreationForm 

157 

158 actions = [ 

159 "address_csv_export", 

160 "student_number_csv_export", 

161 "email_csv_export", 

162 "minimise_data", 

163 ] 

164 

165 inlines = ( 

166 ProfileInline, 

167 MembershipInline, 

168 ActiveMemberInline, 

169 ) 

170 list_filter = ( 

171 MembershipTypeListFilter, 

172 "is_superuser", 

173 HasPermissionsFilter, 

174 "groups", 

175 AgeListFilter, 

176 "profile__event_permissions", 

177 "profile__receive_optin", 

178 "profile__receive_newsletter", 

179 "profile__receive_oldmembers", 

180 "profile__starting_year", 

181 ) 

182 

183 add_fieldsets = [ 

184 ( 

185 None, 

186 { 

187 "classes": ["wide"], 

188 "fields": [ 

189 "username", 

190 "first_name", 

191 "last_name", 

192 "email", 

193 "password1", 

194 "password2", 

195 ], 

196 }, 

197 ), 

198 ] 

199 

200 fieldsets = ( 

201 ( 

202 _("Personal"), 

203 {"fields": ("first_name", "last_name", "email", "username", "password")}, 

204 ), 

205 ( 

206 _("Permissions"), 

207 { 

208 "fields": ( 

209 "is_active", 

210 "is_staff", 

211 "is_superuser", 

212 "groups", 

213 "user_permissions", 

214 "date_joined", 

215 "last_login", 

216 ), 

217 "classes": ("collapse",), 

218 }, 

219 ), 

220 ) 

221 

222 # Facet counts would crash for this admin. See #3584. 

223 show_facets = admin.ShowFacets.NEVER 

224 

225 def email_csv_export(self, request, queryset): 

226 response = HttpResponse(content_type="text/csv") 

227 response["Content-Disposition"] = 'attachment;filename="email.csv"' 

228 writer = csv.writer(response) 

229 writer.writerow([_("First name"), _("Last name"), _("Email")]) 

230 for user in queryset: 

231 writer.writerow( 

232 [ 

233 user.first_name, 

234 user.last_name, 

235 user.email, 

236 ] 

237 ) 

238 return response 

239 

240 email_csv_export.short_description = _( 

241 "Download email addresses for selected users" 

242 ) 

243 

244 def address_csv_export(self, request, queryset): 

245 response = HttpResponse(content_type="text/csv") 

246 response["Content-Disposition"] = 'attachment;\ 

247 filename="addresses.csv"' 

248 writer = csv.writer(response) 

249 writer.writerow( 

250 [ 

251 _("First name"), 

252 _("Last name"), 

253 _("Address"), 

254 _("Address line 2"), 

255 _("Postal code"), 

256 _("City"), 

257 _("Country"), 

258 ] 

259 ) 

260 for user in queryset.exclude(profile=None): 

261 writer.writerow( 

262 [ 

263 user.first_name, 

264 user.last_name, 

265 user.profile.address_street, 

266 user.profile.address_street2, 

267 user.profile.address_postal_code, 

268 user.profile.address_city, 

269 user.profile.get_address_country_display(), 

270 ] 

271 ) 

272 return response 

273 

274 address_csv_export.short_description = _("Download addresses for selected users") 

275 

276 def student_number_csv_export(self, request, queryset): 

277 response = HttpResponse(content_type="text/csv") 

278 response["Content-Disposition"] = 'attachment;filename="student_numbers.csv"' 

279 writer = csv.writer(response) 

280 writer.writerow([_("First name"), _("Last name"), _("Student number")]) 

281 for user in queryset.exclude(profile=None): 

282 writer.writerow( 

283 [user.first_name, user.last_name, user.profile.student_number] 

284 ) 

285 return response 

286 

287 student_number_csv_export.short_description = _( 

288 "Download student number export for selected users" 

289 ) 

290 

291 def minimise_data(self, request, queryset): 

292 processed = len( 

293 services.execute_data_minimisation( 

294 members=Member.objects.filter(pk__in=queryset) 

295 ) 

296 ) 

297 if processed == 0: 

298 self.message_user( 

299 request, 

300 _( 

301 "Data minimisation could not be executed " 

302 "for the selected user(s)." 

303 ), 

304 messages.ERROR, 

305 ) 

306 else: 

307 self.message_user( 

308 request, 

309 _("Data minimisation was executed for {} user(s).").format(processed), 

310 messages.SUCCESS, 

311 ) 

312 

313 minimise_data.short_description = _("Minimise data for the selected users") 

314 

315 

316@admin.register(models.Member) 

317class MemberAdmin(UserAdmin): 

318 def has_module_permission(self, request): 

319 return False 

320 

321 

322admin.site.register(EmailChange) 

323 

324# re-register User admin 

325admin.site.unregister(get_user_model()) 

326admin.site.register(get_user_model(), UserAdmin)