1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-12-03 18:44:52 +01:00

pdf for guest list

This commit is contained in:
quark
2025-12-03 04:47:44 +01:00
parent 13171899c2
commit 7500c33f0f
4 changed files with 124 additions and 48 deletions

View File

@@ -1,7 +1,11 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import os
import shutil
import subprocess
from hashlib import md5
from tempfile import mkdtemp
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
@@ -9,16 +13,19 @@ from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import F, Q
from django.db.models.functions.text import Lower
from django.http import HttpResponse, JsonResponse
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.template.loader import render_to_string
from django.views import View
from django.views.decorators.cache import cache_page
from django.views.generic import DetailView, TemplateView, UpdateView
from django.views.generic.list import ListView
from django_tables2.views import MultiTableMixin, SingleTableMixin
from note_kfet.settings import BASE_DIR
from api.viewsets import is_regex
from note.models import Alias, NoteSpecial, NoteUser
from permission.backends import PermissionBackend
@@ -159,51 +166,6 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMix
.distinct(),
]
def render_to_response(self, context, **response_kwargs):
"""
Gère l'export CSV manuel pour MultiTableMixin.
"""
if "_export" in self.request.GET:
import tablib
table_name = self.request.GET.get("table")
if table_name:
tables = self.get_tables()
data_list = self.get_tables_data()
for t, d in zip(tables, data_list):
if t.prefix == table_name:
# Préparer le CSV
dataset = tablib.Dataset()
columns = list(t.base_columns) # noms des colonnes
dataset.headers = columns
for row in d:
values = []
for col in columns:
try:
val = getattr(row, col, "")
# Gestion spéciale pour la colonne 'entry'
if col == "entry":
if getattr(row, "has_entry", False):
val = timezone.localtime(row.entry.time).strftime("%Y-%m-%d %H:%M:%S")
else:
val = ""
values.append(str(val) if val is not None else "")
except Exception: # RelatedObjectDoesNotExist ou autre
values.append("")
dataset.append(values)
csv_bytes = dataset.export("csv")
if isinstance(csv_bytes, str):
csv_bytes = csv_bytes.encode("utf-8")
response = HttpResponse(csv_bytes, content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="{table_name}.csv"'
return response
# Sinon rendu normal
return super().render_to_response(context, **response_kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data()
@@ -233,6 +195,10 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMix
context["entries_count"] = {self.object: 0}
context["show_entries"] = {self.object: False}
guests = Guest.objects.filter(activity=self.object)
guests_view = guests.filter(PermissionBackend.filter_queryset(self.request, Guest, "view"))
if guests.exists() and guests.count() == guests_view.count():
context["export"] = True
return context
@@ -463,6 +429,71 @@ class ActivityEntryView(LoginRequiredMixin, SingleTableMixin, TemplateView):
return context
class GuestListRenderView(LoginRequiredMixin, View):
"""
Render a generated PDF with the given information and a LaTeX template
"""
def get_queryset(self, **kwargs):
qs = Guest.objects.filter(PermissionBackend.filter_queryset(self.request, Guest, "view"))
qs = qs.filter(activity__pk=self.kwargs["activity_pk"]).order_by(
Lower('last_name'),
Lower('first_name'),
'id',
)
return qs.distinct()
def get(self, request, **kwargs):
qs = self.get_queryset()
activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
if not qs.exists() or qs.count() != Guest.objects.filter(activity=activity).count():
raise PermissionDenied(_("You are not allowed to export the guest list for this activity."))
# Fill the template with the information
tex = render_to_string("activity/guestlist_sample.tex", dict(guests=qs.all(), activity=activity, total=qs.count()))
try:
os.mkdir(BASE_DIR + "/tmp")
except FileExistsError:
pass
# We render the file in a temporary directory
tmp_dir = mkdtemp(prefix=BASE_DIR + "/tmp/")
try:
with open("{}/guest-list.tex".format(tmp_dir), "wb") as f:
f.write(tex.encode("UTF-8"))
del tex
with open(os.devnull, "wb") as devnull:
error = subprocess.Popen(
["/usr/bin/xelatex", "-interaction=nonstopmode", "{}/guest-list.tex".format(tmp_dir)],
cwd=tmp_dir,
stderr=devnull,
stdout=devnull,
).wait()
if error:
with open("{}/guest-list.log".format(tmp_dir), "r") as f:
log = f.read()
raise IOError("An error attempted while generating a Guest list (code=" + str(error) + ")\n\n" + log)
# Display the generated pdf as a HTTP Response
with open("{}/guest-list.pdf".format(tmp_dir), 'rb') as f:
pdf = f.read()
response = HttpResponse(pdf, content_type="application/pdf")
response['Content-Disposition'] = "inline;filename=Liste des invité·e·s.pdf"
except IOError as e:
raise e
finally:
# Delete all temporary files
shutil.rmtree(tmp_dir)
return response
# Cache for 1 hour
@method_decorator(cache_page(60 * 60), name='dispatch')
class CalendarView(View):