svthalia/concrexit

View on GitHub
website/activemembers/admin.py

Summary

Maintainability
A
0 mins
Test Coverage
"""Registers admin interfaces for the activemembers module."""
import csv
import datetime

from django import forms
from django.contrib import admin, messages
from django.db.models import Q
from django.http import HttpResponse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from activemembers import models
from activemembers.forms import MemberGroupForm, MemberGroupMembershipForm
from utils.snippets import datetime_to_lectureyear


class MemberGroupMembershipInlineFormSet(forms.BaseInlineFormSet):
    """Here for performance reasons, and to filter out old memberships.

    Needed because the `__str__()` of `MemberGroupMembership` (which is
    displayed above each inline form) uses the username, name of the member
    and name of the group.
    """

    def __init__(self, *args, **kwargs):
        """Initialize and set queryset."""
        super().__init__(*args, **kwargs)
        now = timezone.now()
        self.queryset = self.queryset.select_related("member", "group")

        # Show only memberships that are active now.
        if not isinstance(self.instance, models.Board):
            self.queryset = self.queryset.filter(
                Q(until=None) | (Q(since__lte=now, until__gte=now))
            )


class MemberGroupMembershipInline(admin.StackedInline):
    """Inline for group memberships."""

    model = models.MemberGroupMembership
    formset = MemberGroupMembershipInlineFormSet
    can_delete = False
    ordering = ("since",)
    extra = 0
    autocomplete_fields = ("member",)


class MemberGroupAdmin(admin.ModelAdmin):
    """Manage the member groups."""

    inlines = (MemberGroupMembershipInline,)
    form = MemberGroupForm
    list_display = ("name", "since", "until", "active", "contact_address")
    list_filter = (
        "until",
        "active",
    )
    search_fields = ("name", "description")
    filter_horizontal = ("permissions", "chair_permissions")

    fields = (
        "name",
        "description",
        "photo",
        "permissions",
        "chair_permissions",
        "since",
        "until",
        "contact_mailinglist",
        "contact_email",
        "active",
        "display_members",
    )


@admin.register(models.Committee)
class CommitteeAdmin(MemberGroupAdmin):
    """Manage the committees."""


@admin.register(models.Society)
class SocietyAdmin(MemberGroupAdmin):
    """Manage the societies."""


@admin.register(models.Board)
class BoardAdmin(admin.ModelAdmin):
    """Manage the board."""

    inlines = (MemberGroupMembershipInline,)
    form = MemberGroupForm
    exclude = ("is_board",)
    filter_horizontal = ("permissions",)

    fields = (
        "name",
        "description",
        "photo",
        "permissions",
        "contact_mailinglist",
        "contact_email",
        "since",
        "until",
        "display_members",
    )


class TypeFilter(admin.SimpleListFilter):
    """Filter memberships on board-only."""

    title = _("group memberships")
    parameter_name = "group_type"

    def lookups(self, request, model_admin):
        return [
            ("boards", _("Only boards")),
            ("committees", _("Only committees")),
            ("societies", _("Only societies")),
        ]

    def queryset(self, request, queryset):
        if self.value() == "boards":
            return queryset.exclude(group__board=None)
        if self.value() == "committees":
            return queryset.exclude(group__committee=None)
        if self.value() == "societies":
            return queryset.exclude(group__society=None)
        return queryset


class LectureYearFilter(admin.SimpleListFilter):
    """Filter the memberships on those started or ended in a lecture year."""

    title = _("lecture year")
    parameter_name = "lecture_year"

    def lookups(self, request, model_admin):
        current_year = datetime_to_lectureyear(timezone.now())
        first_year = datetime_to_lectureyear(
            models.MemberGroupMembership.objects.earliest("since").since
        )

        return [
            (year, f"{year}-{year + 1}") for year in range(first_year, current_year + 1)
        ]

    def queryset(self, request, queryset):
        if not self.value():
            return queryset

        year = int(self.value())
        first_of_september = datetime.date(year=year, month=9, day=1)

        return queryset.exclude(until__lt=first_of_september)


class ActiveMembershipsFilter(admin.SimpleListFilter):
    """Filter the memberships by whether they are active or not."""

    title = _("active memberships")
    parameter_name = "active"

    def lookups(self, request, model_admin):
        return (
            ("active", _("Active")),
            ("inactive", _("Inactive")),
        )

    def queryset(self, request, queryset):
        now = timezone.now()

        if self.value() == "active":
            return queryset.filter(Q(until__isnull=True) | Q(until__gte=now))
        if self.value() == "inactive":
            return queryset.filter(until__lt=now)
        return queryset


@admin.register(models.MemberGroupMembership)
class MemberGroupMembershipAdmin(admin.ModelAdmin):
    """Manage the group memberships."""

    form = MemberGroupMembershipForm
    list_display = (
        "member",
        "group",
        "since",
        "until",
        "chair",
        "has_chair_permissions",
        "role",
    )
    list_filter = ("group", TypeFilter, LectureYearFilter, ActiveMembershipsFilter)
    list_select_related = (
        "member",
        "group",
    )
    search_fields = ("member__first_name", "member__last_name", "member__email")
    date_hierarchy = "since"
    actions = ("export",)

    # Facet counts would crash for this admin.
    show_facets = admin.ShowFacets.NEVER

    def changelist_view(self, request, extra_context=None):
        self.message_user(
            request,
            _(
                "Do not edit existing memberships if the "
                "chair of a group has changed, add a "
                "new membership instead."
            ),
            messages.WARNING,
        )
        return super().changelist_view(request, extra_context)

    def export(self, request, queryset):
        response = HttpResponse(content_type="text/csv")
        response["Content-Disposition"] = 'attachment; filename="group_memberships.csv"'
        writer = csv.writer(response)
        writer.writerow(
            [
                _("First name"),
                _("Last name"),
                _("Email"),
                _("Group"),
                _("Member since"),
                _("Member until"),
                _("Chair of the group"),
                _("Role"),
            ]
        )

        for membership in queryset:
            writer.writerow(
                [
                    membership.member.first_name,
                    membership.member.last_name,
                    membership.member.email,
                    membership.group,
                    membership.since,
                    membership.until,
                    membership.chair,
                    membership.role,
                ]
            )

        return response

    export.short_description = _("Export selected memberships")


@admin.register(models.Mentorship)
class MentorshipAdmin(admin.ModelAdmin):
    """Manage the mentorships."""

    autocomplete_fields = ("member",)
    search_fields = ("member__first_name", "member__last_name")
    list_filter = ("year",)