From 2499c3227cdc7e0f5dc944509a1b74c8f4ec934d Mon Sep 17 00:00:00 2001 From: Benjamin Bach Date: Sun, 28 Jul 2024 18:38:00 +0200 Subject: [PATCH] Implements bulk-creation action for memberships and orders --- src/membership/admin.py | 46 ++++++++++++++++++++++++++++++++++------ src/membership/models.py | 13 ++++++------ 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/membership/admin.py b/src/membership/admin.py index 2d391a5..823d911 100644 --- a/src/membership/admin.py +++ b/src/membership/admin.py @@ -2,10 +2,15 @@ from collections.abc import Callable +from accounting.models import Account +from accounting.models import Order +from accounting.models import OrderProduct from django.contrib import admin +from django.contrib import messages from django.contrib.admin import ModelAdmin from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User +from django.db import transaction from django.db.models import QuerySet from django.http import HttpRequest from django.http import HttpResponse @@ -47,12 +52,34 @@ def decorate_ensure_membership_type_exists(membership_type: models.MembershipTyp return admin_action +@transaction.atomic def ensure_membership_type_exists( - request: HttpRequest, # noqa: ARG001 - queryset: QuerySet, # noqa: ARG001 - membership_type: models.MembershipType, # noqa: ARG001 + request: HttpRequest, + queryset: QuerySet, + membership_type: models.MembershipType, ) -> HttpResponse: """Inner function that ensures that a membership exists for a given queryset of Member objects.""" + for member in queryset: + if member.memberships.filter(membership_type=membership_type).current(): + messages.info(request, f"{member} already has a membership {membership_type}") + else: + # Get the default account of the member. We don't really know what to do if a person owns multiple accounts. + account, __ = Account.objects.get_or_create(owner=member) + # Create an Order for the products in the membership + order = Order.objects.create(member=member, account=account) + # Add stuff to the order + for product in membership_type.products.all(): + OrderProduct.objects.create(order=order, product=product, price=product.price, vat=product.vat) + # Create the Membership + models.Membership.objects.create( + membership_type=membership_type, + user=member, + period=models.SubscriptionPeriod.objects.current(), + order=order, + ) + + # Associate the order with that membership + messages.success(request, f"{member} has ordered a '{membership_type}' (unpaid)") @admin.register(models.Member) @@ -65,11 +92,18 @@ class MemberAdmin(UserAdmin): def get_actions(self, request: HttpRequest) -> dict: """Populate actions with dynamic data (MembershipType).""" current_period = models.SubscriptionPeriod.objects.current() + + super_dict = super().get_actions(request) + if current_period: - for mtype in models.MembershipType.objects.filter(active=True): + for i, mtype in enumerate(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) + action_func = decorate_ensure_membership_type_exists(mtype, action_label) + # Django ModelAdmin uses the non-unique __name__ property, so we need to suffix it to make it unique + action_func.__name__ += f"_{i}" + self.actions.append(action_func) + + return super_dict @admin.register(models.WaitingListEntry) diff --git a/src/membership/models.py b/src/membership/models.py index 23ac4fc..f00c458 100644 --- a/src/membership/models.py +++ b/src/membership/models.py @@ -99,16 +99,17 @@ class Membership(CreatedModifiedAbstract): """Filter memberships for a given member.""" return self.filter(user=member) + def active(self) -> Self: + """Get only activated, non-revoked memberships (may have expired so use also current()).""" + return self.filter(activated=True, revoked=False) + def _current(self) -> Self: """Filter memberships for the current period.""" - return self.filter(activated=True, revoked=False, period__period__contains=timezone.now()) + return self.filter(period__period__contains=timezone.now()) def current(self) -> "Membership | None": """Get the current membership.""" - try: - return self._current().get() - except self.model.DoesNotExist: - return None + return self._current().first() def previous(self) -> list["Membership"]: """Get previous memberships.""" @@ -119,7 +120,7 @@ class Membership(CreatedModifiedAbstract): objects = QuerySet.as_manager() - user = models.ForeignKey("auth.User", on_delete=models.PROTECT) + user = models.ForeignKey("auth.User", on_delete=models.PROTECT, related_name="memberships") membership_type = models.ForeignKey( "membership.MembershipType",