nk20/apps/wei/views.py

1297 lines
52 KiB
Python

# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import os
import shutil
import subprocess
from datetime import date, timedelta
from tempfile import mkdtemp
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import Q, Count
from django.db.models.functions.text import Lower
from django.http import HttpResponse, Http404
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, RedirectView, TemplateView
from django.utils.translation import gettext_lazy as _
from django.views.generic.edit import BaseFormView, DeleteView
from django_tables2 import SingleTableView, MultiTableMixin
from api.viewsets import is_regex
from member.models import Membership, Club
from note.models import Transaction, NoteClub, Alias, SpecialTransaction, NoteSpecial
from note.tables import HistoryTable
from note_kfet.settings import BASE_DIR
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .forms.registration import WEIChooseBusForm
from .models import WEIClub, WEIRegistration, WEIMembership, Bus, BusTeam, WEIRole
from .forms import WEIForm, WEIRegistrationForm, BusForm, BusTeamForm, WEIMembership1AForm, \
WEIMembershipForm, CurrentSurvey
from .tables import BusRepartitionTable, BusTable, BusTeamTable, WEITable, WEIRegistrationTable, \
WEIRegistration1ATable, WEIMembershipTable
class CurrentWEIDetailView(LoginRequiredMixin, RedirectView):
def get_redirect_url(self, *args, **kwargs):
wei = WEIClub.objects.filter(membership_start__lte=date.today()).order_by('date_start')
if wei.exists():
wei = wei.last()
return reverse_lazy('wei:wei_detail', args=(wei.pk,))
else:
return reverse_lazy('wei:wei_list')
class WEIListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
"""
List existing WEI
"""
model = WEIClub
table_class = WEITable
ordering = '-year'
extra_context = {"title": _("Search WEI")}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["can_create_wei"] = PermissionBackend.check_perm(self.request, "wei.add_weiclub", WEIClub(
name="",
email="weiclub@example.com",
year=0,
date_start=date.today(),
date_end=date.today(),
))
return context
class WEICreateView(ProtectQuerysetMixin, ProtectedCreateView):
"""
Create WEI
"""
model = WEIClub
form_class = WEIForm
extra_context = {"title": _("Create WEI")}
def get_sample_object(self):
return WEIClub(
name="",
email="weiclub@example.com",
year=0,
date_start=date.today(),
date_end=date.today(),
)
@transaction.atomic
def form_valid(self, form):
form.instance.requires_membership = True
form.instance.parent_club = Club.objects.get(name="Kfet")
ret = super().form_valid(form)
NoteClub.objects.create(club=form.instance)
return ret
def get_success_url(self):
self.object.refresh_from_db()
return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.pk})
class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, DetailView):
"""
View WEI information
"""
model = WEIClub
context_object_name = "club"
extra_context = {"title": _("WEI Detail")}
tables = [
lambda data: HistoryTable(data, prefix="history-"),
lambda data: WEIMembershipTable(data, prefix="membership-"),
lambda data: WEIRegistrationTable(data, prefix="pre-registration-"),
lambda data: BusTable(data, prefix="bus-"),
]
paginate_by = 20 # number of rows in tables
def get_tables_data(self):
club = self.object
club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note)) \
.filter(PermissionBackend.filter_queryset(self.request, Transaction, "view")) \
.order_by('-created_at', '-id')
club_member = WEIMembership.objects.filter(
club=club,
date_end__gte=date.today(),
).filter(PermissionBackend.filter_queryset(self.request, WEIMembership, "view"))
pre_registrations = WEIRegistration.objects.filter(
PermissionBackend.filter_queryset(self.request, WEIRegistration, "view")).filter(
membership=None,
wei=club
)
buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request, Bus, "view")) \
.filter(wei=self.object).annotate(count=Count("memberships")).order_by("name")
return [club_transactions, club_member, pre_registrations, buses, ]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
club = context["club"]
tables = context["tables"]
for name, table in zip(["history_list", "member_list", "pre_registrations", "buses"], tables):
context[name] = table
my_registration = WEIRegistration.objects.filter(wei=club, user=self.request.user)
if my_registration.exists():
my_registration = my_registration.get()
else:
my_registration = None
context["my_registration"] = my_registration
random_user = User.objects.filter(~Q(wei__wei__in=[club])).first()
if random_user is None:
# This case occurs when all users are registered to the WEI.
# Don't worry, Pikachu never went to the WEI.
# This bug can arrive only in dev mode.
context["can_add_first_year_member"] = True
context["can_add_any_member"] = True
else:
# Check if the user has the right to create a registration of a random first year member.
empty_fy_registration = WEIRegistration(
wei=club,
user=random_user,
first_year=True,
birth_date="1970-01-01",
gender="No",
emergency_contact_name="No",
emergency_contact_phone="No",
)
context["can_add_first_year_member"] = PermissionBackend \
.check_perm(self.request, "wei.add_weiregistration", empty_fy_registration)
# Check if the user has the right to create a registration of a random old member.
empty_old_registration = WEIRegistration(
wei=club,
user=User.objects.filter(~Q(wei__wei__in=[club])).first(),
first_year=False,
birth_date="1970-01-01",
gender="No",
emergency_contact_name="No",
emergency_contact_phone="No",
)
context["can_add_any_member"] = PermissionBackend \
.check_perm(self.request, "wei.add_weiregistration", empty_old_registration)
empty_bus = Bus(
wei=club,
name="",
)
context["can_add_bus"] = PermissionBackend.check_perm(self.request, "wei.add_bus", empty_bus)
context["not_first_year"] = WEIMembership.objects.filter(user=self.request.user).exists()
qs = WEIMembership.objects.filter(club=club, registration__first_year=True, bus__isnull=True)
context["can_validate_1a"] = PermissionBackend.check_perm(
self.request, "wei.change_weimembership_bus", qs.first()) if qs.exists() else False
return context
class WEIMembershipsView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
"""
List all WEI memberships
"""
model = WEIMembership
table_class = WEIMembershipTable
extra_context = {"title": _("View members of the WEI")}
def dispatch(self, request, *args, **kwargs):
self.club = WEIClub.objects.get(pk=self.kwargs["pk"])
return super().dispatch(request, *args, **kwargs)
def get_queryset(self, **kwargs):
qs = super().get_queryset(**kwargs).filter(club=self.club).distinct()
pattern = self.request.GET.get("search", "")
if not pattern:
return qs.none()
# Check if this is a valid regex. If not, we won't check regex
valid_regex = is_regex(pattern)
suffix_alias = "__iregex" if valid_regex else "__istartswith"
suffix = "__iregex" if valid_regex else "__icontains"
prefix = "^" if valid_regex else ""
qs = qs.filter(
Q(**{f"user__first_name{suffix}": pattern})
| Q(**{f"user__last_name{suffix}": pattern})
| Q(**{f"user__note__alias__name{suffix_alias}": prefix + pattern})
| Q(**{f"user__note__alias__normalized_name{suffix_alias}": prefix + Alias.normalize(pattern)})
| Q(**{f"bus__name{suffix}": pattern})
| Q(**{f"team__name{suffix}": pattern})
)
return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["club"] = self.club
context["title"] = _("Find WEI Membership")
return context
class WEIRegistrationsView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
"""
List all non-validated WEI registrations.
"""
model = WEIRegistration
table_class = WEIRegistrationTable
extra_context = {"title": _("View registrations to the WEI")}
def dispatch(self, request, *args, **kwargs):
self.club = WEIClub.objects.get(pk=self.kwargs["pk"])
return super().dispatch(request, *args, **kwargs)
def get_queryset(self, **kwargs):
qs = super().get_queryset(**kwargs).filter(wei=self.club, membership=None).distinct()
pattern = self.request.GET.get("search", "")
if pattern:
# Check if this is a valid regex. If not, we won't check regex
valid_regex = is_regex(pattern)
suffix_alias = "__iregex" if valid_regex else "__istartswith"
suffix = "__iregex" if valid_regex else "__icontains"
prefix = "^" if valid_regex else ""
qs = qs.filter(
Q(**{f"user__first_name{suffix}": pattern})
| Q(**{f"user__last_name{suffix}": pattern})
| Q(**{f"user__note__alias__name{suffix_alias}": prefix + pattern})
| Q(**{f"user__note__alias__normalized_name{suffix_alias}": prefix + Alias.normalize(pattern)})
)
return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["club"] = self.club
context["title"] = _("Find WEI Registration")
return context
class WEIUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
"""
Update the information of the WEI.
"""
model = WEIClub
context_object_name = "club"
form_class = WEIForm
extra_context = {"title": _("Update the WEI")}
def dispatch(self, request, *args, **kwargs):
wei = self.get_object()
today = date.today()
# We can't update a past WEI
# But we can update it while it is not officially opened
if today > wei.date_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
return super().dispatch(request, *args, **kwargs)
def get_success_url(self):
return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.pk})
class BusCreateView(ProtectQuerysetMixin, ProtectedCreateView):
"""
Create Bus
"""
model = Bus
form_class = BusForm
extra_context = {"title": _("Create new bus")}
def get_sample_object(self):
wei = WEIClub.objects.get(pk=self.kwargs["pk"])
return Bus(
wei=wei,
name="",
)
def dispatch(self, request, *args, **kwargs):
wei = WEIClub.objects.get(pk=self.kwargs["pk"])
today = date.today()
# We can't add a bus once the WEI is started
if today >= wei.date_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["club"] = WEIClub.objects.get(pk=self.kwargs["pk"])
return context
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields["wei"].initial = WEIClub.objects.get(pk=self.kwargs["pk"])
return form
def get_success_url(self):
self.object.refresh_from_db()
return reverse_lazy("wei:manage_bus", kwargs={"pk": self.object.pk})
class BusUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
"""
Update Bus
"""
model = Bus
form_class = BusForm
extra_context = {"title": _("Update bus")}
def dispatch(self, request, *args, **kwargs):
wei = self.get_object().wei
today = date.today()
# We can't update a bus once the WEI is started
if today >= wei.date_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["club"] = self.object.wei
context["information"] = CurrentSurvey.get_algorithm_class().get_bus_information(self.object)
self.object.save()
return context
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields["wei"].disabled = True
return form
def get_success_url(self):
self.object.refresh_from_db()
return reverse_lazy("wei:manage_bus", kwargs={"pk": self.object.pk})
class BusManageView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
"""
Manage Bus
"""
model = Bus
extra_context = {"title": _("Manage bus")}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["club"] = self.object.wei
bus = self.object
teams = BusTeam.objects.filter(PermissionBackend.filter_queryset(self.request, BusTeam, "view")) \
.filter(bus=bus).annotate(count=Count("memberships")).order_by("name")
teams_table = BusTeamTable(data=teams, prefix="team-")
context["teams"] = teams_table
memberships = WEIMembership.objects.filter(PermissionBackend.filter_queryset(
self.request, WEIMembership, "view")).filter(bus=bus)
memberships_table = WEIMembershipTable(data=memberships, prefix="membership-")
memberships_table.paginate(per_page=20, page=self.request.GET.get("membership-page", 1))
context["memberships"] = memberships_table
return context
class BusTeamCreateView(ProtectQuerysetMixin, ProtectedCreateView):
"""
Create BusTeam
"""
model = BusTeam
form_class = BusTeamForm
extra_context = {"title": _("Create new team")}
def get_sample_object(self):
bus = Bus.objects.get(pk=self.kwargs["pk"])
return BusTeam(
name="",
bus=bus,
color=0,
)
def dispatch(self, request, *args, **kwargs):
wei = WEIClub.objects.get(buses__pk=self.kwargs["pk"])
today = date.today()
# We can't add a team once the WEI is started
if today >= wei.date_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
bus = Bus.objects.get(pk=self.kwargs["pk"])
context["club"] = bus.wei
return context
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields["bus"].initial = Bus.objects.get(pk=self.kwargs["pk"])
return form
def get_success_url(self):
self.object.refresh_from_db()
return reverse_lazy("wei:manage_bus_team", kwargs={"pk": self.object.pk})
class BusTeamUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
"""
Update Bus team
"""
model = BusTeam
form_class = BusTeamForm
extra_context = {"title": _("Update team")}
def dispatch(self, request, *args, **kwargs):
wei = self.get_object().bus.wei
today = date.today()
# We can't update a bus once the WEI is started
if today >= wei.date_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["club"] = self.object.bus.wei
context["bus"] = self.object.bus
return context
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields["bus"].disabled = True
return form
def get_success_url(self):
self.object.refresh_from_db()
return reverse_lazy("wei:manage_bus_team", kwargs={"pk": self.object.pk})
class BusTeamManageView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
"""
Manage Bus team
"""
model = BusTeam
extra_context = {"title": _("Manage WEI team")}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["bus"] = self.object.bus
context["club"] = self.object.bus.wei
memberships = WEIMembership.objects.filter(PermissionBackend.filter_queryset(
self.request, WEIMembership, "view")).filter(team=self.object)
memberships_table = WEIMembershipTable(data=memberships, prefix="membership-")
memberships_table.paginate(per_page=20, page=self.request.GET.get("membership-page", 1))
context["memberships"] = memberships_table
return context
class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView):
"""
Register a new user to the WEI
"""
model = WEIRegistration
form_class = WEIRegistrationForm
extra_context = {"title": _("Register first year student to the WEI")}
def get_sample_object(self):
wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
if "myself" in self.request.path:
user = self.request.user
else:
# To avoid unique validation issues, we use an account that can't join the WEI.
# In development mode, the note account may not exist, we use a random user (may fail)
user = User.objects.get(username="note") \
if User.objects.filter(username="note").exists() else User.objects.first()
return WEIRegistration(
wei=wei,
user=user,
first_year=True,
birth_date="1970-01-01",
gender="No",
emergency_contact_name="No",
emergency_contact_phone="No",
)
def dispatch(self, request, *args, **kwargs):
wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
today = date.today()
# We can't register someone once the WEI is started and before the membership start date
if today >= wei.date_start or today < wei.membership_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
# Don't register twice
if 'myself' in self.request.path and not self.request.user.is_anonymous \
and WEIRegistration.objects.filter(wei=wei, user=self.request.user).exists():
obj = WEIRegistration.objects.get(wei=wei, user=self.request.user)
return redirect(reverse_lazy('wei:wei_update_registration', args=(obj.pk,)))
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = _("Register 1A")
context['club'] = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
if "myself" in self.request.path:
context["form"].fields["user"].disabled = True
return context
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields["user"].initial = self.request.user
del form.fields["first_year"]
del form.fields["caution_check"]
del form.fields["information_json"]
return form
@transaction.atomic
def form_valid(self, form):
form.instance.wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
form.instance.first_year = True
if not form.instance.pk:
# Check if the user is not already registered to the WEI
if WEIRegistration.objects.filter(wei=form.instance.wei, user=form.instance.user).exists():
form.add_error('user', _("This user is already registered to this WEI."))
return self.form_invalid(form)
# Check if the user can be in her/his first year (yeah, no cheat)
if WEIRegistration.objects.filter(user=form.instance.user).exists():
form.add_error('user', _("This user can't be in her/his first year since he/she has already"
" participated to a WEI."))
return self.form_invalid(form)
if 'treasury' in settings.INSTALLED_APPS:
from treasury.models import SogeCredit
form.instance.soge_credit = \
form.instance.soge_credit \
or SogeCredit.objects.filter(user=form.instance.user, credit_transaction__valid=False).exists()
return super().form_valid(form)
def get_success_url(self):
self.object.refresh_from_db()
return reverse_lazy("wei:wei_survey", kwargs={"pk": self.object.pk})
class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView):
"""
Register an old user to the WEI
"""
model = WEIRegistration
form_class = WEIRegistrationForm
extra_context = {"title": _("Register old student to the WEI")}
def get_sample_object(self):
wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
if "myself" in self.request.path:
user = self.request.user
else:
# To avoid unique validation issues, we use an account that can't join the WEI.
# In development mode, the note account may not exist, we use a random user (may fail)
user = User.objects.get(username="note") \
if User.objects.filter(username="note").exists() else User.objects.first()
return WEIRegistration(
wei=wei,
user=user,
first_year=True,
birth_date="1970-01-01",
gender="No",
emergency_contact_name="No",
emergency_contact_phone="No",
)
def dispatch(self, request, *args, **kwargs):
wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
today = date.today()
# We can't register someone once the WEI is started and before the membership start date
if today >= wei.date_start or today < wei.membership_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
# Don't register twice
if 'myself' in self.request.path and not self.request.user.is_anonymous \
and WEIRegistration.objects.filter(wei=wei, user=self.request.user).exists():
obj = WEIRegistration.objects.get(wei=wei, user=self.request.user)
return redirect(reverse_lazy('wei:wei_update_registration', args=(obj.pk,)))
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = _("Register 2A+")
context['club'] = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
if "myself" in self.request.path:
context["form"].fields["user"].disabled = True
choose_bus_form = WEIChooseBusForm(self.request.POST if self.request.POST else None)
choose_bus_form.fields["bus"].queryset = Bus.objects.filter(wei=context["club"]).order_by('name')
choose_bus_form.fields["team"].queryset = BusTeam.objects.filter(bus__wei=context["club"])\
.order_by('bus__name', 'name')
context['membership_form'] = choose_bus_form
return context
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields["user"].initial = self.request.user
if "myself" in self.request.path and self.request.user.profile.soge:
form.fields["soge_credit"].disabled = True
form.fields["soge_credit"].help_text = _("You already opened an account in the Société générale.")
del form.fields["caution_check"]
del form.fields["first_year"]
del form.fields["information_json"]
return form
@transaction.atomic
def form_valid(self, form):
form.instance.wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
form.instance.first_year = False
if not form.instance.pk:
# Check if the user is not already registered to the WEI
if WEIRegistration.objects.filter(wei=form.instance.wei, user=form.instance.user).exists():
form.add_error('user', _("This user is already registered to this WEI."))
return self.form_invalid(form)
choose_bus_form = WEIChooseBusForm(self.request.POST)
if not choose_bus_form.is_valid():
return self.form_invalid(form)
information = form.instance.information
information["preferred_bus_pk"] = [bus.pk for bus in choose_bus_form.cleaned_data["bus"]]
information["preferred_bus_name"] = [bus.name for bus in choose_bus_form.cleaned_data["bus"]]
information["preferred_team_pk"] = [team.pk for team in choose_bus_form.cleaned_data["team"]]
information["preferred_team_name"] = [team.name for team in choose_bus_form.cleaned_data["team"]]
information["preferred_roles_pk"] = [role.pk for role in choose_bus_form.cleaned_data["roles"]]
information["preferred_roles_name"] = [role.name for role in choose_bus_form.cleaned_data["roles"]]
form.instance.information = information
form.instance.save()
if 'treasury' in settings.INSTALLED_APPS:
from treasury.models import SogeCredit
form.instance.soge_credit = \
form.instance.soge_credit \
or SogeCredit.objects.filter(user=form.instance.user, credit_transaction__valid=False).exists()
return super().form_valid(form)
def get_success_url(self):
self.object.refresh_from_db()
return reverse_lazy("wei:wei_survey", kwargs={"pk": self.object.pk})
class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
"""
Update a registration for the WEI
"""
model = WEIRegistration
form_class = WEIRegistrationForm
extra_context = {"title": _("Update WEI Registration")}
def dispatch(self, request, *args, **kwargs):
wei = self.get_object().wei
today = date.today()
# We can't update a registration once the WEI is started and before the membership start date
if today >= wei.date_start or today < wei.membership_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["club"] = self.object.wei
if self.object.is_validated:
membership_form = self.get_membership_form(instance=self.object.membership,
data=self.request.POST)
context["membership_form"] = membership_form
elif not self.object.first_year and PermissionBackend.check_perm(
self.request, "wei.change_weiregistration_information_json", self.object):
information = self.object.information
d = dict(
bus=Bus.objects.filter(pk__in=information["preferred_bus_pk"]).all(),
team=BusTeam.objects.filter(pk__in=information["preferred_team_pk"]).all(),
roles=WEIRole.objects.filter(pk__in=information["preferred_roles_pk"]).all(),
) if 'preferred_bus_pk' in information else dict()
choose_bus_form = WEIChooseBusForm(
self.request.POST if self.request.POST else d
)
choose_bus_form.fields["bus"].queryset = Bus.objects.filter(wei=context["club"])
choose_bus_form.fields["team"].queryset = BusTeam.objects.filter(bus__wei=context["club"])
context["membership_form"] = choose_bus_form
if not self.object.soge_credit and self.object.user.profile.soge:
form = context["form"]
form.fields["soge_credit"].disabled = True
form.fields["soge_credit"].help_text = _("You already opened an account in the Société générale.")
return context
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields["user"].disabled = True
# The auto-json-format may cause issues with the default field remove
if not PermissionBackend.check_perm(self.request, 'wei.change_weiregistration_information_json', self.object):
del form.fields["information_json"]
return form
def get_membership_form(self, data=None, instance=None):
membership_form = WEIMembershipForm(data if data else None, instance=instance)
del membership_form.fields["credit_type"]
del membership_form.fields["credit_amount"]
del membership_form.fields["first_name"]
del membership_form.fields["last_name"]
del membership_form.fields["bank"]
for field_name, _field in list(membership_form.fields.items()):
if not PermissionBackend.check_perm(
self.request, "wei.change_weimembership_" + field_name, self.object.membership):
del membership_form.fields[field_name]
return membership_form
@transaction.atomic
def form_valid(self, form):
# If the membership is already validated, then we update the bus and the team (and the roles)
if form.instance.is_validated:
membership_form = self.get_membership_form(self.request.POST, form.instance.membership)
if not membership_form.is_valid():
return self.form_invalid(form)
membership_form.save()
# If it is not validated and if this is an old member, then we update the choices
elif not form.instance.first_year and PermissionBackend.check_perm(
self.request, "wei.change_weiregistration_information_json", self.object):
choose_bus_form = WEIChooseBusForm(self.request.POST)
if not choose_bus_form.is_valid():
return self.form_invalid(form)
information = form.instance.information
information["preferred_bus_pk"] = [bus.pk for bus in choose_bus_form.cleaned_data["bus"]]
information["preferred_bus_name"] = [bus.name for bus in choose_bus_form.cleaned_data["bus"]]
information["preferred_team_pk"] = [team.pk for team in choose_bus_form.cleaned_data["team"]]
information["preferred_team_name"] = [team.name for team in choose_bus_form.cleaned_data["team"]]
information["preferred_roles_pk"] = [role.pk for role in choose_bus_form.cleaned_data["roles"]]
information["preferred_roles_name"] = [role.name for role in choose_bus_form.cleaned_data["roles"]]
form.instance.information = information
form.instance.save()
return super().form_valid(form)
def get_success_url(self):
self.object.refresh_from_db()
if self.object.first_year:
survey = CurrentSurvey(self.object)
if not survey.is_complete():
return reverse_lazy("wei:wei_survey", kwargs={"pk": self.object.pk})
if PermissionBackend.check_perm(self.request, "wei.add_weimembership", WEIMembership(
club=self.object.wei,
user=self.object.user,
date_start=date.today(),
date_end=date.today(),
fee=0,
registration=self.object,
)):
return reverse_lazy("wei:validate_registration", kwargs={"pk": self.object.pk})
return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.wei.pk})
class WEIDeleteRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView):
"""
Delete a non-validated WEI registration
"""
model = WEIRegistration
extra_context = {"title": _("Delete WEI registration")}
def dispatch(self, request, *args, **kwargs):
object = self.get_object()
wei = object.wei
today = date.today()
# We can't delete a registration of a past WEI
if today > wei.membership_end:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
if not PermissionBackend.check_perm(self.request, "wei.delete_weiregistration", object):
raise PermissionDenied(_("You don't have the right to delete this WEI registration."))
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["club"] = self.object.wei
return context
def get_success_url(self):
return reverse_lazy('wei:wei_detail', args=(self.object.wei.pk,))
class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
"""
Validate WEI Registration
"""
model = WEIMembership
extra_context = {"title": _("Validate WEI registration")}
def get_sample_object(self):
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"])
return WEIMembership(
club=registration.wei,
user=registration.user,
date_start=date.today(),
date_end=date.today() + timedelta(days=1),
fee=0,
registration=registration,
)
def dispatch(self, request, *args, **kwargs):
wei = WEIRegistration.objects.get(pk=self.kwargs["pk"]).wei
today = date.today()
# We can't validate anyone once the WEI is started and before the membership start date
if today >= wei.date_start or today < wei.membership_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"])
context["registration"] = registration
survey = CurrentSurvey(registration)
if survey.information.valid:
context["suggested_bus"] = survey.information.get_selected_bus()
context["club"] = registration.wei
kfet = registration.wei.parent_club
bde = kfet.parent_club
context["kfet_member"] = Membership.objects.filter(
club__name=kfet.name,
user=registration.user,
date_start__gte=kfet.membership_start,
).exists()
context["bde_member"] = Membership.objects.filter(
club__name=bde.name,
user=registration.user,
date_start__gte=bde.membership_start,
).exists()
context["fee"] = registration.fee
form = context["form"]
if registration.soge_credit:
form.fields["credit_amount"].initial = registration.fee
else:
form.fields["credit_amount"].initial = max(0, registration.fee - registration.user.note.balance)
return context
def get_form_class(self):
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"])
if registration.first_year and 'sleected_bus_pk' not in registration.information:
return WEIMembership1AForm
return WEIMembershipForm
def get_form(self, form_class=None):
form = super().get_form(form_class)
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"])
form.fields["last_name"].initial = registration.user.last_name
form.fields["first_name"].initial = registration.user.first_name
if "caution_check" in form.fields:
form.fields["caution_check"].initial = registration.caution_check
if registration.soge_credit:
form.fields["credit_type"].disabled = True
form.fields["credit_type"].initial = NoteSpecial.objects.get(special_type="Virement bancaire")
form.fields["credit_amount"].disabled = True
form.fields["last_name"].disabled = True
form.fields["first_name"].disabled = True
form.fields["bank"].disabled = True
form.fields["bank"].initial = "Société générale"
if 'bus' in form.fields:
# For 2A+ and hardcoded 1A
form.fields["bus"].widget.attrs["api_url"] = "/api/wei/bus/?wei=" + str(registration.wei.pk)
if registration.first_year:
# Use the results of the survey to fill initial data
# A first year has no other role than "1A"
del form.fields["roles"]
survey = CurrentSurvey(registration)
if survey.information.valid:
form.fields["bus"].initial = survey.information.get_selected_bus()
else:
# Use the choice of the member to fill initial data
information = registration.information
if "preferred_bus_pk" in information and len(information["preferred_bus_pk"]) == 1:
form["bus"].initial = Bus.objects.get(pk=information["preferred_bus_pk"][0])
if "preferred_team_pk" in information and len(information["preferred_team_pk"]) == 1:
form["team"].initial = BusTeam.objects.get(pk=information["preferred_team_pk"][0])
if "preferred_roles_pk" in information:
form["roles"].initial = WEIRole.objects.filter(
Q(pk__in=information["preferred_roles_pk"]) | Q(name="Adhérent⋅e WEI")
).all()
return form
@transaction.atomic
def form_valid(self, form):
"""
Create membership, check that all is good, make transactions
"""
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"])
club = registration.wei
user = registration.user
if "caution_check" in form.data:
registration.caution_check = form.data["caution_check"] == "on"
registration.save()
membership = form.instance
membership.user = user
membership.club = club
membership.date_start = min(date.today(), club.date_start)
membership.registration = registration
# Force the membership of the clubs BDE and Kfet
membership._force_renew_parent = True
fee = club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid
kfet = club.parent_club
bde = kfet.parent_club
kfet_member = Membership.objects.filter(
club__name=kfet.name,
user=registration.user,
date_start__gte=kfet.membership_start,
).exists()
bde_member = Membership.objects.filter(
club__name=bde.name,
user=registration.user,
date_start__gte=bde.membership_start,
).exists()
if not kfet_member:
fee += kfet.membership_fee_paid if registration.user.profile.paid else kfet.membership_fee_unpaid
if not bde_member:
fee += bde.membership_fee_paid if registration.user.profile.paid else bde.membership_fee_unpaid
credit_type = form.cleaned_data["credit_type"]
credit_amount = form.cleaned_data["credit_amount"]
last_name = form.cleaned_data["last_name"]
first_name = form.cleaned_data["first_name"]
bank = form.cleaned_data["bank"]
if credit_type is None or registration.soge_credit:
credit_amount = 0
if not registration.soge_credit and user.note.balance + credit_amount < fee:
# Users must have money before registering to the WEI.
form.add_error('credit_type',
_("This user don't have enough money to join this club, and can't have a negative balance."))
return super().form_invalid(form)
if credit_amount:
if not last_name:
form.add_error('last_name', _("This field is required."))
return super().form_invalid(form)
if not first_name:
form.add_error('first_name', _("This field is required."))
return super().form_invalid(form)
# Credit note before adding the membership
SpecialTransaction.objects.create(
source=credit_type,
destination=registration.user.note,
amount=credit_amount,
reason="Crédit " + str(credit_type) + " (WEI)",
last_name=last_name,
first_name=first_name,
bank=bank,
)
# Now, all is fine, the membership can be created.
if registration.soge_credit:
form.instance._soge = True
if registration.first_year:
membership = form.instance
# If the user is not a member of the club Kfet, then the membership is created.
membership.save()
membership.refresh_from_db()
membership.roles.set(WEIRole.objects.filter(name="1A").all())
membership.save()
membership.save()
membership.refresh_from_db()
membership.roles.add(WEIRole.objects.get(name="Adhérent⋅e WEI"))
return super().form_valid(form)
def get_success_url(self):
self.object.refresh_from_db()
return reverse_lazy("wei:wei_registrations", kwargs={"pk": self.object.club.pk})
class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView):
"""
Display the survey for the WEI for first year members.
Warning: this page is accessible for anyone that is connected, the view doesn't extend ProtectQuerySetMixin.
"""
model = WEIRegistration
template_name = "wei/survey.html"
survey = None
extra_context = {"title": _("Survey WEI")}
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
self.object = obj
wei = obj.wei
today = date.today()
# We can't access to the WEI survey once the WEI is started and before the membership start date
if today >= wei.date_start or today < wei.membership_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
if not self.survey:
self.survey = CurrentSurvey(obj)
# If the survey is complete, then display the end page.
if self.survey.is_complete():
return redirect(reverse_lazy('wei:wei_survey_end', args=(self.survey.registration.pk,)))
# Non first year members don't have a survey
if not obj.first_year:
return redirect(reverse_lazy('wei:wei_survey_end', args=(self.survey.registration.pk,)))
return super().dispatch(request, *args, **kwargs)
def get_form_class(self):
"""
Get the survey form. It may depend on the current state of the survey.
"""
return self.survey.get_form_class()
def get_form(self, form_class=None):
"""
Update the form with the data of the survey.
"""
form = super().get_form(form_class)
self.survey.update_form(form)
return form
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["club"] = self.object.wei
return context
@transaction.atomic
def form_valid(self, form):
"""
Update the survey with the data of the form.
"""
self.survey.form_valid(form)
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('wei:wei_survey', args=(self.get_object().pk,))
class WEISurveyEndView(LoginRequiredMixin, TemplateView):
template_name = "wei/survey_end.html"
extra_context = {"title": _("Survey WEI")}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
club = WEIRegistration.objects.get(pk=self.kwargs["pk"]).wei
context["club"] = club
random_user = User.objects.filter(~Q(wei__wei__in=[club])).first()
if random_user is None:
# This case occurs when all users are registered to the WEI.
# Don't worry, Pikachu never went to the WEI.
# This bug can arrive only in dev mode.
context["can_add_first_year_member"] = True
context["can_add_any_member"] = True
else:
# Check if the user has the right to create a registration of a random first year member.
empty_fy_registration = WEIRegistration(
wei=club,
user=random_user,
first_year=True,
birth_date="1970-01-01",
gender="No",
emergency_contact_name="No",
emergency_contact_phone="No",
)
context["can_add_first_year_member"] = PermissionBackend \
.check_perm(self.request, "wei.add_weiregistration", empty_fy_registration)
# Check if the user has the right to create a registration of a random old member.
empty_old_registration = WEIRegistration(
wei=club,
user=User.objects.filter(~Q(wei__wei__in=[club])).first(),
first_year=False,
birth_date="1970-01-01",
gender="No",
emergency_contact_name="No",
emergency_contact_phone="No",
)
context["can_add_any_member"] = PermissionBackend \
.check_perm(self.request, "wei.add_weiregistration", empty_old_registration)
return context
class WEIClosedView(LoginRequiredMixin, TemplateView):
template_name = "wei/survey_closed.html"
extra_context = {"title": _("Survey WEI")}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["club"] = WEIClub.objects.get(pk=self.kwargs["pk"])
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, WEIMembership, "view"))
qs = qs.filter(club__pk=self.kwargs["wei_pk"]).order_by(
Lower('bus__name'),
Lower('team__name'),
'user__profile__promotion',
Lower('user__last_name'),
Lower('user__first_name'),
'id',
)
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.distinct()
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
with open(os.devnull, "wb") as devnull:
error = subprocess.Popen(
["/usr/bin/xelatex", "-interaction=nonstopmode", "{}/wei-list.tex".format(tmp_dir)],
cwd=tmp_dir,
stderr=devnull,
stdout=devnull,
).wait()
if error:
with open("{}/wei-list.log".format(tmp_dir), "r") as f:
log = f.read()
raise IOError("An error attempted while generating a WEI list (code=" + str(error) + ")\n\n" + log)
# Display the generated pdf as a HTTP Response
with open("{}/wei-list.pdf".format(tmp_dir), 'rb') as f:
pdf = f.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
class WEI1AListView(LoginRequiredMixin, ProtectQuerysetMixin, SingleTableView):
model = WEIRegistration
template_name = "wei/1A_list.html"
table_class = WEIRegistration1ATable
extra_context = {"title": _("Attribute buses to first year members")}
def dispatch(self, request, *args, **kwargs):
self.club = WEIClub.objects.get(pk=self.kwargs["pk"])
return super().dispatch(request, *args, **kwargs)
def get_queryset(self, filter_permissions=True, **kwargs):
qs = super().get_queryset(filter_permissions, **kwargs)
qs = qs.filter(first_year=True, membership__isnull=False)
qs = qs.order_by('-membership__bus')
return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['club'] = self.club
context['bus_repartition_table'] = BusRepartitionTable(
Bus.objects.filter(wei=self.club, size__gt=0)
.filter(PermissionBackend.filter_queryset(self.request, Bus, "view"))
.all())
return context
class WEIAttributeBus1AView(ProtectQuerysetMixin, DetailView):
model = WEIRegistration
template_name = "wei/attribute_bus_1A.html"
extra_context = {"title": _("Attribute bus")}
def get_queryset(self, filter_permissions=True, **kwargs):
qs = super().get_queryset(filter_permissions, **kwargs)
qs = qs.filter(first_year=True)
return qs
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
if 'selected_bus_pk' not in obj.information:
return redirect(reverse_lazy('wei:wei_survey', args=(obj.pk,)))
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['club'] = self.object.wei
context['survey'] = CurrentSurvey(self.object)
return context
class WEIAttributeBus1ANextView(LoginRequiredMixin, RedirectView):
def get_redirect_url(self, *args, **kwargs):
wei = WEIClub.objects.filter(pk=self.kwargs['pk'])
if not wei.exists():
raise Http404
wei = wei.get()
qs = WEIRegistration.objects.filter(wei=wei, membership__isnull=False, membership__bus__isnull=True)
qs = qs.filter(information_json__contains='selected_bus_pk') # not perfect, but works...
if qs.exists():
return reverse_lazy('wei:wei_bus_1A', args=(qs.first().pk, ))
return reverse_lazy('wei:wei_1A_list', args=(wei.pk, ))