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-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.*"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue