membersystem/src/membership/views.py
Víðir Valberg Guðmundsson 938556cb60 An application form! (#83)
Co-authored-by: bbb <benjamin@overtag.dk>
Reviewed-on: https://git.data.coop/data.coop/membersystem/pulls/83
Reviewed-by: benjaoming <benjaoming@data.coop>
Co-authored-by: Víðir Valberg Guðmundsson <valberg@orn.li>
Co-committed-by: Víðir Valberg Guðmundsson <valberg@orn.li>
2025-03-08 22:10:26 +00:00

197 lines
5.8 KiB
Python

"""Views for the membership app."""
from __future__ import annotations
from typing import TYPE_CHECKING
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
from django.utils.translation import gettext_lazy as _
from django_ratelimit.decorators import ratelimit
from django_view_decorator import namespaced_decorator_factory
from utils.view_utils import RenderConfig
from utils.view_utils import RowAction
from utils.view_utils import render
from .forms import InviteForm
from .forms import MemberApplicationForm
from .models import Membership
from .permissions import ADMINISTRATE_MEMBERS
from .selectors import get_member
from .selectors import get_members
from .selectors import get_memberships
from .selectors import get_subscription_periods
if TYPE_CHECKING:
from django.http import HttpRequest
from django.http import HttpResponse
from utils.types import AuthenticatedHttpRequest
member_view = namespaced_decorator_factory(namespace="member", base_path="membership")
@member_view(
paths="",
name="membership-overview",
login_required=True,
)
def membership_overview(request: AuthenticatedHttpRequest) -> HttpResponse:
"""View to show the membership overview."""
memberships = get_memberships(member=request.user)
current_membership = memberships.current()
previous_memberships = memberships.previous()
current_period = current_membership.period.period if current_membership else None
context = {
"current_membership": current_membership,
"current_period": current_period,
"previous_memberships": previous_memberships,
}
return render(
request=request,
template_name="membership/membership_overview.html",
context=context,
)
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],
)
def members_admin(request: AuthenticatedHttpRequest) -> HttpResponse:
"""View to list all members."""
users = get_members()
render_config = RenderConfig(
entity_name="member",
entity_name_plural="members",
paginate_by=20,
objects=users,
columns=[
("username", _("Username")),
("first_name", _("First name")),
("last_name", _("Last name")),
("email", _("Email")),
("active_membership", _("Active membership")),
],
row_actions=[
RowAction(
label=_("View"),
url_name="admin-members:detail",
url_kwargs={"member_id": "id"},
),
],
)
return render_config.render_list(
request=request,
)
@admin_members_view(
paths="<int:member_id>/",
name="detail",
login_required=True,
permissions=[ADMINISTRATE_MEMBERS.path],
)
def members_admin_detail(request: AuthenticatedHttpRequest, member_id: int) -> HttpResponse:
"""View to show the details of a member."""
member = get_member(member_id=member_id)
subscription_periods = get_subscription_periods(member=member)
context = {
"member": member,
"subscription_periods": subscription_periods,
"base_path": "admin-members:list",
}
return render(
request=request,
template_name="membership/members_admin_detail.html",
context=context,
)
@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:
return HttpResponseForbidden("Token not valid - maybe it expired?")
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,
)
@member_view(
paths="apply/",
name="apply",
login_required=False,
)
def apply_for_membership(request: HttpRequest) -> HttpResponse:
"""View for applying for membership."""
if request.method == "POST":
form = MemberApplicationForm(request.POST)
if form.is_valid():
form.save()
messages.success(
request,
_("Thank you for your application! We will review it and get back to you soon."),
)
return redirect("index")
else:
form = MemberApplicationForm()
return render(
request,
"membership/apply.html",
{"form": form},
)