Coverage for website/activemembers/admin.py: 57.60%
109 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
1import csv
2import datetime
4from django import forms
5from django.contrib import admin, messages
6from django.db.models import Q
7from django.http import HttpResponse
8from django.utils import timezone
9from django.utils.translation import gettext_lazy as _
11from activemembers import models
12from activemembers.forms import MemberGroupForm, MemberGroupMembershipForm
13from utils.snippets import datetime_to_lectureyear
16class MemberGroupMembershipInlineFormSet(forms.BaseInlineFormSet):
17 """Here for performance reasons, and to filter out old memberships.
19 Needed because the `__str__()` of `MemberGroupMembership` (which is
20 displayed above each inline form) uses the username, name of the member
21 and name of the group.
22 """
24 def __init__(self, *args, **kwargs):
25 """Initialize and set queryset."""
26 super().__init__(*args, **kwargs)
27 now = timezone.now()
28 self.queryset = self.queryset.select_related("member", "group")
30 # Show only memberships that are active now.
31 if not isinstance(self.instance, models.Board):
32 self.queryset = self.queryset.filter(
33 Q(until=None) | (Q(since__lte=now, until__gte=now))
34 )
37class MemberGroupMembershipInline(admin.StackedInline):
38 """Inline for group memberships."""
40 model = models.MemberGroupMembership
41 formset = MemberGroupMembershipInlineFormSet
42 can_delete = False
43 ordering = ("since",)
44 extra = 0
45 autocomplete_fields = ("member",)
48class MemberGroupAdmin(admin.ModelAdmin):
49 """Manage the member groups."""
51 inlines = (MemberGroupMembershipInline,)
52 form = MemberGroupForm
53 list_display = ("name", "since", "until", "active", "contact_address")
54 list_filter = (
55 "until",
56 "active",
57 )
58 search_fields = ("name", "description")
59 filter_horizontal = ("permissions", "chair_permissions")
61 fields = (
62 "name",
63 "description",
64 "photo",
65 "permissions",
66 "chair_permissions",
67 "since",
68 "until",
69 "contact_mailinglist",
70 "contact_email",
71 "active",
72 "display_members",
73 )
76@admin.register(models.Committee)
77class CommitteeAdmin(MemberGroupAdmin):
78 """Manage the committees."""
81@admin.register(models.Society)
82class SocietyAdmin(MemberGroupAdmin):
83 """Manage the societies."""
86@admin.register(models.Board)
87class BoardAdmin(admin.ModelAdmin):
88 """Manage the board."""
90 inlines = (MemberGroupMembershipInline,)
91 form = MemberGroupForm
92 exclude = ("is_board",)
93 filter_horizontal = ("permissions",)
95 fields = (
96 "name",
97 "description",
98 "photo",
99 "permissions",
100 "contact_mailinglist",
101 "contact_email",
102 "since",
103 "until",
104 "display_members",
105 )
108class TypeFilter(admin.SimpleListFilter):
109 """Filter memberships on board-only."""
111 title = _("group memberships")
112 parameter_name = "group_type"
114 def lookups(self, request, model_admin):
115 return [
116 ("boards", _("Only boards")),
117 ("committees", _("Only committees")),
118 ("societies", _("Only societies")),
119 ]
121 def queryset(self, request, queryset):
122 if self.value() == "boards":
123 return queryset.exclude(group__board=None)
124 if self.value() == "committees":
125 return queryset.exclude(group__committee=None)
126 if self.value() == "societies":
127 return queryset.exclude(group__society=None)
128 return queryset
131class LectureYearFilter(admin.SimpleListFilter):
132 """Filter the memberships on those started or ended in a lecture year."""
134 title = _("lecture year")
135 parameter_name = "lecture_year"
137 def lookups(self, request, model_admin):
138 current_year = datetime_to_lectureyear(timezone.now())
139 first_year = datetime_to_lectureyear(
140 models.MemberGroupMembership.objects.earliest("since").since
141 )
143 return [
144 (year, f"{year}-{year + 1}") for year in range(first_year, current_year + 1)
145 ]
147 def queryset(self, request, queryset):
148 if not self.value():
149 return queryset
151 year = int(self.value())
152 first_of_september = datetime.date(year=year, month=9, day=1)
154 return queryset.exclude(until__lt=first_of_september)
157class ActiveMembershipsFilter(admin.SimpleListFilter):
158 """Filter the memberships by whether they are active or not."""
160 title = _("active memberships")
161 parameter_name = "active"
163 def lookups(self, request, model_admin):
164 return (
165 ("active", _("Active")),
166 ("inactive", _("Inactive")),
167 )
169 def queryset(self, request, queryset):
170 now = timezone.now()
172 if self.value() == "active":
173 return queryset.filter(Q(until__isnull=True) | Q(until__gte=now))
174 if self.value() == "inactive":
175 return queryset.filter(until__lt=now)
176 return queryset
179@admin.register(models.MemberGroupMembership)
180class MemberGroupMembershipAdmin(admin.ModelAdmin):
181 """Manage the group memberships."""
183 form = MemberGroupMembershipForm
184 list_display = (
185 "member",
186 "group",
187 "since",
188 "until",
189 "chair",
190 "has_chair_permissions",
191 "role",
192 )
193 list_filter = ("group", TypeFilter, LectureYearFilter, ActiveMembershipsFilter)
194 list_select_related = (
195 "member",
196 "group",
197 )
198 search_fields = ("member__first_name", "member__last_name", "member__email")
199 date_hierarchy = "since"
200 actions = ("export",)
202 # Facet counts would crash for this admin.
203 show_facets = admin.ShowFacets.NEVER
205 def changelist_view(self, request, extra_context=None):
206 self.message_user(
207 request,
208 _(
209 "Do not edit existing memberships if the "
210 "chair of a group has changed, add a "
211 "new membership instead."
212 ),
213 messages.WARNING,
214 )
215 return super().changelist_view(request, extra_context)
217 def export(self, request, queryset):
218 response = HttpResponse(content_type="text/csv")
219 response["Content-Disposition"] = 'attachment; filename="group_memberships.csv"'
220 writer = csv.writer(response)
221 writer.writerow(
222 [
223 _("First name"),
224 _("Last name"),
225 _("Email"),
226 _("Group"),
227 _("Member since"),
228 _("Member until"),
229 _("Chair of the group"),
230 _("Role"),
231 ]
232 )
234 for membership in queryset:
235 writer.writerow(
236 [
237 membership.member.first_name,
238 membership.member.last_name,
239 membership.member.email,
240 membership.group,
241 membership.since,
242 membership.until,
243 membership.chair,
244 membership.role,
245 ]
246 )
248 return response
250 export.short_description = _("Export selected memberships")
253@admin.register(models.Mentorship)
254class MentorshipAdmin(admin.ModelAdmin):
255 """Manage the mentorships."""
257 autocomplete_fields = ("member",)
258 search_fields = ("member__first_name", "member__last_name")
259 list_filter = ("year",)