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.core.mail import send_mail
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.utils import timezone
from membership.models import Membership
from . import models from . import models
@ -19,3 +21,19 @@ def check_total_amount(sender: models.Payment, instance: models.Payment, **kwarg
settings.DEFAULT_FROM_EMAIL, settings.DEFAULT_FROM_EMAIL,
settings.ADMINS, 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 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",