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
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1import csv
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
17import qrcode
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
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."""
32 template_name = "events/admin/details.html"
33 model = Event
34 context_object_name = "event"
35 permission_required = "events.change_event"
37 def get_context_data(self, **kwargs):
38 context = super().get_context_data(**kwargs)
40 context.update({"payment": Payment, "has_permission": True, "site_url": "/"})
42 return context
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.
50 The user should be authenticated.
51 """
53 form_class = FieldsForm
54 template_name = "admin/change_form.html"
55 registration = None
56 admin = None
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
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
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))
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)
125@method_decorator(staff_member_required, name="dispatch")
126@method_decorator(organiser_only, name="dispatch")
127class EventRegistrationsExport(View, PermissionRequiredMixin):
128 """View to export registrations."""
130 template_name = "events/admin/details.html"
131 permission_required = "events.change_event"
133 def get(self, request, pk):
134 """Export the registration of a specified event.
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()
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 )
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)
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")
197 data.update(
198 {
199 field["field"].name: field["value"]
200 for field in registration.information_fields
201 }
202 )
203 rows.append(data)
205 response = HttpResponse(content_type="text/csv")
206 writer = csv.DictWriter(response, header_fields)
207 writer.writeheader()
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 )
221 for row in rows:
222 writer.writerow(row)
224 response["Content-Disposition"] = (
225 f'attachment; filename="{slugify(event.title)}.csv"'
226 )
227 return response
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)
237 response = HttpResponse(content_type="image/png")
238 image.save(response, "PNG")
240 return response