1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-06-20 17:41:55 +02:00

Add PDF member lists

This commit is contained in:
Yohann D'ANELLO
2020-04-23 18:28:16 +02:00
parent 38b32b0623
commit b81f186866
15 changed files with 427 additions and 166 deletions

View File

@ -273,6 +273,7 @@ class Membership(models.Model):
user = models.ForeignKey(
User,
on_delete=models.PROTECT,
related_name="memberships",
verbose_name=_("user"),
)

View File

@ -36,13 +36,15 @@ class PermissionBackend(ModelBackend):
# Unauthenticated users have no permissions
return Permission.objects.none()
return Permission.objects.annotate(club=F("rolepermissions__role__membership__club")) \
.filter(
rolepermissions__role__membership__user=user,
rolepermissions__role__membership__date_start__lte=datetime.date.today(),
rolepermissions__role__membership__date_end__gte=datetime.date.today(),
type=t,
mask__rank__lte=get_current_session().get("permission_mask", 0),
return Permission.objects.annotate(
club=F("rolepermissions__role__membership__club"),
membership=F("rolepermissions__role__membership"),
).filter(
rolepermissions__role__membership__user=user,
rolepermissions__role__membership__date_start__lte=datetime.date.today(),
rolepermissions__role__membership__date_end__gte=datetime.date.today(),
type=t,
mask__rank__lte=get_current_session().get("permission_mask", 0),
).distinct()
@staticmethod
@ -55,6 +57,7 @@ class PermissionBackend(ModelBackend):
:return: A generator of the requested permissions
"""
clubs = {}
memberships = {}
for permission in PermissionBackend.get_raw_permissions(user, type):
if not isinstance(model.model_class()(), permission.model.model_class()) or not permission.club:
@ -64,9 +67,16 @@ class PermissionBackend(ModelBackend):
clubs[permission.club] = club = Club.objects.get(pk=permission.club)
else:
club = clubs[permission.club]
if permission.membership not in memberships:
memberships[permission.membership] = membership = Membership.objects.get(pk=permission.membership)
else:
membership = memberships[permission.membership]
permission = permission.about(
user=user,
club=club,
membership=membership,
User=User,
Club=Club,
Membership=Membership,

View File

@ -1470,7 +1470,7 @@
"wei",
"weiregistration"
],
"query": "{\"user\": [\"user\"], \"wei\": [\"club\"], \"wei__membership_start__lte\": [\"today\"], \"wei__year\": [\"today\", \"year\"]}",
"query": "{\"user\": [\"user\"], \"wei\": [\"club\"], \"wei__membership_start__lte\": [\"today\"]}",
"type": "view",
"mask": 1,
"field": "",
@ -1882,6 +1882,36 @@
"description": "View my own WEI membership if I am an old member or if the WEI is past"
}
},
{
"model": "permission.permission",
"pk": 115,
"fields": {
"model": [
"wei",
"weimembership"
],
"query": "{\"wei\": [\"club\"], \"bus\": [\"membership\", \"weimembership\", \"bus\"]}",
"type": "view",
"mask": 1,
"field": "",
"description": "View the members of the bus"
}
},
{
"model": "permission.permission",
"pk": 116,
"fields": {
"model": [
"wei",
"weimembership"
],
"query": "{\"wei\": [\"club\"], \"team\": [\"membership\", \"weimembership\", \"team\"]}",
"type": "view",
"mask": 1,
"field": "",
"description": "View the members of the team"
}
},
{
"model": "permission.rolepermissions",
"pk": 1,
@ -2230,5 +2260,25 @@
113
]
}
},
{
"model": "permission.rolepermissions",
"pk": 13,
"fields": {
"role": 13,
"permissions": [
115
]
}
},
{
"model": "permission.rolepermissions",
"pk": 14,
"fields": {
"role": 14,
"permissions": [
116
]
}
}
]

View File

@ -38,6 +38,7 @@ class UserCreateView(CreateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["profile_form"] = self.second_form()
del context["profile_form"].fields["section"]
return context

View File

@ -182,7 +182,7 @@ class InvoiceRenderView(LoginRequiredMixin, View):
# Display the generated pdf as a HTTP Response
pdf = open("{}/invoice-{}.pdf".format(tmp_dir, pk), 'rb').read()
response = HttpResponse(pdf, content_type="application/pdf")
response['Content-Disposition'] = "inline;filename=invoice-{:d}.pdf".format(pk)
response['Content-Disposition'] = "inline;filename=Facture%20n°{:d}.pdf".format(pk)
except IOError as e:
raise e
finally:

View File

@ -265,6 +265,7 @@ class WEIMembership(Membership):
bus = models.ForeignKey(
Bus,
on_delete=models.PROTECT,
related_name="memberships",
null=True,
default=None,
verbose_name=_("bus"),

View File

@ -91,6 +91,11 @@ class WEIMembershipTable(tables.Table):
args=[A('registration.pk')],
)
year = tables.Column(
accessor=A("pk"),
verbose_name=_("Year"),
)
bus = tables.LinkColumn(
'wei:manage_bus',
args=[A('bus.pk')],
@ -101,13 +106,17 @@ class WEIMembershipTable(tables.Table):
args=[A('bus.pk')],
)
def render_year(self, record):
return str(record.user.profile.ens_year) + "A"
class Meta:
attrs = {
'class': 'table table-condensed table-striped table-hover'
}
model = WEIMembership
template_name = 'django_tables2/bootstrap4.html'
fields = ('user', 'user.first_name', 'user.last_name', 'bus', 'team', )
fields = ('user', 'user.last_name', 'user.first_name', 'registration.gender', 'user.profile.department',
'year', 'bus', 'team', )
row_attrs = {
'class': 'table-row',
'id': lambda record: "row-" + str(record.pk),
@ -130,9 +139,16 @@ class BusTable(tables.Table):
}
)
count = tables.Column(
verbose_name=_("Members count"),
)
def render_teams(self, value):
return ", ".join(team.name for team in value.all())
def render_count(self, value):
return str(value) + " " + (str(_("members")) if value > 0 else str(_("member")))
class Meta:
attrs = {
'class': 'table table-condensed table-striped table-hover'
@ -161,6 +177,13 @@ class BusTeamTable(tables.Table):
}
)
def render_count(self, value):
return str(value) + " " + (str(_("members")) if value > 0 else str(_("member")))
count = tables.Column(
verbose_name=_("Members count"),
)
def render_color(self, value):
return "#{:06X}".format(value)

View File

@ -4,7 +4,7 @@
from django.urls import path
from .views import CurrentWEIDetailView, WEIListView, WEICreateView, WEIDetailView, WEIUpdateView,\
WEIRegistrationsView, WEIMembershipsView,\
WEIRegistrationsView, WEIMembershipsView, MemberListRenderView,\
BusCreateView, BusManageView, BusUpdateView, BusTeamCreateView, BusTeamManageView, BusTeamUpdateView,\
WEIRegister1AView, WEIRegister2AView, WEIUpdateRegistrationView, WEIDeleteRegistrationView,\
WEIValidateRegistrationView, WEISurveyView, WEISurveyEndView, WEIClosedView
@ -19,6 +19,11 @@ urlpatterns = [
path('update/<int:pk>/', WEIUpdateView.as_view(), name="wei_update"),
path('detail/<int:pk>/registrations/', WEIRegistrationsView.as_view(), name="wei_registrations"),
path('detail/<int:pk>/memberships/', WEIMembershipsView.as_view(), name="wei_memberships"),
path('detail/<int:wei_pk>/memberships/pdf/', MemberListRenderView.as_view(), name="wei_memberships_pdf"),
path('detail/<int:wei_pk>/memberships/pdf/<int:bus_pk>/', MemberListRenderView.as_view(),
name="wei_memberships_bus_pdf"),
path('detail/<int:wei_pk>/memberships/pdf/<int:bus_pk>/<int:team_pk>/', MemberListRenderView.as_view(),
name="wei_memberships_team_pdf"),
path('add-bus/<int:pk>/', BusCreateView.as_view(), name="add_bus"),
path('manage-bus/<int:pk>/', BusManageView.as_view(), name="manage_bus"),
path('update-bus/<int:pk>/', BusUpdateView.as_view(), name="update_bus"),

View File

@ -1,15 +1,23 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import os
import shutil
import subprocess
from datetime import datetime, date
from tempfile import mkdtemp
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.db.models import Q, Count
from django.db.models.functions import Lower
from django.forms import HiddenInput
from django.http import HttpResponse
from django.shortcuts import redirect
from django.template.loader import render_to_string
from django.urls import reverse_lazy
from django.views import View
from django.views.generic import DetailView, UpdateView, CreateView, RedirectView, TemplateView
from django.utils.translation import gettext_lazy as _
from django.views.generic.edit import BaseFormView, DeleteView
@ -17,6 +25,7 @@ from django_tables2 import SingleTableView
from member.models import Membership, Club
from note.models import Transaction, NoteClub, Alias
from note.tables import HistoryTable
from note_kfet.settings import BASE_DIR
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin
@ -108,7 +117,7 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
context["my_registration"] = my_registration
buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request.user, Bus, "view"))\
.filter(wei=self.object)
.filter(wei=self.object).annotate(count=Count("memberships"))
bus_table = BusTable(data=buses, prefix="bus-")
context['buses'] = bus_table
@ -299,7 +308,7 @@ class BusManageView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
bus = self.object
teams = BusTeam.objects.filter(PermissionBackend.filter_queryset(self.request.user, BusTeam, "view"))\
.filter(bus=bus)
.filter(bus=bus).annotate(count=Count("memberships"))
teams_table = BusTeamTable(data=teams, prefix="team-")
context["teams"] = teams_table
@ -824,3 +833,80 @@ class WEIClosedView(LoginRequiredMixin, TemplateView):
context["club"] = WEIClub.objects.get(pk=self.kwargs["pk"])
context["title"] = _("Survey WEI")
return context
class MemberListRenderView(LoginRequiredMixin, View):
"""
Render Invoice as a generated PDF with the given information and a LaTeX template
"""
def get_queryset(self, **kwargs):
qs = WEIMembership.objects.filter(PermissionBackend.filter_queryset(self.request.user, WEIMembership, "view"))
qs = qs.filter(club__pk=self.kwargs["wei_pk"]).order_by(
Lower('bus__name'),
Lower('team__name'),
'roles',
Lower('user__last_name'),
Lower('user__first_name'),
).distinct()
if "bus_pk" in self.kwargs:
qs = qs.filter(bus__pk=self.kwargs["bus_pk"])
if "team_pk" in self.kwargs:
qs = qs.filter(team__pk=self.kwargs["team_pk"] if self.kwargs["team_pk"] else None)
return qs
def get(self, request, **kwargs):
qs = self.get_queryset()
wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
bus = team = None
if "bus_pk" in self.kwargs:
bus = Bus.objects.get(pk=self.kwargs["bus_pk"])
if "team_pk" in self.kwargs:
team = BusTeam.objects.filter(pk=self.kwargs["team_pk"] if self.kwargs["team_pk"] else None)
if team.exists():
team = team.get()
bus = team.bus
else:
team = dict(name="Staff")
# Fill the template with the information
tex = render_to_string("wei/weilist_sample.tex", dict(memberships=qs.all(), wei=wei, bus=bus, team=team))
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("{}/wei-list.tex".format(tmp_dir), "wb") as f:
f.write(tex.encode("UTF-8"))
del tex
error = subprocess.Popen(
["pdflatex", "{}/wei-list.tex".format(tmp_dir)],
cwd=tmp_dir,
stdin=open(os.devnull, "r"),
stderr=open(os.devnull, "wb"),
stdout=open(os.devnull, "wb"),
).wait()
if error:
raise IOError("An error attempted while generating a WEI list (code=" + str(error) + ")")
# Display the generated pdf as a HTTP Response
pdf = open("{}/wei-list.pdf".format(tmp_dir), 'rb').read()
response = HttpResponse(pdf, content_type="application/pdf")
response['Content-Disposition'] = "inline;filename=Liste%20des%20participants%20au%20WEI.pdf"
except IOError as e:
raise e
finally:
# Delete all temporary files
shutil.rmtree(tmp_dir)
return response