Changes to payment models #32
|
@ -23,6 +23,7 @@ dependencies = [
|
|||
"django-registries==0.0.3",
|
||||
"django-view-decorator==0.0.4",
|
||||
"django-oauth-toolkit==2.4.0",
|
||||
"django_stubs_ext",
|
||||
benjaoming marked this conversation as resolved
|
||||
]
|
||||
version = "0.0.1"
|
||||
|
||||
|
@ -104,10 +105,10 @@ show_error_codes = true
|
|||
strict = true
|
||||
warn_unreachable = true
|
||||
follow_imports = "normal"
|
||||
#plugins = ["mypy_django_plugin.main"]
|
||||
plugins = ["mypy_django_plugin.main"]
|
||||
|
||||
[tool.django-stubs]
|
||||
#django_settings_module = "tests.settings"
|
||||
django_settings_module = "project.settings"
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "tests.*"
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
"""Admin configuration for membership app."""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import ModelAdmin
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import QuerySet
|
||||
from django.http import HttpRequest
|
||||
from django.http import HttpResponse
|
||||
|
||||
from . import models
|
||||
|
||||
|
@ -31,11 +37,39 @@ class MembershipInlineAdmin(admin.TabularInline):
|
|||
model = models.Membership
|
||||
|
||||
|
||||
def decorate_ensure_membership_type_exists(membership_type: models.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
|
||||
|
||||
|
||||
def ensure_membership_type_exists(
|
||||
request: HttpRequest, # noqa: ARG001
|
||||
queryset: QuerySet, # noqa: ARG001
|
||||
membership_type: models.MembershipType, # noqa: ARG001
|
||||
) -> HttpResponse:
|
||||
"""Inner function that ensures that a membership exists for a given queryset of Member objects."""
|
||||
|
||||
|
||||
@admin.register(models.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 = models.SubscriptionPeriod.objects.current()
|
||||
if current_period:
|
||||
for mtype in 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)
|
||||
|
||||
|
||||
@admin.register(models.WaitingListEntry)
|
||||
|
|
|
@ -10,6 +10,7 @@ from django.contrib.postgres.fields import RangeOperators
|
|||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
from djmoney.money import Money
|
||||
from utils.mixins import CreatedModifiedAbstract
|
||||
|
||||
|
||||
|
@ -53,6 +54,22 @@ class SubscriptionPeriod(CreatedModifiedAbstract):
|
|||
Denotes a period for which members should pay their membership fee for.
|
||||
"""
|
||||
|
||||
class QuerySet(models.QuerySet):
|
||||
"""QuerySet for the Membership model."""
|
||||
|
||||
def _current(self) -> Self:
|
||||
"""Filter memberships for the current period."""
|
||||
return self.filter(period__contains=timezone.now())
|
||||
|
||||
def current(self) -> "Membership | None":
|
||||
"""Get the current membership."""
|
||||
try:
|
||||
return self._current().get()
|
||||
except self.model.DoesNotExist:
|
||||
return None
|
||||
|
||||
objects = QuerySet.as_manager()
|
||||
|
||||
period = DateRangeField(verbose_name=_("period"))
|
||||
|
||||
class Meta:
|
||||
|
@ -179,6 +196,11 @@ class MembershipType(CreatedModifiedAbstract):
|
|||
period=get_current_subscription_period(),
|
||||
)
|
||||
|
||||
@property
|
||||
def total_including_vat(self) -> Money:
|
||||
"""Calculate the total price of this membership (including VAT)."""
|
||||
return sum(product.price + product.vat for product in self.products.all())
|
||||
|
||||
|
||||
class WaitingListEntry(CreatedModifiedAbstract):
|
||||
"""People who for some reason could want to be added to a waiting list and invited to join later."""
|
||||
|
|
|
@ -2,9 +2,12 @@
|
|||
|
||||
from pathlib import Path
|
||||
|
||||
import django_stubs_ext
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from environs import Env
|
||||
|
||||
django_stubs_ext.monkeypatch()
|
||||
benjaoming marked this conversation as resolved
benjaoming
commented
This is where django-stubs is used. It's said to be "production safe" :) This is where django-stubs is used. It's said to be "production safe" :)
valberg
commented
Ah yes! I can vouch for it - we use it at $WORK without issues Ah yes! I can vouch for it - we use it at $WORK without issues
benjaoming
commented
Nice! I'm gonna vouch that we keep it for now and see if we'll start using mypy checks or not... now at least, it can be run. Nice! I'm gonna vouch that we keep it for now and see if we'll start using mypy checks or not... now at least, it can be run.
benjaoming
commented
https://git.data.coop/data.coop/membersystem/issues/37
|
||||
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
|
|
Loading…
Reference in a new issue
Noting that this got reintroduced... I don't use it... but is it used? Or did you remove it on purpose @valberg ?
I did not do it on purpose no 😊 my all means remove it if it is isn't being used
Oh god now I know what's going on... it's for the mypy django-stubs thing...
Yes, we need it for running mypy with Django. I think I spend 2 minutes to get it working. But I didn't actually fix any errors. I think we can keep it for now and then we should open an issue if we want to keep using it.
Or we can decide that we don't want to spend time on this because it doesn't find any real issues.
#37