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
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1"""Register admin pages for the models."""
3import csv
4import datetime
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 _
14from activemembers.models import MemberGroupMembership
15from members import services
16from members.models import EmailChange, Member
18from . import forms, models
21class ActiveMemberInline(admin.TabularInline):
22 model = MemberGroupMembership
23 classes = ["collapse"]
24 extra = 0
26 def has_change_permission(self, request, obj=None):
27 return False
29 def has_add_permission(self, request, obj=None):
30 return False
32 def has_delete_permission(self, request, obj=None):
33 return False
36class MembershipInline(admin.StackedInline):
37 model = models.Membership
38 classes = ["collapse"]
39 extra = 0
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
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
80class MembershipTypeListFilter(admin.SimpleListFilter):
81 title = _("current membership type")
82 parameter_name = "membership"
84 def lookups(self, request, model_admin):
85 return models.Membership.MEMBERSHIP_TYPES + (("none", _("None")),)
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 )
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 )
104class AgeListFilter(admin.SimpleListFilter):
105 title = _("Age")
106 parameter_name = "birthday"
108 def lookups(self, request, model_admin):
109 return (
110 ("18+", _("≥ 18")),
111 ("18-", _("< 18")),
112 ("unknown", _("Unknown")),
113 )
115 def queryset(self, request, queryset):
116 if not self.value():
117 return queryset
119 today = datetime.date.today()
120 eightteen_years_ago = today.replace(year=today.year - 18)
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)
129 return queryset
132class HasPermissionsFilter(admin.SimpleListFilter):
133 title = _("Has individual permissions")
134 parameter_name = "permissions"
136 def lookups(self, request, model_admin):
137 return (
138 ("yes", _("Yes")),
139 ("no", _("No")),
140 )
142 def queryset(self, request, queryset):
143 if not self.value():
144 return queryset
146 queryset = queryset.annotate(permission_count=Count("user_permissions"))
148 if self.value() == "yes":
149 return queryset.filter(permission_count__gt=0)
151 return queryset.filter(permission_count=0)
154class UserAdmin(BaseUserAdmin):
155 form = forms.UserChangeForm
156 add_form = forms.UserCreationForm
158 actions = [
159 "address_csv_export",
160 "student_number_csv_export",
161 "email_csv_export",
162 "minimise_data",
163 ]
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 )
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 ]
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 )
222 # Facet counts would crash for this admin. See #3584.
223 show_facets = admin.ShowFacets.NEVER
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
240 email_csv_export.short_description = _(
241 "Download email addresses for selected users"
242 )
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
274 address_csv_export.short_description = _("Download addresses for selected users")
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
287 student_number_csv_export.short_description = _(
288 "Download student number export for selected users"
289 )
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 )
313 minimise_data.short_description = _("Minimise data for the selected users")
316@admin.register(models.Member)
317class MemberAdmin(UserAdmin):
318 def has_module_permission(self, request):
319 return False
322admin.site.register(EmailChange)
324# re-register User admin
325admin.site.unregister(get_user_model())
326admin.site.register(get_user_model(), UserAdmin)