Coverage for website/events/admin/views.py: 42.34%

115 statements  

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

1import csv 

2 

3from django.contrib import messages 

4from django.contrib.admin import helpers 

5from django.contrib.admin.views.decorators import staff_member_required 

6from django.contrib.auth.mixins import PermissionRequiredMixin 

7from django.http import HttpResponse 

8from django.shortcuts import get_object_or_404, redirect 

9from django.utils import timezone 

10from django.utils.decorators import method_decorator 

11from django.utils.text import slugify 

12from django.utils.translation import gettext_lazy as _ 

13from django.utils.translation import pgettext_lazy 

14from django.views import View 

15from django.views.generic import DetailView, FormView 

16 

17import qrcode 

18 

19from events import services 

20from events.decorators import organiser_only 

21from events.exceptions import RegistrationError 

22from events.forms import FieldsForm 

23from events.models import Event, EventRegistration 

24from payments.models import Payment 

25 

26 

27@method_decorator(staff_member_required, name="dispatch") 

28@method_decorator(organiser_only, name="dispatch") 

29class EventAdminDetails(DetailView, PermissionRequiredMixin): 

30 """Render an overview of registrations for the specified event.""" 

31 

32 template_name = "events/admin/details.html" 

33 model = Event 

34 context_object_name = "event" 

35 permission_required = "events.change_event" 

36 

37 def get_context_data(self, **kwargs): 

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

39 

40 context.update({"payment": Payment, "has_permission": True, "site_url": "/"}) 

41 

42 return context 

43 

44 

45@method_decorator(staff_member_required, name="dispatch") 

46@method_decorator(organiser_only, name="dispatch") 

47class RegistrationAdminFields(FormView): 

48 """Render a form that allows the user to change the details of their registration. 

49 

50 The user should be authenticated. 

51 """ 

52 

53 form_class = FieldsForm 

54 template_name = "admin/change_form.html" 

55 registration = None 

56 admin = None 

57 

58 def get_context_data(self, **kwargs): 

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

60 context.update( 

61 { 

62 **self.admin.admin_site.each_context(self.request), 

63 "add": False, 

64 "change": True, 

65 "has_view_permission": True, 

66 "has_add_permission": False, 

67 "has_change_permission": self.request.user.has_perm( 

68 "events.change_eventregistration" 

69 ), 

70 "has_delete_permission": False, 

71 "has_editable_inline_admin_formsets": False, 

72 "app_label": "events", 

73 "opts": self.registration._meta, 

74 "is_popup": False, 

75 "save_as": False, 

76 "save_on_top": False, 

77 "original": self.registration, 

78 "obj_id": self.registration.pk, 

79 "title": _("Change registration fields"), 

80 "adminform": helpers.AdminForm( 

81 context["form"], 

82 ((None, {"fields": context["form"].fields.keys()}),), 

83 {}, 

84 ), 

85 } 

86 ) 

87 return context 

88 

89 def get_form_kwargs(self): 

90 kwargs = super().get_form_kwargs() 

91 kwargs["fields"] = services.registration_fields( 

92 self.request, registration=self.registration 

93 ) 

94 return kwargs 

95 

96 def form_valid(self, form): 

97 values = form.field_values() 

98 try: 

99 services.update_registration( 

100 registration=self.registration, 

101 field_values=values, 

102 actor=self.request.member, 

103 ) 

104 messages.success(self.request, _("Registration successfully saved.")) 

105 if "_save" in self.request.POST: 

106 return redirect( 

107 "admin:events_eventregistration_change", self.registration.pk 

108 ) 

109 except RegistrationError as e: 

110 messages.error(self.request, e) 

111 return self.render_to_response(self.get_context_data(form=form)) 

112 

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

114 self.registration = get_object_or_404( 

115 EventRegistration, pk=self.kwargs["registration"] 

116 ) 

117 try: 

118 if self.registration.event.has_fields: 

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

