forked from data.coop/membersystem
Compare commits
2 commits
3193cafe4b
...
5c5153adb6
Author | SHA1 | Date | |
---|---|---|---|
Benjamin Bach | 5c5153adb6 | ||
Benjamin Bach | 2499c3227c |
|
@ -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()
|
||||||
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue