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:
parent
4d03d9460d
commit
3018c1e0fe
42
apps/api/filters.py
Normal file
42
apps/api/filters.py
Normal 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
|
108
apps/wei/management/commands/make_teams.py
Normal file
108
apps/wei/management/commands/make_teams.py
Normal 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
|
133
note_kfet/static/css/custom.css.save
Normal file
133
note_kfet/static/css/custom.css.save
Normal 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);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user