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:
@ -273,6 +273,7 @@ class Membership(models.Model):
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.PROTECT,
|
||||
related_name="memberships",
|
||||
verbose_name=_("user"),
|
||||
)
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -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
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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"),
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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"),
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user