forked from data.coop/membersystem
Add dynamic admin actions for bulk creating memberships
This commit is contained in:
parent
768ef5a7d2
commit
3193cafe4b
|
@ -23,6 +23,7 @@ dependencies = [
|
|||
"django-registries==0.0.3",
|
||||
"django-view-decorator==0.0.4",
|
||||
"django-oauth-toolkit==2.4.0",
|
||||
"django_stubs_ext",
|
||||
]
|
||||
version = "0.0.1"
|
||||
|
||||
|
@ -104,10 +105,10 @@ show_error_codes = true
|
|||
strict = true
|
||||
warn_unreachable = true
|
||||
follow_imports = "normal"
|
||||
#plugins = ["mypy_django_plugin.main"]
|
||||
plugins = ["mypy_django_plugin.main"]
|
||||
|
||||
[tool.django-stubs]
|
||||
#django_settings_module = "tests.settings"
|
||||
django_settings_module = "project.settings"
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "tests.*"
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
"""Admin configuration for membership app."""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import ModelAdmin
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
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
|
||||
|
||||
|
@ -31,11 +37,39 @@ class MembershipInlineAdmin(admin.TabularInline):
|
|||
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)
|
||||
class MemberAdmin(UserAdmin):
|
||||
"""Member admin is actually an admin for User objects."""
|
||||
|
||||
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)
|
||||
|
|
|
@ -10,6 +10,7 @@ from django.contrib.postgres.fields import RangeOperators
|
|||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
from djmoney.money import Money
|
||||
from utils.mixins import CreatedModifiedAbstract
|
||||
|
||||
|
||||
|
@ -53,6 +54,22 @@ class SubscriptionPeriod(CreatedModifiedAbstract):
|
|||
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"))
|
||||
|
||||
class Meta:
|
||||
|
@ -179,6 +196,11 @@ class MembershipType(CreatedModifiedAbstract):
|
|||
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):
|
||||
"""People who for some reason could want to be added to a waiting list and invited to join later."""
|
||||
|
|
|
@ -2,9 +2,12 @@
|
|||
|
||||
from pathlib import Path
|
||||
|
||||
import django_stubs_ext
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from environs import Env
|
||||
|
||||
django_stubs_ext.monkeypatch()
|
||||
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
|
|
Loading…
Reference in a new issue