membersystem/src/membership/admin.py
Benjamin Bach 4254baf09d Changes to payment models (#32)
Flagging incoming changes, no actions required.

This is stuff I consider "MVP", as in what we need urgently to send out payment links to members and receive payments via Stripe.

- [x] Allow products, orders etc
- [x] Define several products per membership type
- [x] Possibility to create a membership BEFORE it's paid
- [x] Mark memberships active when payments are received
- [x] Create membership history for each member (via Django admin)
- [x] Efficiently mark members in a list and choose "create <membership type> for current year with an unpaid order" (Django Admin actions)
- [x] Order payment page w/ Stripe integration
- [ ] Send email with order payment link
- [ ] Send payment confirmation emails
- [x] Re-generate migrations

Co-authored-by: valberg <valberg@orn.li>
Reviewed-on: https://git.data.coop/data.coop/membersystem/pulls/32
Reviewed-by: valberg <valberg@orn.li>
Co-authored-by: Benjamin Bach <benjamin@overtag.dk>
Co-committed-by: Benjamin Bach <benjamin@overtag.dk>
2024-08-03 17:55:32 +00:00

115 lines
4.2 KiB
Python

"""Admin configuration for membership app."""
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
from .models import Member
from .models import Membership
from .models import MembershipType
from .models import SubscriptionPeriod
from .models import WaitingListEntry
# Do not use existing user admin
admin.site.unregister(User)
@admin.register(Membership)
class MembershipAdmin(admin.ModelAdmin):
"""Admin for Membership model."""
@admin.register(MembershipType)
class MembershipTypeAdmin(admin.ModelAdmin):
"""Admin for MembershipType model."""
@admin.register(SubscriptionPeriod)
class SubscriptionPeriodAdmin(admin.ModelAdmin):
"""Admin for SubscriptionPeriod model."""
class MembershipInlineAdmin(admin.TabularInline):
"""Inline admin."""
model = Membership
def decorate_ensure_membership_type_exists(membership_type: MembershipType, label: str) -> Callable:
"""Generate an admin action for given membership type and label."""
@admin.action(description=label)
def admin_action(modeladmin: ModelAdmin, request: HttpRequest, queryset: QuerySet) -> HttpResponse: # noqa: ARG001
return ensure_membership_type_exists(request, queryset, membership_type)
return admin_action
@transaction.atomic
def ensure_membership_type_exists(
request: HttpRequest,
queryset: QuerySet,
membership_type: 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, description=membership_type.name)
# 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
Membership.objects.create(
membership_type=membership_type,
user=member,
period=SubscriptionPeriod.objects.current(),
order=order,
)
# Associate the order with that membership
messages.success(request, f"{member} has ordered a '{membership_type}' (unpaid)")
@admin.register(Member)
class MemberAdmin(UserAdmin):
"""Member admin is actually an admin for User objects."""
inlines = (MembershipInlineAdmin,)
actions: list[Callable] = [] # noqa: RUF012
def get_actions(self, request: HttpRequest) -> dict:
"""Populate actions with dynamic data (MembershipType)."""
current_period = SubscriptionPeriod.objects.current()
super_dict = super().get_actions(request)
if current_period:
for i, mtype in enumerate(MembershipType.objects.filter(active=True)):
action_label = f"Ensure membership {mtype.name}, {current_period.period}, {mtype.total_including_vat}"
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(WaitingListEntry)
class WaitingListEntryAdmin(admin.ModelAdmin):
"""Admin for WaitingList model."""