Compare commits

...

2 commits

3 changed files with 65 additions and 12 deletions

View file

@ -4,6 +4,8 @@ from django.conf import settings
from django.core.mail import send_mail
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone
from membership.models import Membership
from . import models
@ -19,3 +21,19 @@ def check_total_amount(sender: models.Payment, instance: models.Payment, **kwarg
settings.DEFAULT_FROM_EMAIL,
settings.ADMINS,
)
@receiver(post_save, sender=models.Payment)
def mark_order_paid(sender: models.Payment, instance: models.Payment, **kwargs: dict) -> None: # noqa: ARG001
"""Mark an order as paid when payment is received."""
instance.order.is_paid = True
instance.order.save()
@receiver(post_save, sender=models.Payment)
def activate_membership(sender: models.Order, instance: models.Order, **kwargs: dict) -> None: # noqa: ARG001
"""Mark a membership as activated when its order is marked as paid."""
if instance.is_paid:
Membership.objects.filter(order=instance, activated=False, activated_on=None).update(
activated=True, activated_on=timezone.now()
)

View file

@ -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)

View file

@ -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",