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

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 

15 

16from django_ratelimit.decorators import ratelimit 

17 

18from members.decorators import membership_required 

19from members.models import Membership 

20 

21from . import emails, forms, services 

22from .models import Entry, Reference, Registration, Renewal 

23 

24 

25class BecomeAMemberView(TemplateView): 

26 """View that render a HTML template with context data.""" 

27 

28 template_name = "registrations/become_a_member.html" 

29 

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 

39 

40 

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.""" 

48 

49 def post(self, request, *args, **kwargs): 

50 action = request.POST.get("action") 

51 entry = get_object_or_404(Entry, pk=kwargs["pk"]) 

52 

53 registration = getattr(entry, "registration", None) 

54 renewal = getattr(entry, "renewal", None) 

55 

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}.") 

93 

94 redirect_model = "registration" if registration is not None else "renewal" 

95 return redirect(f"admin:registrations_{redirect_model}_change", kwargs["pk"]) 

96 

97 

98class ConfirmEmailView(View, TemplateResponseMixin): 

99 """View that confirms the email address of the provided registration.""" 

100 

101 template_name = "registrations/confirm_email.html" 

102 

103 def get(self, request, *args, **kwargs): 

104 registration = get_object_or_404(Registration, pk=kwargs["pk"]) 

105 

106 if registration.status == Registration.STATUS_CONFIRM: 

107 services.confirm_registration(registration) 

108 

109 if registration.status != Registration.STATUS_REVIEW: 

110 raise Http404 

111 

112 return self.render_to_response({}) 

113 

114 

115class BaseRegistrationFormView(FormView): 

116 """View that renders a membership registration form.""" 

117 

118 form_class = forms.MemberRegistrationForm 

119 template_name = "registrations/register_member.html" 

120 

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 

131 

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) 

136 

137 def form_valid(self, form): 

138 form.save() 

139 emails.send_registration_email_confirmation(form.instance) 

140 return redirect("registrations:register-success") 

141 

142 @method_decorator(ratelimit(key="ip", rate="10/d")) 

143 def post(self, request, *args, **kwargs): 

144 return super().post(request, *args, **kwargs) 

145 

146 

147class MemberRegistrationFormView(BaseRegistrationFormView): 

148 """View that renders the `member` membership registration form.""" 

149 

150 form_class = forms.MemberRegistrationForm 

151 template_name = "registrations/register_member.html" 

152 

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 

160 

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) 

165 

166 

167class BenefactorRegistrationFormView(BaseRegistrationFormView): 

168 """View that renders the `benefactor` membership registration form.""" 

169 

170 form_class = forms.BenefactorRegistrationForm 

171 template_name = "registrations/register_benefactor.html" 

172 

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 

180 

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) 

190 

191 

192class NewYearRenewalFormView(FormView): 

193 """View that renders the membership extension form for study memberships.""" 

194 

195 form_class = forms.NewYearForm 

196 template_name = "registrations/new_year_renewal.html" 

197 

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 ) 

221 

222 return redirect(reverse("registrations:renew")) 

223 

224 return super().dispatch(request, *args, **kwargs) 

225 

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") 

233 

234 

235@method_decorator(login_required, name="dispatch") 

236class RenewalFormView(FormView): 

237 """View that renders the membership renewal form.""" 

238 

239 form_class = forms.RenewalForm 

240 template_name = "registrations/renewal.html" 

241 

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() 

261 

262 context["benefactor_type"] = Membership.BENEFACTOR 

263 return context 

264 

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 ) 

277 

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 ] 

289 

290 return form 

291 

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 

303 

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 

314 

315 return super().post(request, *args, **kwargs) 

316 

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") 

323 

324 

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.""" 

329 

330 model = Reference 

331 form_class = forms.ReferenceForm 

332 template_name = "registrations/reference.html" 

333 entry = None 

334 success = False 

335 

336 def get_success_url(self): 

337 return reverse("registrations:reference-success", args=(self.entry.pk,)) 

338 

339 def get_context_data(self, **kwargs): 

340 context = super().get_context_data(**kwargs) 

341 

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() 

347 

348 return context 

349 

350 def dispatch(self, request, *args, **kwargs): 

351 self.entry = get_object_or_404(Entry, pk=kwargs.get("pk")) 

352 

353 if ( 

354 self.entry.no_references 

355 or self.entry.membership_type != Membership.BENEFACTOR 

356 ): 

357 raise Http404 

358 

359 return super().dispatch(request, *args, **kwargs) 

360 

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)