2024-07-15 00:19:37 +02:00
|
|
|
"""Views for the membership app."""
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
2024-08-14 09:17:29 +00:00
|
|
|
from django.contrib import messages
|
|
|
|
from django.contrib.auth.tokens import default_token_generator
|
|
|
|
from django.http import HttpResponseForbidden
|
|
|
|
from django.shortcuts import get_object_or_404
|
|
|
|
from django.shortcuts import redirect
|
2023-01-03 17:00:07 +01:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2024-08-14 09:17:29 +00:00
|
|
|
from django_ratelimit.decorators import ratelimit
|
2024-01-14 12:27:36 +01:00
|
|
|
from django_view_decorator import namespaced_decorator_factory
|
2024-07-15 00:19:37 +02:00
|
|
|
from utils.view_utils import RenderConfig
|
2024-02-29 21:25:59 +01:00
|
|
|
from utils.view_utils import RowAction
|
|
|
|
from utils.view_utils import render
|
2021-02-28 23:00:11 +01:00
|
|
|
|
2024-08-14 09:17:29 +00:00
|
|
|
from .forms import InviteForm
|
|
|
|
from .models import Membership
|
2023-01-03 17:00:07 +01:00
|
|
|
from .permissions import ADMINISTRATE_MEMBERS
|
2023-01-03 21:36:34 +01:00
|
|
|
from .selectors import get_member
|
|
|
|
from .selectors import get_members
|
2023-01-02 23:06:00 +01:00
|
|
|
from .selectors import get_memberships
|
2023-01-14 23:33:58 +01:00
|
|
|
from .selectors import get_subscription_periods
|
2021-02-28 23:00:11 +01:00
|
|
|
|
2024-07-15 00:19:37 +02:00
|
|
|
if TYPE_CHECKING:
|
2025-03-03 18:20:28 +00:00
|
|
|
from utils.types import AuthenticatedHttpRequest
|
2024-07-15 00:19:37 +02:00
|
|
|
from django.http import HttpRequest
|
|
|
|
from django.http import HttpResponse
|
|
|
|
|
2024-01-14 12:27:36 +01:00
|
|
|
member_view = namespaced_decorator_factory(namespace="member", base_path="membership")
|
|
|
|
|
|
|
|
|
|
|
|
@member_view(
|
|
|
|
paths="",
|
|
|
|
name="membership-overview",
|
|
|
|
login_required=True,
|
|
|
|
)
|
2025-03-03 18:20:28 +00:00
|
|
|
def membership_overview(request: AuthenticatedHttpRequest) -> HttpResponse:
|
2024-07-15 00:19:37 +02:00
|
|
|
"""View to show the membership overview."""
|
2023-09-16 16:27:15 +02:00
|
|
|
memberships = get_memberships(member=request.user)
|
2021-02-28 23:00:11 +01:00
|
|
|
current_membership = memberships.current()
|
|
|
|
previous_memberships = memberships.previous()
|
|
|
|
|
2023-01-02 22:13:25 +01:00
|
|
|
current_period = current_membership.period.period if current_membership else None
|
|
|
|
|
2023-01-21 16:20:50 +01:00
|
|
|
context = {
|
2022-08-09 14:39:02 +02:00
|
|
|
"current_membership": current_membership,
|
2023-01-02 22:13:25 +01:00
|
|
|
"current_period": current_period,
|
2022-08-09 14:39:02 +02:00
|
|
|
"previous_memberships": previous_memberships,
|
|
|
|
}
|
2021-02-28 23:00:11 +01:00
|
|
|
|
2023-01-02 22:13:25 +01:00
|
|
|
return render(
|
|
|
|
request=request,
|
2023-01-02 23:06:00 +01:00
|
|
|
template_name="membership/membership_overview.html",
|
|
|
|
context=context,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2024-01-14 12:27:36 +01:00
|
|
|
admin_members_view = namespaced_decorator_factory(
|
|
|
|
namespace="admin-members",
|
|
|
|
base_path="admin",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@admin_members_view(
|
|
|
|
paths="members/",
|
|
|
|
name="list",
|
|
|
|
login_required=True,
|
|
|
|
permissions=[ADMINISTRATE_MEMBERS.path],
|
|
|
|
)
|
2025-03-03 18:20:28 +00:00
|
|
|
def members_admin(request: AuthenticatedHttpRequest) -> HttpResponse:
|
2024-07-15 00:19:37 +02:00
|
|
|
"""View to list all members."""
|
2023-01-03 21:36:34 +01:00
|
|
|
users = get_members()
|
2023-01-02 23:06:00 +01:00
|
|
|
|
2024-07-15 00:19:37 +02:00
|
|
|
render_config = RenderConfig(
|
2023-10-02 20:50:39 +02:00
|
|
|
entity_name="member",
|
|
|
|
entity_name_plural="members",
|
2023-01-12 17:31:34 +01:00
|
|
|
paginate_by=20,
|
2023-01-03 17:00:07 +01:00
|
|
|
objects=users,
|
|
|
|
columns=[
|
|
|
|
("username", _("Username")),
|
|
|
|
("first_name", _("First name")),
|
|
|
|
("last_name", _("Last name")),
|
|
|
|
("email", _("Email")),
|
2023-01-03 21:36:34 +01:00
|
|
|
("active_membership", _("Active membership")),
|
2023-01-03 17:00:07 +01:00
|
|
|
],
|
2023-01-03 21:36:34 +01:00
|
|
|
row_actions=[
|
|
|
|
RowAction(
|
|
|
|
label=_("View"),
|
2024-01-14 12:27:36 +01:00
|
|
|
url_name="admin-members:detail",
|
2023-01-03 21:36:34 +01:00
|
|
|
url_kwargs={"member_id": "id"},
|
2023-01-11 21:55:58 +01:00
|
|
|
),
|
2023-01-03 21:36:34 +01:00
|
|
|
],
|
|
|
|
)
|
|
|
|
|
2024-07-15 00:19:37 +02:00
|
|
|
return render_config.render_list(
|
|
|
|
request=request,
|
|
|
|
)
|
|
|
|
|
2023-01-03 21:36:34 +01:00
|
|
|
|
2024-01-14 12:27:36 +01:00
|
|
|
@admin_members_view(
|
|
|
|
paths="<int:member_id>/",
|
|
|
|
name="detail",
|
|
|
|
login_required=True,
|
|
|
|
permissions=[ADMINISTRATE_MEMBERS.path],
|
|
|
|
)
|
2025-03-03 18:20:28 +00:00
|
|
|
def members_admin_detail(request: AuthenticatedHttpRequest, member_id: int) -> HttpResponse:
|
2024-07-15 00:19:37 +02:00
|
|
|
"""View to show the details of a member."""
|
2023-01-03 21:36:34 +01:00
|
|
|
member = get_member(member_id=member_id)
|
2023-01-14 23:33:58 +01:00
|
|
|
subscription_periods = get_subscription_periods(member=member)
|
2023-01-03 21:36:34 +01:00
|
|
|
|
2023-01-21 16:20:50 +01:00
|
|
|
context = {
|
2023-01-14 23:33:58 +01:00
|
|
|
"member": member,
|
|
|
|
"subscription_periods": subscription_periods,
|
2024-01-14 12:27:36 +01:00
|
|
|
"base_path": "admin-members:list",
|
2023-01-14 23:33:58 +01:00
|
|
|
}
|
2023-01-03 21:36:34 +01:00
|
|
|
|
|
|
|
return render(
|
|
|
|
request=request,
|
|
|
|
template_name="membership/members_admin_detail.html",
|
|
|
|
context=context,
|
2023-01-02 22:13:25 +01:00
|
|
|
)
|
2024-08-14 09:17:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
@ratelimit(group="membership", key="ip", rate="10/d", method="ALL", block=True)
|
|
|
|
@member_view(
|
|
|
|
paths="invite/<str:referral_code>/<str:token>/",
|
|
|
|
name="membership-invite",
|
|
|
|
login_required=False,
|
|
|
|
)
|
|
|
|
def invite(request: HttpRequest, referral_code: str, token: str) -> HttpResponse:
|
|
|
|
"""View to invite a member to create a membership.
|
|
|
|
|
|
|
|
The token belongs to a non-active Member object. If the token is valid,
|
|
|
|
the caller is allowed to create a membership.
|
|
|
|
|
|
|
|
We ratelimit this view so it's not possible to brute-force tokens.
|
|
|
|
"""
|
|
|
|
if request.user.is_authenticated:
|
|
|
|
return HttpResponseForbidden("You're already logged in. So you cannot receive an invite.")
|
|
|
|
|
|
|
|
# Firstly, we get the membership by the referral code.
|
|
|
|
membership = get_object_or_404(Membership, referral_code=referral_code, user__is_active=False, revoked=False)
|
|
|
|
|
|
|
|
token_valid = default_token_generator.check_token(membership.user, token)
|
|
|
|
|
|
|
|
if not token_valid:
|
2025-03-03 18:20:28 +00:00
|
|
|
return HttpResponseForbidden("Token not valid - maybe it expired?")
|
2024-08-14 09:17:29 +00:00
|
|
|
|
|
|
|
if request.method == "POST":
|
|
|
|
form = InviteForm(membership=membership, data=request.POST)
|
|
|
|
if form.is_valid():
|
|
|
|
form.save()
|
|
|
|
messages.info(request, _("Password is set for your account and you can now login."))
|
|
|
|
return redirect("account_login")
|
|
|
|
else:
|
|
|
|
form = InviteForm(membership=membership)
|
|
|
|
|
|
|
|
context = {
|
|
|
|
"token": token,
|
|
|
|
"membership": membership,
|
|
|
|
"form": form,
|
|
|
|
}
|
|
|
|
return render(
|
|
|
|
request=request,
|
|
|
|
template_name="membership/invite.html",
|
|
|
|
context=context,
|
|
|
|
)
|