Implements bulk-creation action for memberships and orders

This commit is contained in:
Benjamin Bach 2024-07-28 18:38:00 +02:00
parent 3193cafe4b
commit 2499c3227c
No known key found for this signature in database
GPG key ID: 486F0D69C845416E
2 changed files with 47 additions and 12 deletions

View file

@ -2,10 +2,15 @@
from collections.abc import Callable 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 admin
from django.contrib import messages
from django.contrib.admin import ModelAdmin 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 import transaction
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http import HttpRequest from django.http import HttpRequest
from django.http import HttpResponse from django.http import HttpResponse
@ -47,12 +52,34 @@ def decorate_ensure_membership_type_exists(membership_type: models.MembershipTyp
return admin_action return admin_action
@transaction.atomic
def ensure_membership_type_exists( def ensure_membership_type_exists(
request: HttpRequest, # noqa: ARG001 request: HttpRequest,
queryset: QuerySet, # noqa: ARG001 queryset: QuerySet,
membership_type: models.MembershipType, # noqa: ARG001 membership_type: models.MembershipType,
) -> HttpResponse: ) -> HttpResponse:
"""Inner function that ensures that a membership exists for a given queryset of Member objects.""" """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) @admin.register(models.Member)
@ -65,11 +92,18 @@ class MemberAdmin(UserAdmin):
def get_actions(self, request: HttpRequest) -> dict: def get_actions(self, request: HttpRequest) -> dict:
"""Populate actions with dynamic data (MembershipType).""" """Populate actions with dynamic data (MembershipType)."""
current_period = models.SubscriptionPeriod.objects.current() current_period = models.SubscriptionPeriod.objects.current()
super_dict = super().get_actions(request)
if current_period: 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}" 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)) action_func = decorate_ensure_membership_type_exists(mtype, action_label)
return super().get_actions(request) # 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) @admin.register(models.WaitingListEntry)

View file

@ -99,16 +99,17 @@ class Membership(CreatedModifiedAbstract):
"""Filter memberships for a given member.""" """Filter memberships for a given member."""
return self.filter(user=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: def _current(self) -> Self:
"""Filter memberships for the current period.""" """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": def current(self) -> "Membership | None":
"""Get the current membership.""" """Get the current membership."""
try: return self._current().first()
return self._current().get()
except self.model.DoesNotExist:
return None
def previous(self) -> list["Membership"]: def previous(self) -> list["Membership"]:
"""Get previous memberships.""" """Get previous memberships."""
@ -119,7 +120,7 @@ class Membership(CreatedModifiedAbstract):
objects = QuerySet.as_manager() 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_type = models.ForeignKey(
"membership.MembershipType", "membership.MembershipType",