120 except RegistrationError: 

121 pass 

122 return redirect("admin:events_eventregistration_change", self.registration.pk) 

123 

124 

125@method_decorator(staff_member_required, name="dispatch") 

126@method_decorator(organiser_only, name="dispatch") 

127class EventRegistrationsExport(View, PermissionRequiredMixin): 

128 """View to export registrations.""" 

129 

130 template_name = "events/admin/details.html" 

131 permission_required = "events.change_event" 

132 

133 def get(self, request, pk): 

134 """Export the registration of a specified event. 

135 

136 :param request: the request object 

137 :param pk: the primary key of the event 

138 :return: A CSV containing all registrations for the event 

139 """ 

140 event = get_object_or_404(Event, pk=pk) 

141 extra_fields = event.registrationinformationfield_set.all() 

142 registrations = event.eventregistration_set.all() 

143 

144 header_fields = ( 

145 [ 

146 _("Name"), 

147 _("Email"), 

148 _("Paid"), 

149 _("Present"), 

150 _("Status"), 

151 _("Phone number"), 

152 ] 

153 + [field.name for field in extra_fields] 

154 + [_("Date"), _("Date cancelled")] 

155 ) 

156 

157 rows = [] 

158 if event.price == 0: 

159 header_fields.remove(_("Paid")) 

160 for registration in registrations: 

161 if registration.member: 

162 name = registration.member.get_full_name() 

163 else: 

164 name = registration.name 

165 status = pgettext_lazy("registration status", "registered").capitalize() 

166 cancelled = None 

167 if registration.date_cancelled: 

168 if registration.is_late_cancellation(): 

169 status = pgettext_lazy( 

170 "registration status", "late cancellation" 

171 ).capitalize() 

172 else: 

173 status = pgettext_lazy( 

174 "registration status", "cancelled" 

175 ).capitalize() 

176 cancelled = timezone.localtime(registration.date_cancelled) 

177 

178 elif registration.queue_position: 

179 status = pgettext_lazy("registration status", "waiting") 

180 data = { 

181 _("Name"): name, 

182 _("Date"): timezone.localtime(registration.date), 

183 _("Present"): _("Yes") if registration.present else "", 

184 _("Phone number"): ( 

185 registration.phone_number if registration.phone_number else "" 

186 ), 

187 _("Email"): (registration.email if registration.email else ""), 

188 _("Status"): status, 

189 _("Date cancelled"): cancelled, 

190 } 

191 if event.price > 0: 

192 if registration.is_paid(): 

193 data[_("Paid")] = registration.payment.get_type_display() 

194 else: 

195 data[_("Paid")] = _("No") 

196 

197 data.update( 

198 { 

199 field["field"].name: field["value"] 

200 for field in registration.information_fields 

201 } 

202 ) 

203 rows.append(data) 

204 

205 response = HttpResponse(content_type="text/csv") 

206 writer = csv.DictWriter(response, header_fields) 

207 writer.writeheader() 

208 

209 rows = sorted( 

210 rows, 

211 key=lambda row: ( 

212 row[_("Status")] 

213 == pgettext_lazy( 

214 "registration status", "late cancellation" 

215 ).capitalize(), 

216 row[_("Date")], 

217 ), 

218 reverse=True, 

219 ) 

220 

221 for row in rows: 

222 writer.writerow(row) 

223 

224 response["Content-Disposition"] = ( 

225 f'attachment; filename="{slugify(event.title)}.csv"' 

226 ) 

227 return response 

228 

229 

230@method_decorator(staff_member_required, name="dispatch") 

231@method_decorator(organiser_only, name="dispatch") 

232class EventMarkPresentQR(View): 

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

234 event = get_object_or_404(Event, pk=kwargs["pk"]) 

235 image = qrcode.make(event.mark_present_url) 

236 

237 response = HttpResponse(content_type="image/png") 

238 image.save(response, "PNG") 

239 

240 return response