Coverage for website/registrations/admin.py: 100.00%

85 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2025-08-14 10:31 +0000

1from functools import partial 

2 

3from django.contrib import admin 

4from django.forms import Field 

5from django.utils.translation import gettext_lazy as _ 

6 

7from payments.widgets import PaymentWidget 

8from registrations.services import ( 

9 accept_registration, 

10 accept_renewal, 

11 reject_registration, 

12 reject_renewal, 

13) 

14 

15from .forms import RegistrationAdminForm 

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

17 

18 

19class ReferenceInline(admin.StackedInline): 

20 model = Reference 

21 extra = 0 

22 

23 

24@admin.register(Registration) 

25class RegistrationAdmin(admin.ModelAdmin): 

26 """Manage the registrations.""" 

27 

28 list_display = ( 

29 "name", 

30 "email", 

31 "status", 

32 "membership_type", 

33 "contribution", 

34 "created_at", 

35 "payment", 

36 "no_references", 

37 "reference_count", 

38 ) 

39 list_filter = ( 

40 "status", 

41 "programme", 

42 "membership_type", 

43 "no_references", 

44 "payment__type", 

45 "contribution", 

46 ) 

47 inlines = (ReferenceInline,) 

48 search_fields = ( 

49 "first_name", 

50 "last_name", 

51 "email", 

52 "phone_number", 

53 "student_number", 

54 ) 

55 date_hierarchy = "created_at" 

56 fieldsets = ( 

57 ( 

58 _("Application information"), 

59 { 

60 "fields": ( 

61 "created_at", 

62 "updated_at", 

63 "username", 

64 "length", 

65 "contribution", 

66 "membership_type", 

67 "status", 

68 "payment", 

69 "remarks", 

70 ) 

71 }, 

72 ), 

73 ( 

74 _("Personal information"), 

75 { 

76 "fields": ( 

77 "first_name", 

78 "last_name", 

79 "birthday", 

80 "optin_birthday", 

81 "email", 

82 "optin_mailinglist", 

83 "phone_number", 

84 ) 

85 }, 

86 ), 

87 ( 

88 _("Address"), 

89 { 

90 "fields": ( 

91 "address_street", 

92 "address_street2", 

93 "address_postal_code", 

94 "address_city", 

95 "address_country", 

96 ) 

97 }, 

98 ), 

99 ( 

100 _("Financial"), 

101 { 

102 "fields": ( 

103 "direct_debit", 

104 "initials", 

105 "iban", 

106 "bic", 

107 "signature", 

108 ) 

109 }, 

110 ), 

111 ( 

112 _("University information"), 

113 { 

114 "fields": ( 

115 "student_number", 

116 "programme", 

117 "starting_year", 

118 ) 

119 }, 

120 ), 

121 ) 

122 

123 form = RegistrationAdminForm 

124 

125 actions = ["accept_registrations", "reject_registrations"] 

126 

127 def get_actions(self, request): 

128 actions = super().get_actions(request) 

129 

130 if not request.user.has_perm("registrations.review_entries"): 

131 if "accept_registrations" in actions: 

132 del actions["accept_registrations"] 

133 if "reject_registrations" in actions: 

134 del actions["reject_registrations"] 

135 

136 return actions 

137 

138 @admin.action(description="Accept selected registrations") 

139 def accept_registrations(self, request, queryset): # pragma: no cover 

140 if queryset.exclude(status=Registration.STATUS_REVIEW).exists(): 

141 self.message_user( 

142 request, "Only registrations in review can be accepted", "error" 

143 ) 

144 return 

145 

146 count = 0 

147 for registration in queryset: 

148 try: 

149 accept_registration(registration, actor=request.user) 

150 count += 1 

151 except ValueError as e: 

152 self.message_user( 

153 request, f"Error accepting {registration}: {e.message}", "error" 

154 ) 

155 

156 self.message_user(request, f"Accepted {count} registrations", "success") 

157 

158 @admin.action(description="Reject selected registrations") 

159 def reject_registrations(self, request, queryset): # pragma: no cover 

160 if queryset.exclude(status=Registration.STATUS_REVIEW).exists(): 

161 self.message_user( 

162 request, "Only registrations in review can be rejected", "error" 

163 ) 

164 return 

165 

166 count = queryset.count() 

167 for registration in queryset: 

168 reject_registration(registration, actor=request.user) 

169 

170 self.message_user(request, f"Rejected {count} registrations", "success") 

171 

172 def reference_count(self, obj): 

173 return obj.reference_set.count() 

174 

175 reference_count.short_description = _("references") 

176 

177 def get_form(self, request, obj=None, **kwargs): 

178 return super().get_form( 

179 request, 

180 obj, 

181 formfield_callback=partial( 

182 self.formfield_for_dbfield, request=request, obj=obj 

183 ), 

184 **kwargs, 

185 ) 

186 

187 def formfield_for_dbfield(self, db_field, request, obj=None, **kwargs): 

188 field = super().formfield_for_dbfield(db_field, request, **kwargs) 

189 if db_field.name == "payment": 

190 return Field( 

191 widget=PaymentWidget(obj=obj), initial=field.initial, required=False 

192 ) 

193 return field 

194 

195 def changeform_view(self, request, object_id=None, form_url="", extra_context=None): 

196 """Render the change formview. 

197 

198 Only allow when the entry has not been processed yet 

199 """ 

200 obj = None 

201 can_review = False 

