"""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="/", 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///", 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}, )