1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-02-25 01:21:20 +00:00

Modification vers couleur Surviva[list]

This commit is contained in:
root 2022-04-08 17:30:39 +02:00
parent 4d03d9460d
commit 3018c1e0fe
3 changed files with 283 additions and 0 deletions

42
apps/api/filters.py Normal file
View File

@ -0,0 +1,42 @@
import re
from functools import lru_cache
from rest_framework.filters import SearchFilter
class RegexSafeSearchFilter(SearchFilter):
@lru_cache
def validate_regex(self, search_term) -> bool:
try:
re.compile(search_term)
return True
except re.error:
return False
def get_search_fields(self, view, request):
"""
Ensure that given regex are valid.
If not, we consider that the user is trying to search by substring.
"""
search_fields = super().get_search_fields(view, request)
search_terms = self.get_search_terms(request)
for search_term in search_terms:
if not self.validate_regex(search_term):
# Invalid regex. We assume we don't query by regex but by substring.
search_fields = [f.replace('$', '') for f in search_fields]
break
return search_fields
def get_search_terms(self, request):
"""
Ensure that search field is a valid regex query. If not, we remove extra characters.
"""
terms = super().get_search_terms(request)
if not all(self.validate_regex(term) for term in terms):
# Invalid regex. If a ^ is prefixed to the search term, we remove it.
terms = [term[1:] if term[0] == '^' else term for term in terms]
# Same for dollars.
terms = [term[:-1] if term[-1] == '$' else term for term in terms]
return terms

View File

@ -0,0 +1,108 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from argparse import ArgumentParser, FileType
from django.core.management import BaseCommand
from django.db import transaction
from django.db.models import Q
from ...forms import CurrentSurvey
from ...models import Bus, WEIMembership
class Command(BaseCommand):
help = "Attribute to each first year member a team for the WEI"
def add_arguments(self, parser: ArgumentParser):
parser.add_argument('--doit', '-d', action='store_true', help='Finally run the algorithm in non-dry mode.')
parser.add_argument('--output', '-o', nargs='?', type=FileType('w'), default=self.stdout,
help='Output file for the algorithm result. Default is standard output.')
def attribute_teams(self, bus):
if not bus.size:
return
teams = bus.teams.all()
old_members = WEIMembership.objects.filter(registration__first_year=False, bus=bus, team__in=teams)
new_members = WEIMembership.objects.filter(registration__first_year=True, bus=bus)
n_chef_tot = old_members.count()
n_equipe = teams.count()
size_goal = {}
for team in teams:
size_goal[team] = int(old_members.filter(team=team).count() * new_members.count() / n_chef_tot)
i = 0
non_men = list(new_members.filter(~Q(registration__gender='male')))
men = list(new_members.filter(registration__gender='male'))
print(bus.name, size_goal.values())
return
while non_men:
if new_members.filter(team=teams[i]).count() < size_goal[teams[i]]:
non_man = non_men.pop()
non_man.team = teams[i]
non_man.save()
i += 1
i %= n_equipe
for i, team in enumerate(teams):
print(i, new_members.filter(team=team).count())
if new_members.filter(team=team).count() == 1:
# Si une fille est seule on l'enlève.
g_alone = new_members.get(team=team)
g_alone.team = teams[i + 1]
g_alone.save()
remain = True
i = 0
while men and remain:
if new_members.filter(team=teams[i]).count() < size_goal[teams[i]]:
m = men.pop()
m.team = teams[i]
m.save()
i += 1
i %= n_equipe
@transaction.atomic
def handle(self, *args, **options):
"""
Run the WEI algorithm to attribute a bus to each first year member.
"""
sid = transaction.savepoint()
algorithm = CurrentSurvey.get_algorithm_class()()
try:
from tqdm import tqdm
display_tqdm = True
except ImportError:
display_tqdm = False
for bus in Bus.objects.all():
self.attribute_teams(bus)
raise Exception()
output = options['output']
registrations = algorithm.get_registrations()
per_bus = {bus: [r for r in registrations if 'selected_bus_pk' in r.information
and r.information['selected_bus_pk'] == bus.pk]
for bus in algorithm.get_buses()}
for bus, members in per_bus.items():
output.write(bus.name + "\n")
output.write("=" * len(bus.name) + "\n")
order = -1
for r in members:
survey = CurrentSurvey(r)
for order, (b, _score) in enumerate(survey.ordered_buses()):
if b == bus:
break
output.write(f"{r.user.username} ({order + 1})\n")
output.write("\n")
if not options['doit']:
self.stderr.write(self.style.WARNING("Running in dry mode. "
"Use --doit option to really execute the algorithm."))
transaction.savepoint_rollback(sid)
return

View File

@ -0,0 +1,133 @@
.validate:hover {
cursor: pointer;
text-decoration: underline;
}
/* Opaque tooltip with white background */
.tooltip.show {
opacity: 1;
}
.tooltip-inner {
background-color: #fff;
box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15);
border: 1px solid rgba(0, 0, 0, .250);
color: #000;
margin: 0 .5rem .25rem .5rem;
padding: 0;
width: 200px;
}
.bs-tooltip-bottom .arrow::before {
border-bottom-color: rgba(0, 0, 0, .250);
}
/* Fixed width picture column */
.picture-col {
max-width: 202px;
}
/* Limit fluid container to a max size */
.container-fluid {
max-width: 1600px;
}
/* Apply Bootstrap table-responsive to all Django tables */
.table-container {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* Smaller language selector */
select.language {
padding: 0;
background: transparent;
border: none;
height: calc(1.5em + .5rem);
color: #6c757d;
-moz-appearance: none;
width: auto;
cursor: pointer;
}
/* Remove horizontal padding on mark */
.mark,
mark {
padding: .2em 0;
}
/* Make navbar more readable */
.navbar-dark .navbar-nav .nav-link {
color: rgba(255, 255, 255, .75);
}
/* Last BDE colors */
.bg-primary {
background-color: rgb(0, 0, 0) !important;
}
html {
scrollbar-color: rgba(0, 0, 0, 1) rgba(35, 35, 39, 1);
}
body {
background-color: rgba(64, 64, 64, 1);
background-image: url(../img/background-texture.png);
}
.btn-outline-primary:hover,
.btn-outline-primary:not(:disabled):not(.disabled).active,
.btn-outline-primary:not(:disabled):not(.disabled):active {
color: #fff;
background-color: rgb(0, 0, 0);
border-color: rgb(0, 0, 0);
}
.btn-outline-primary {
color: rgb(0, 0, 0);
background-color: rgba(248, 249, 250, 0.9);
border-color: rgb(0, 0, 0);
}
.turbolinks-progress-bar {
background-color: #12432E;
}
.btn-primary:hover,
.btn-primary:not(:disabled):not(.disabled).active,
.btn-primary:not(:disabled):not(.disabled):active {
color: #fff;
background-color: rgb(0, 0, 0);
border-color: rgb(0, 0, 0);
}
.btn-primary {
color: rgba(248, 249, 250, 0.9);
background-color: rgb(28, 114, 10);
border-color: rgb0à, 67, 46);
}
.border-primary {
border-color: rgb(28, 114, 10) !important;
}
a {
color: rgb(28, 114, 10);
}
a:hover {
color: rgb(122, 163, 75);
}
.form-control:focus {
box-shadow: 0 0 0 0.25rem rgba(122, 163, 75, 0.25);
border-color: rgb(122, 163, 75);
}
.btn-outline-primary.focus {
box-shadow: 0 0 0 0.25rem rgba(122, 163, 75, 0.5);
}