202 can_resend = False 

203 can_revert = False 

204 if object_id is not None and request.user.has_perm( 

205 "registrations.review_entries" 

206 ): # pragma: no cover 

207 obj = self.get_object(request, object_id) 

208 if obj is None: 

209 return self._get_obj_does_not_exist_redirect( 

210 request, self.opts, object_id 

211 ) 

212 can_review = obj.status == Entry.STATUS_REVIEW 

213 can_revert = obj.status in [Entry.STATUS_ACCEPTED, Entry.STATUS_REJECTED] 

214 can_resend = obj.status == Entry.STATUS_CONFIRM and isinstance( 

215 obj, Registration 

216 ) 

217 

218 return super().changeform_view( 

219 request, 

220 object_id, 

221 form_url, 

222 { 

223 "entry": obj, 

224 "can_review": can_review, 

225 "can_resend": can_resend, 

226 "can_revert": can_revert, 

227 }, 

228 ) 

229 

230 def get_readonly_fields(self, request, obj=None): 

231 if obj is None or obj.status not in ( 

232 Entry.STATUS_REJECTED, 

233 Entry.STATUS_ACCEPTED, 

234 Entry.STATUS_COMPLETED, 

235 ): 

236 return ["status", "created_at", "updated_at", "payment"] 

237 return [ 

238 field.name 

239 for field in self.model._meta.get_fields() 

240 if field.name not in ["payment", "no_references"] and field.editable 

241 ] 

242 

243 @staticmethod 

244 def name(obj): 

245 return obj.get_full_name() 

246 

247 def has_change_permission(self, request, obj=None): 

248 """Completed registrations are read-only.""" 

249 return ( 

250 False 

251 if obj and obj.status == Entry.STATUS_COMPLETED 

252 else super().has_change_permission(request, obj) 

253 ) 

254 

255 def has_add_permission(self, request): 

256 return False 

257 

258 def save_model(self, request, obj, form, change): 

259 if obj.status not in ( 

260 Entry.STATUS_REJECTED, 

261 Entry.STATUS_ACCEPTED, 

262 Entry.STATUS_COMPLETED, 

263 ): 

264 super().save_model(request, obj, form, change) 

265 

266 

267@admin.register(Renewal) 

268class RenewalAdmin(RegistrationAdmin): 

269 """Manage the renewals.""" 

270 

271 list_display = ( 

272 "name", 

273 "email", 

274 "status", 

275 "membership_type", 

276 "contribution", 

277 "created_at", 

278 "payment", 

279 "no_references", 

280 "reference_count", 

281 ) 

282 list_filter = ( 

283 "status", 

284 "membership_type", 

285 "no_references", 

286 "payment__type", 

287 "contribution", 

288 ) 

289 search_fields = ( 

290 "member__first_name", 

291 "member__last_name", 

292 "member__email", 

293 "member__profile__phone_number", 

294 "member__profile__student_number", 

295 ) 

296 date_hierarchy = "created_at" 

297 fieldsets = ( 

298 ( 

299 _("Application information"), 

300 { 

301 "fields": ( 

302 "created_at", 

303 "updated_at", 

304 "length", 

305 "contribution", 

306 "membership_type", 

307 "status", 

308 "payment", 

309 "remarks", 

310 "member", 

311 ) 

312 }, 

313 ), 

314 ) 

315 

316 actions = ["accept_renewals", "reject_renewals"] 

317 

318 def get_actions(self, request): 

319 actions = super().get_actions(request) 

320 

321 if not request.user.has_perm("registrations.review_entries"): 

322 if "accept_renewals" in actions: # pragma: no cover 

323 del actions["accept_renewals"] 

324 if "reject_renewals" in actions: # pragma: no cover 

325 del actions["reject_renewals"] 

326 

327 return actions 

328 

329 @admin.action(description="Accept selected renewals") 

330 def accept_renewals(self, request, queryset): # pragma: no cover 

331 if queryset.exclude(status=Renewal.STATUS_REVIEW).exists(): 

332 self.message_user( 

333 request, "Only renewals in review can be accepted", "error" 

334 ) 

335 return 

336 

337 count = queryset.count() 

338 for renewal in queryset: 

339 accept_renewal(renewal, actor=request.user) 

340 count += 1 

341 

342 self.message_user(request, f"Accepted {count} renewals", "success") 

343 

344 @admin.action(description="Reject selected renewals") 

345 def reject_renewals(self, request, queryset): # pragma: no cover 

346 if queryset.exclude(status=Renewal.STATUS_REVIEW).exists(): 

347 self.message_user( 

348 request, "Only renewals in review can be rejected", "error" 

349 ) 

350 return 

351 

352 count = queryset.count() 

353 for renewal in queryset: 

354 reject_renewal(renewal, actor=request.user) 

355 

356 self.message_user(request, f"Rejected {count} renewals", "success") 

357 

358 def get_readonly_fields(self, request, obj=None): 

359 """Make all fields read-only and add member if needed.""" 

360 fields = super().get_readonly_fields(request, obj) 

361 if "member" not in fields and obj is not None: 

362 return fields + ["member"] 

363 return fields 

364 

365 def has_add_permission(self, request): 

366 return False 

367 

368 @staticmethod 

369 def name(obj): 

370 return obj.member.get_full_name() 

371 

372 name.short_description = _("name") 

373 

374 @staticmethod 

375 def email(obj): 

376 return obj.member.email