Add dynamic admin actions for bulk creating memberships

This commit is contained in:
Benjamin Bach 2024-07-28 10:55:15 +02:00
parent 768ef5a7d2
commit 3193cafe4b
No known key found for this signature in database
GPG key ID: 486F0D69C845416E
4 changed files with 62 additions and 2 deletions

View file

@ -23,6 +23,7 @@ dependencies = [
"django-registries==0.0.3", "django-registries==0.0.3",
"django-view-decorator==0.0.4", "django-view-decorator==0.0.4",
"django-oauth-toolkit==2.4.0", "django-oauth-toolkit==2.4.0",
"django_stubs_ext",
] ]
version = "0.0.1" version = "0.0.1"
@ -104,10 +105,10 @@ show_error_codes = true
strict = true strict = true
warn_unreachable = true warn_unreachable = true
follow_imports = "normal" follow_imports = "normal"
#plugins = ["mypy_django_plugin.main"] plugins = ["mypy_django_plugin.main"]
[tool.django-stubs] [tool.django-stubs]
#django_settings_module = "tests.settings" django_settings_module = "project.settings"
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
module = "tests.*" module = "tests.*"

View file

@ -1,8 +1,14 @@
"""Admin configuration for membership app.""" """Admin configuration for membership app."""
from collections.abc import Callable
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import ModelAdmin
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import QuerySet
from django.http import HttpRequest
from django.http import HttpResponse
from . import models from . import models
@ -31,11 +37,39 @@ class MembershipInlineAdmin(admin.TabularInline):
model = models.Membership model = models.Membership
def decorate_ensure_membership_type_exists(membership_type: models.MembershipType, label: str) -> Callable:
"""Generate an admin action for given membership type and label."""
@admin.action(description=label)
def admin_action(modeladmin: ModelAdmin, request: HttpRequest, queryset: QuerySet) -> HttpResponse: # noqa: ARG001
return ensure_membership_type_exists(request, queryset, membership_type)
return admin_action
def ensure_membership_type_exists(
request: HttpRequest, # noqa: ARG001
queryset: QuerySet, # noqa: ARG001
membership_type: models.MembershipType, # noqa: ARG001
) -> HttpResponse:
"""Inner function that ensures that a membership exists for a given queryset of Member objects."""
@admin.register(models.Member) @admin.register(models.Member)
class MemberAdmin(UserAdmin): class MemberAdmin(UserAdmin):
"""Member admin is actually an admin for User objects.""" """Member admin is actually an admin for User objects."""
inlines = (MembershipInlineAdmin,) inlines = (MembershipInlineAdmin,)
actions: list[Callable] = [] # noqa: RUF012
def get_actions(self, request: HttpRequest) -> dict:
"""Populate actions with dynamic data (MembershipType)."""
current_period = models.SubscriptionPeriod.objects.current()
if current_period:
for mtype in models.MembershipType.objects.filter(active=True):
action_label = f"Ensure membership {mtype.name}, {current_period.period}, {mtype.total_including_vat}"
self.actions.append(decorate_ensure_membership_type_exists(mtype, action_label))
return super().get_actions(request)
@admin.register(models.WaitingListEntry) @admin.register(models.WaitingListEntry)

View file

@ -10,6 +10,7 @@ from django.contrib.postgres.fields import RangeOperators
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from djmoney.money import Money
from utils.mixins import CreatedModifiedAbstract from utils.mixins import CreatedModifiedAbstract
@ -53,6 +54,22 @@ class SubscriptionPeriod(CreatedModifiedAbstract):
Denotes a period for which members should pay their membership fee for. Denotes a period for which members should pay their membership fee for.
""" """
class QuerySet(models.QuerySet):
"""QuerySet for the Membership model."""
def _current(self) -> Self:
"""Filter memberships for the current period."""
return self.filter(period__contains=timezone.now())
def current(self) -> "Membership | None":
"""Get the current membership."""
try:
return self._current().get()
except self.model.DoesNotExist:
return None
objects = QuerySet.as_manager()
period = DateRangeField(verbose_name=_("period")) period = DateRangeField(verbose_name=_("period"))
class Meta: class Meta:
@ -179,6 +196,11 @@ class MembershipType(CreatedModifiedAbstract):
period=get_current_subscription_period(), period=get_current_subscription_period(),
) )
@property
def total_including_vat(self) -> Money:
"""Calculate the total price of this membership (including VAT)."""
return sum(product.price + product.vat for product in self.products.all())
class WaitingListEntry(CreatedModifiedAbstract): class WaitingListEntry(CreatedModifiedAbstract):
"""People who for some reason could want to be added to a waiting list and invited to join later.""" """People who for some reason could want to be added to a waiting list and invited to join later."""

View file

@ -2,9 +2,12 @@
from pathlib import Path from pathlib import Path
import django_stubs_ext
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from environs import Env from environs import Env
django_stubs_ext.monkeypatch()
env = Env() env = Env()
env.read_env() env.read_env()