Coverage for website/registrations/views.py: 100.00%
195 statements
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1from django.conf import settings
2from django.contrib import messages
3from django.contrib.admin.views.decorators import staff_member_required
4from django.contrib.auth.decorators import login_required, permission_required
5from django.db.models import Q
6from django.http import Http404
7from django.shortcuts import get_object_or_404, redirect
8from django.template.defaultfilters import floatformat
9from django.urls import reverse
10from django.utils import timezone
11from django.utils.decorators import method_decorator
12from django.views import View
13from django.views.generic import CreateView, FormView
14from django.views.generic.base import TemplateResponseMixin, TemplateView
16from django_ratelimit.decorators import ratelimit
18from members.decorators import membership_required
19from members.models import Membership
21from . import emails, forms, services
22from .models import Entry, Reference, Registration, Renewal
25class BecomeAMemberView(TemplateView):
26 """View that render a HTML template with context data."""
28 template_name = "registrations/become_a_member.html"
30 def get_context_data(self, **kwargs):
31 context = super().get_context_data(**kwargs)
32 context["year_fees"] = floatformat(
33 settings.MEMBERSHIP_PRICES[Entry.MEMBERSHIP_YEAR], 2
34 )
35 context["study_fees"] = floatformat(
36 settings.MEMBERSHIP_PRICES[Entry.MEMBERSHIP_STUDY], 2
37 )
38 return context
41@method_decorator(staff_member_required, name="dispatch")
42@method_decorator(
43 permission_required("registrations.review_entries"),
44 name="dispatch",
45)
46class EntryAdminView(View):
47 """View that handles the processing of entries."""
49 def post(self, request, *args, **kwargs):
50 action = request.POST.get("action")
51 entry = get_object_or_404(Entry, pk=kwargs["pk"])
53 registration = getattr(entry, "registration", None)
54 renewal = getattr(entry, "renewal", None)
56 if action == "accept":
57 if registration is not None:
58 if not registration.check_user_is_unique():
59 messages.error(
60 request,
61 f"Could not accept {registration}. Username or email is not unique.",
62 )
63 else:
64 services.accept_registration(registration, actor=request.user)
65 messages.success(request, f"Successfully accepted {registration}.")
66 elif renewal is not None: # pragma: no cover
67 services.accept_renewal(renewal, actor=request.user)
68 messages.success(request, f"Successfully accepted {renewal}.")
69 elif action == "reject":
70 if registration is not None:
71 services.reject_registration(registration, actor=request.user)
72 messages.success(request, f"Successfully rejected {registration}.")
73 elif renewal is not None: # pragma: no cover
74 services.reject_renewal(renewal, actor=request.user)
75 messages.success(request, f"Successfully rejected {renewal}.")
76 elif action == "resend":
77 if registration is not None:
78 emails.send_registration_email_confirmation(entry.registration)
79 messages.success(
80 request, f"Resent registration email of {registration}."
81 )
82 else:
83 messages.error(request, "Cannot resend renewal.")
84 elif action == "revert": # pragma: no cover
85 if registration is not None:
86 services.revert_registration(registration, actor=request.user)
87 messages.success(
88 request, f"Successfully reverted registration {registration}."
89 )
90 elif renewal is not None:
91 services.revert_renewal(renewal, actor=request.user)
92 messages.success(request, f"Successfully reverted renewal {renewal}.")
94 redirect_model = "registration" if registration is not None else "renewal"
95 return redirect(f"admin:registrations_{redirect_model}_change", kwargs["pk"])
98class ConfirmEmailView(View, TemplateResponseMixin):
99 """View that confirms the email address of the provided registration."""
101 template_name = "registrations/confirm_email.html"
103 def get(self, request, *args, **kwargs):
104 registration = get_object_or_404(Registration, pk=kwargs["pk"])
106 if registration.status == Registration.STATUS_CONFIRM:
107 services.confirm_registration(registration)
109 if registration.status != Registration.STATUS_REVIEW:
110 raise Http404
112 return self.render_to_response({})
115class BaseRegistrationFormView(FormView):
116 """View that renders a membership registration form."""
118 form_class = forms.MemberRegistrationForm
119 template_name = "registrations/register_member.html"
121 def get_context_data(self, **kwargs):
122 context = super().get_context_data(**kwargs)
123 context["google_api_key"] = settings.GOOGLE_PLACES_API_KEY
124 context["year_fees"] = floatformat(
125 settings.MEMBERSHIP_PRICES[Entry.MEMBERSHIP_YEAR], 2
126 )
127 context["study_fees"] = floatformat(
128 settings.MEMBERSHIP_PRICES[Entry.MEMBERSHIP_STUDY], 2
129 )
130 return context
132 def get(self, request, *args, **kwargs):
133 if request.user.is_authenticated:
134 return redirect("registrations:renew")
135 return super().get(request, args, kwargs)
137 def form_valid(self, form):
138 form.save()
139 emails.send_registration_email_confirmation(form.instance)
140 return redirect("registrations:register-success")
142 @method_decorator(ratelimit(key="ip", rate="10/d"))
143 def post(self, request, *args, **kwargs):
144 return super().post(request, *args, **kwargs)
147class MemberRegistrationFormView(BaseRegistrationFormView):
148 """View that renders the `member` membership registration form."""
150 form_class = forms.MemberRegistrationForm
151 template_name = "registrations/register_member.html"
153 def get_context_data(self, **kwargs):
154 context = super().get_context_data(**kwargs)
155 context["tpay_enabled"] = (
156 settings.THALIA_PAY_ENABLED_PAYMENT_METHOD
157 and settings.THALIA_PAY_FOR_NEW_MEMBERS
158 )
159 return context
161 def post(self, request, *args, **kwargs):
162 request.POST = request.POST.dict()
163 request.POST["membership_type"] = Membership.MEMBER
164 return super().post(request, *args, **kwargs)
167class BenefactorRegistrationFormView(BaseRegistrationFormView):
168 """View that renders the `benefactor` membership registration form."""
170 form_class = forms.BenefactorRegistrationForm
171 template_name = "registrations/register_benefactor.html"
173 def get_context_data(self, **kwargs):
174 context = super().get_context_data(**kwargs)
175 context["tpay_enabled"] = (
176 settings.THALIA_PAY_ENABLED_PAYMENT_METHOD
177 and settings.THALIA_PAY_FOR_NEW_MEMBERS
178 )
179 return context
181 def post(self, request, *args, **kwargs):
182 request.POST = request.POST.dict()
183 request.POST["membership_type"] = Membership.BENEFACTOR
184 request.POST["length"] = Entry.MEMBERSHIP_YEAR
185 request.POST["remarks"] = (
186 "Registered as iCIS employee" if "icis_employee" in request.POST else ""
187 )
188 request.POST["no_references"] = "icis_employee" in request.POST
189 return super().post(request, *args, **kwargs)
192class NewYearRenewalFormView(FormView):
193 """View that renders the membership extension form for study memberships."""
195 form_class = forms.NewYearForm
196 template_name = "registrations/new_year_renewal.html"
198 @method_decorator(login_required)
199 def dispatch(self, request, *args, **kwargs):
200 membership = request.member.latest_membership
201 existing_renewal = Renewal.objects.filter(
202 Q(member=self.request.member)
203 & (
204 Q(status=Registration.STATUS_ACCEPTED)
205 | Q(status=Registration.STATUS_REVIEW)
206 )
207 ).last()
208 if (
209 existing_renewal
210 or membership is None
211 or membership.type != Membership.MEMBER
212 or not membership.study_long
213 or request.member.profile.is_minimized
214 or membership.until is None
215 or ((membership.until - timezone.now().date()).days > 31)
216 ):
217 messages.error(
218 self.request,
219 "You cannot currently prolong a study membership.",
220 )
222 return redirect(reverse("registrations:renew"))
224 return super().dispatch(request, *args, **kwargs)
226 def form_valid(self, form):
227 membership = self.request.member.latest_membership
228 membership.until = timezone.datetime(
229 year=membership.until.year + 1, month=9, day=1
230 ).date()
231 membership.save()
232 return redirect("registrations:renew-studylong-success")
235@method_decorator(login_required, name="dispatch")
236class RenewalFormView(FormView):
237 """View that renders the membership renewal form."""
239 form_class = forms.RenewalForm
240 template_name = "registrations/renewal.html"
242 def get_context_data(self, **kwargs):
243 context = super().get_context_data(**kwargs)
244 context["year_fees"] = floatformat(
245 settings.MEMBERSHIP_PRICES[Entry.MEMBERSHIP_YEAR], 2
246 )
247 context["study_fees"] = floatformat(
248 settings.MEMBERSHIP_PRICES[Entry.MEMBERSHIP_STUDY], 2
249 )
250 context["latest_membership"] = self.request.member.latest_membership
251 context["latest_renewal"] = Renewal.objects.filter(
252 Q(member=self.request.member)
253 & (
254 Q(status=Registration.STATUS_ACCEPTED)
255 | Q(status=Registration.STATUS_REVIEW)
256 )
257 ).last()
258 context["was_member"] = Membership.objects.filter(
259 user=self.request.member, type=Membership.MEMBER
260 ).exists()
262 context["benefactor_type"] = Membership.BENEFACTOR
263 return context
265 def get_form(self, form_class=None):
266 form = super().get_form(form_class)
267 member = self.request.member
268 if member is not None and member.latest_membership is not None:
269 latest_membership = member.latest_membership
270 # If latest membership has not ended or does not ends
271 # within 1 month: do not show 'year' length and disable benefactor option
272 hide_year_choice = not (
273 latest_membership is not None
274 and latest_membership.until is not None
275 and (latest_membership.until - timezone.now().date()).days <= 31
276 )
278 if hide_year_choice:
279 form.fields["length"].choices = [
280 c
281 for c in form.fields["length"].choices
282 if c[0] != Entry.MEMBERSHIP_YEAR
283 ]
284 form.fields["membership_type"].choices = [
285 c
286 for c in form.fields["membership_type"].choices
287 if c[0] != Membership.BENEFACTOR
288 ]
290 return form
292 def post(self, request, *args, **kwargs):
293 request.POST = request.POST.dict()
294 if (
295 request.member.latest_membership.type == Membership.BENEFACTOR
296 or request.member.latest_membership.study_long
297 ):
298 request.POST["membership_type"] = Membership.BENEFACTOR
299 request.POST["length"] = Entry.MEMBERSHIP_YEAR
300 request.POST["member"] = request.member.pk
301 request.POST["remarks"] = ""
302 request.POST["no_references"] = True
304 if request.POST["membership_type"] == Membership.BENEFACTOR:
305 request.POST["no_references"] = False
306 if Membership.objects.filter(
307 user=request.member, type=Membership.MEMBER
308 ).exists():
309 request.POST["remarks"] = "Was a Thalia member in the past."
310 request.POST["no_references"] = True
311 if "icis_employee" in request.POST:
312 request.POST["remarks"] = "Registered as iCIS employee."
313 request.POST["no_references"] = True
315 return super().post(request, *args, **kwargs)
317 def form_valid(self, form):
318 renewal = form.save()
319 if not renewal.no_references:
320 emails.send_references_information_message(renewal)
321 emails.send_new_renewal_board_message(renewal)
322 return redirect("registrations:renew-success")
325@method_decorator(login_required, name="dispatch")
326@method_decorator(membership_required, name="dispatch")
327class ReferenceCreateView(CreateView):
328 """View that renders a reference creation form."""
330 model = Reference
331 form_class = forms.ReferenceForm
332 template_name = "registrations/reference.html"
333 entry = None
334 success = False
336 def get_success_url(self):
337 return reverse("registrations:reference-success", args=(self.entry.pk,))
339 def get_context_data(self, **kwargs):
340 context = super().get_context_data(**kwargs)
342 context["success"] = self.success
343 try:
344 context["name"] = self.entry.registration.get_full_name()
345 except Registration.DoesNotExist:
346 context["name"] = self.entry.renewal.member.get_full_name()
348 return context
350 def dispatch(self, request, *args, **kwargs):
351 self.entry = get_object_or_404(Entry, pk=kwargs.get("pk"))
353 if (
354 self.entry.no_references
355 or self.entry.membership_type != Membership.BENEFACTOR
356 ):
357 raise Http404
359 return super().dispatch(request, *args, **kwargs)
361 def post(self, request, *args, **kwargs):
362 request.POST = request.POST.dict()
363 request.POST["member"] = request.member.pk
364 request.POST["entry"] = kwargs["pk"]
365 return super().post(request, *args, **kwargs)