Merge branch 'Kerl/use_django_custommail' of git.eleves.ens.fr:cof-geek/gestioCOF into Kerl/use_django_custommail
This commit is contained in:
commit
5d35112c87
26
README.md
26
README.md
|
@ -84,29 +84,30 @@ visualiser la dernière version du code.
|
|||
Si vous optez pour une installation manuelle plutôt que d'utiliser Vagrant, il
|
||||
est fortement conseillé d'utiliser un environnement virtuel pour Python.
|
||||
|
||||
Il vous faudra installer mercurial, pip, les librairies de développement de
|
||||
python, un client et un serveur MySQL ainsi qu'un serveur redis ; sous Debian et
|
||||
dérivées (Ubuntu, ...) :
|
||||
Il vous faudra installer pip, les librairies de développement de python, un
|
||||
client et un serveur MySQL ainsi qu'un serveur redis ; sous Debian et dérivées
|
||||
(Ubuntu, ...) :
|
||||
|
||||
sudo apt-get install mercurial python-pip python-dev libmysqlclient-dev
|
||||
redis-server
|
||||
sudo apt-get install python-pip python-dev libmysqlclient-dev redis-server
|
||||
|
||||
Si vous décidez d'utiliser un environnement virtuel Python (virtualenv;
|
||||
fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF
|
||||
(le dossier où se trouve ce README), et créez-le maintenant :
|
||||
|
||||
virtualenv env
|
||||
virtualenv env -p $(which python3)
|
||||
|
||||
Pour l'activer, il faut faire
|
||||
L'option `-p` sert à préciser l'exécutable python à utiliser. Vous devez choisir
|
||||
python3, si c'est la version de python par défaut sur votre système, ceci n'est
|
||||
pas nécessaire. Pour l'activer, il faut faire
|
||||
|
||||
. env/bin/activate
|
||||
|
||||
dans le même dossier.
|
||||
|
||||
Vous pouvez maintenant installer les dépendances Python depuis les fichiers
|
||||
`requirements.txt` et `requirements-devel.txt` :
|
||||
Vous pouvez maintenant installer les dépendances Python depuis le fichier
|
||||
`requirements-devel.txt` :
|
||||
|
||||
pip install -r requirements.txt -r requirements-devel.txt
|
||||
pip install -r requirements-devel.txt
|
||||
|
||||
Copiez le fichier `cof/settings_dev.py` dans `cof/settings.py`.
|
||||
|
||||
|
@ -174,9 +175,10 @@ Charger les mails indispensables au bon fonctionnement de GestioCOF :
|
|||
|
||||
python manage.py syncmails
|
||||
|
||||
Une base de donnée pré-remplie est disponible en lançant la commande :
|
||||
Une base de donnée pré-remplie est disponible en lançant les commandes :
|
||||
|
||||
python manage.py loaddata users root bda gestion sites accounts groups articles
|
||||
python manage.py loaddata gestion sites accounts groups articles
|
||||
python manage.py loaddevdata
|
||||
|
||||
Vous êtes prêts à développer ! Lancer GestioCOF en faisant
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
107
bda/management/commands/loadbdadevdata.py
Normal file
107
bda/management/commands/loadbdadevdata.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
"""
|
||||
Crée deux tirages de test et y inscrit les utilisateurs
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from gestioncof.management.base import MyBaseCommand
|
||||
from bda.models import Tirage, Spectacle, Salle, Participant, ChoixSpectacle
|
||||
from bda.views import do_tirage
|
||||
|
||||
|
||||
# Où sont stockés les fichiers json
|
||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
||||
'data')
|
||||
|
||||
|
||||
class Command(MyBaseCommand):
|
||||
help = "Crée deux tirages de test et y inscrit les utilisateurs."
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# ---
|
||||
# Tirages
|
||||
# ---
|
||||
|
||||
Tirage.objects.all().delete()
|
||||
Tirage.objects.bulk_create([
|
||||
Tirage(
|
||||
title="Tirage de test 1",
|
||||
ouverture=timezone.now()-timezone.timedelta(days=7),
|
||||
fermeture=timezone.now(),
|
||||
active=True
|
||||
),
|
||||
Tirage(
|
||||
title="Tirage de test 2",
|
||||
ouverture=timezone.now(),
|
||||
fermeture=timezone.now()+timezone.timedelta(days=60),
|
||||
active=True
|
||||
)
|
||||
])
|
||||
tirages = Tirage.objects.all()
|
||||
|
||||
# ---
|
||||
# Salles
|
||||
# ---
|
||||
|
||||
locations = self.from_json('locations.json', DATA_DIR, Salle)
|
||||
|
||||
# ---
|
||||
# Spectacles
|
||||
# ---
|
||||
|
||||
def show_callback(show):
|
||||
"""
|
||||
Assigne un tirage, une date et un lieu à un spectacle et décide si
|
||||
les places sont sur listing.
|
||||
"""
|
||||
show.tirage = random.choice(tirages)
|
||||
show.listing = bool(random.randint(0, 1))
|
||||
show.date = (
|
||||
show.tirage.fermeture
|
||||
+ timezone.timedelta(days=random.randint(60, 90))
|
||||
)
|
||||
show.location = random.choice(locations)
|
||||
return show
|
||||
shows = self.from_json(
|
||||
'shows.json', DATA_DIR, Spectacle, show_callback
|
||||
)
|
||||
|
||||
# ---
|
||||
# Inscriptions
|
||||
# ---
|
||||
|
||||
self.stdout.write("Inscription des utilisateurs aux tirages")
|
||||
ChoixSpectacle.objects.all().delete()
|
||||
choices = []
|
||||
for user in User.objects.filter(profile__is_cof=True):
|
||||
for tirage in tirages:
|
||||
part, _ = Participant.objects.get_or_create(
|
||||
user=user,
|
||||
tirage=tirage
|
||||
)
|
||||
shows = random.sample(
|
||||
list(tirage.spectacle_set.all()),
|
||||
tirage.spectacle_set.count() // 2
|
||||
)
|
||||
for (rank, show) in enumerate(shows):
|
||||
choices.append(ChoixSpectacle(
|
||||
participant=part,
|
||||
spectacle=show,
|
||||
priority=rank + 1,
|
||||
double_choice=random.choice(
|
||||
['1', 'double', 'autoquit']
|
||||
)
|
||||
))
|
||||
ChoixSpectacle.objects.bulk_create(choices)
|
||||
self.stdout.write("- {:d} inscriptions générées".format(len(choices)))
|
||||
|
||||
# ---
|
||||
# On lance le premier tirage
|
||||
# ---
|
||||
|
||||
self.stdout.write("Lancement du premier tirage")
|
||||
do_tirage(tirages[0], "dummy_token")
|
26
bda/management/data/locations.json
Normal file
26
bda/management/data/locations.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
[
|
||||
{
|
||||
"name": "Cour\u00f4",
|
||||
"address": "45 rue d'Ulm, cour\u00f4"
|
||||
},
|
||||
{
|
||||
"name": "K-F\u00eat",
|
||||
"address": "45 rue d'Ulm, escalier C, niveau -1"
|
||||
},
|
||||
{
|
||||
"name": "Th\u00e9\u00e2tre",
|
||||
"address": "45 rue d'Ulm, escalier C, niveau -1"
|
||||
},
|
||||
{
|
||||
"name": "Cours Pasteur",
|
||||
"address": "45 rue d'Ulm, cours pasteur"
|
||||
},
|
||||
{
|
||||
"name": "Salle des actes",
|
||||
"address": "45 rue d'Ulm, escalier A, niveau 1"
|
||||
},
|
||||
{
|
||||
"name": "Amphi Rataud",
|
||||
"address": "45 rue d'Ulm, NIR, niveau PB"
|
||||
}
|
||||
]
|
100
bda/management/data/shows.json
Normal file
100
bda/management/data/shows.json
Normal file
|
@ -0,0 +1,100 @@
|
|||
[
|
||||
{
|
||||
"description": "Jazz / Funk",
|
||||
"title": "Un super concert",
|
||||
"price": 10.0,
|
||||
"slots_description": "Debout",
|
||||
"slots": 5
|
||||
},
|
||||
{
|
||||
"description": "Homemade",
|
||||
"title": "Une super pi\u00e8ce",
|
||||
"price": 10.0,
|
||||
"slots_description": "Assises",
|
||||
"slots": 60
|
||||
},
|
||||
{
|
||||
"description": "Plein air, soleil, bonne musique",
|
||||
"title": "Concert pour la f\u00eate de la musique",
|
||||
"price": 5.0,
|
||||
"slots_description": "Debout, attention \u00e0 la fontaine",
|
||||
"slots": 30
|
||||
},
|
||||
{
|
||||
"description": "Sous le regard s\u00e9v\u00e8re de Louis Pasteur",
|
||||
"title": "Op\u00e9ra sans d\u00e9cors",
|
||||
"price": 5.0,
|
||||
"slots_description": "Assis sur l'herbe",
|
||||
"slots": 20
|
||||
},
|
||||
{
|
||||
"description": "Buffet \u00e0 la fin",
|
||||
"title": "Concert Trouv\u00e8re",
|
||||
"price": 20.0,
|
||||
"slots_description": "Assises",
|
||||
"slots": 15
|
||||
},
|
||||
{
|
||||
"description": "Vive les maths",
|
||||
"title": "Dessin \u00e0 la craie sur tableau noir",
|
||||
"price": 10.0,
|
||||
"slots_description": "Assises, tablette pour prendre des notes",
|
||||
"slots": 30
|
||||
},
|
||||
{
|
||||
"description": "Une pi\u00e8ce \u00e0 un personnage",
|
||||
"title": "D\u00e9cors, d\u00e9montage en musique",
|
||||
"price": 0.0,
|
||||
"slots_description": "Assises",
|
||||
"slots": 20
|
||||
},
|
||||
{
|
||||
"description": "Annulera, annulera pas\u00a0?",
|
||||
"title": "La Nuit",
|
||||
"price": 27.0,
|
||||
"slots_description": "",
|
||||
"slots": 1000
|
||||
},
|
||||
{
|
||||
"description": "Le boum fait sa carte blanche",
|
||||
"title": "Turbomix",
|
||||
"price": 10.0,
|
||||
"slots_description": "Debout les mains en l'air",
|
||||
"slots": 20
|
||||
},
|
||||
{
|
||||
"description": "Unique repr\u00e9sentation",
|
||||
"title": "Carinettes et trombone",
|
||||
"price": 15.0,
|
||||
"slots_description": "Chaises ikea",
|
||||
"slots": 10
|
||||
},
|
||||
{
|
||||
"description": "Suivi d'une jam session",
|
||||
"title": "Percussion sur rondins",
|
||||
"price": 5.0,
|
||||
"slots_description": "B\u00fbches",
|
||||
"slots": 14
|
||||
},
|
||||
{
|
||||
"description": "\u00c9preuve sportive et artistique",
|
||||
"title": "Bassin aux ernests, nage libre",
|
||||
"price": 5.0,
|
||||
"slots_description": "Humides",
|
||||
"slots": 10
|
||||
},
|
||||
{
|
||||
"description": "Sonore",
|
||||
"title": "Chant du barde",
|
||||
"price": 13.0,
|
||||
"slots_description": "Ne venez pas",
|
||||
"slots": 20
|
||||
},
|
||||
{
|
||||
"description": "Cocorico",
|
||||
"title": "Chant du coq",
|
||||
"price": 4.0,
|
||||
"slots_description": "bancs",
|
||||
"slots": 15
|
||||
}
|
||||
]
|
|
@ -10,10 +10,8 @@ from django.db import models
|
|||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
from django.utils import timezone, formats
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Tirage(models.Model):
|
||||
title = models.CharField("Titre", max_length=300)
|
||||
ouverture = models.DateTimeField("Date et heure d'ouverture du tirage")
|
||||
|
@ -28,7 +26,6 @@ class Tirage(models.Model):
|
|||
timezone.template_localtime(self.fermeture)))
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Salle(models.Model):
|
||||
name = models.CharField("Nom", max_length=300)
|
||||
address = models.TextField("Adresse")
|
||||
|
@ -37,7 +34,6 @@ class Salle(models.Model):
|
|||
return self.name
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CategorieSpectacle(models.Model):
|
||||
name = models.CharField('Nom', max_length=100, unique=True)
|
||||
|
||||
|
@ -114,6 +110,10 @@ class Spectacle(models.Model):
|
|||
# On renvoie la liste des destinataires
|
||||
return members.values()
|
||||
|
||||
@property
|
||||
def is_past(self):
|
||||
return self.date < timezone.now()
|
||||
|
||||
|
||||
class Quote(models.Model):
|
||||
spectacle = models.ForeignKey(Spectacle)
|
||||
|
@ -129,7 +129,6 @@ PAYMENT_TYPES = (
|
|||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Participant(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
choices = models.ManyToManyField(Spectacle,
|
||||
|
@ -157,7 +156,6 @@ DOUBLE_CHOICES = (
|
|||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ChoixSpectacle(models.Model):
|
||||
participant = models.ForeignKey(Participant)
|
||||
spectacle = models.ForeignKey(Spectacle, related_name="participants")
|
||||
|
@ -176,7 +174,7 @@ class ChoixSpectacle(models.Model):
|
|||
|
||||
def __str__(self):
|
||||
return "Vœux de %s pour %s" % (
|
||||
self.participant.user.get_full_name,
|
||||
self.participant.user.get_full_name(),
|
||||
self.spectacle.title)
|
||||
|
||||
class Meta:
|
||||
|
@ -186,7 +184,6 @@ class ChoixSpectacle(models.Model):
|
|||
verbose_name_plural = "voeux"
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Attribution(models.Model):
|
||||
participant = models.ForeignKey(Participant)
|
||||
spectacle = models.ForeignKey(Spectacle, related_name="attribues")
|
||||
|
@ -197,7 +194,6 @@ class Attribution(models.Model):
|
|||
self.spectacle.date)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SpectacleRevente(models.Model):
|
||||
attribution = models.OneToOneField(Attribution,
|
||||
related_name="revente")
|
||||
|
|
|
@ -43,3 +43,6 @@ td {
|
|||
margin: 10px 0px;
|
||||
}
|
||||
|
||||
.spectacle-passe {
|
||||
opacity:0.5;
|
||||
}
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
{% load staticfiles %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link href="{{ STATIC_URL }}grappelli/jquery/ui/css/custom-theme/jquery-ui-1.8.custom.css" rel="stylesheet" type="text/css" media="screen" title="no title" charset="utf-8" />
|
||||
<script src="{{ STATIC_URL }}grappelli/jquery/jquery-1.6.2.min.js" type="text/javascript"></script>
|
||||
<script src="{{ STATIC_URL }}grappelli/jquery/ui/js/jquery-ui-1.8.15.custom.min.js" type="text/javascript"></script>
|
||||
<link href="{{ STATIC_URL }}grappelli/css/tools.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{{ STATIC_URL }}grappelli/css/jquery-ui-grappelli-extensions.css" rel="stylesheet" type="text/css" />
|
||||
<script src="{% static 'js/jquery.min.js'%}" type="text/javascript"></script>
|
||||
<script src="{% static 'js/jquery-ui.min.js' %}" type="text/javascript"></script>
|
||||
<script src="{% static "js/jquery.ui.touch-punch.min.js" %}" type="text/javascript"></script>
|
||||
<link type="text/css" rel="stylesheet" href="{% static "css/jquery-ui.min.css" %}" />
|
||||
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
|
||||
{% endblock %}
|
||||
|
||||
|
@ -102,7 +101,7 @@ var django = {
|
|||
{% endif %}
|
||||
<form class="form-horizontal" id="bda_form" method="post" action="{% url 'bda-tirage-inscription' tirage.id %}">
|
||||
{% csrf_token %}
|
||||
{% include "inscription-formset.html" %}
|
||||
{% include "bda/inscription-formset.html" %}
|
||||
<div class="inscription-bottom">
|
||||
<span class="bda-prix">Prix total actuel : {{ total_price }}€</span>
|
||||
<div class="pull-right">
|
|
@ -1,6 +1,10 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
|
||||
{% endblock %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2><strong>{{tirage_name}}</strong></h2>
|
||||
<h3>Liste des spectacles</h3>
|
||||
|
@ -17,9 +21,9 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{% for spectacle in object_list %}
|
||||
<tr class="clickable-row" data-href="{% url 'bda-spectacle' tirage_id spectacle.id %}">
|
||||
<tr class="clickable-row {% if spectacle.is_past %}spectacle-passe{% endif %}" data-href="{% url 'bda-spectacle' tirage_id spectacle.id %}">
|
||||
<td><a href="{% url 'bda-spectacle' tirage_id spectacle.id %}">{{ spectacle.title }} <span style="font-size:small;" class="glyphicon glyphicon-link" aria-hidden="true"></span></a></td>
|
||||
<td data-sort-value="{{ spectacle.timestamp }}">{{ spectacle.date }}</td>
|
||||
<td data-sort-value="{{ spectacle.timestamp }}"">{{ spectacle.date }}</td>
|
||||
<td data-sort-value="{{ spectacle.location }}">{{ spectacle.location }}</td>
|
||||
<td data-sort-value="{{ spectacle.price |stringformat:".3f" }}">
|
||||
{{ spectacle.price |floatformat }}€
|
||||
|
|
158
bda/views.py
158
bda/views.py
|
@ -124,14 +124,14 @@ def inscription(request, tirage_id):
|
|||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||
if timezone.now() < tirage.ouverture:
|
||||
error_desc = tirage.ouverture.strftime('Ouverture le %d %b %Y à %H:%M')
|
||||
return render(request, 'resume_inscription.html',
|
||||
return render(request, 'bda/resume-inscription-tirage.html',
|
||||
{"error_title": "Le tirage n'est pas encore ouvert !",
|
||||
"error_description": error_desc})
|
||||
if timezone.now() > tirage.fermeture:
|
||||
participant, created = Participant.objects.get_or_create(
|
||||
user=request.user, tirage=tirage)
|
||||
choices = participant.choixspectacle_set.order_by("priority").all()
|
||||
return render(request, "resume_inscription.html",
|
||||
return render(request, "bda/resume-inscription-tirage.html",
|
||||
{"error_title": "C'est fini !",
|
||||
"error_description":
|
||||
"Tirage au sort dans la journée !",
|
||||
|
@ -170,7 +170,7 @@ def inscription(request, tirage_id):
|
|||
total_price += choice.spectacle.price
|
||||
if choice.double:
|
||||
total_price += choice.spectacle.price
|
||||
return render(request, "inscription-bda.html",
|
||||
return render(request, "bda/inscription-tirage.html",
|
||||
{"formset": formset,
|
||||
"success": success,
|
||||
"total_price": total_price,
|
||||
|
@ -179,79 +179,96 @@ def inscription(request, tirage_id):
|
|||
"stateerror": stateerror})
|
||||
|
||||
|
||||
def do_tirage(request, tirage_id):
|
||||
tirage_elt = get_object_or_404(Tirage, id=tirage_id)
|
||||
form = TokenForm(request.POST)
|
||||
if not form.is_valid():
|
||||
return tirage(request, tirage_id)
|
||||
def do_tirage(tirage_elt, token):
|
||||
"""
|
||||
Fonction auxiliaire à la vue ``tirage`` qui lance effectivement le tirage
|
||||
après qu'on a vérifié que c'est légitime et que le token donné en argument
|
||||
est correct.
|
||||
Rend les résultats
|
||||
"""
|
||||
# Initialisation du dictionnaire data qui va contenir les résultats
|
||||
start = time.time()
|
||||
data = {}
|
||||
shows = tirage_elt.spectacle_set.select_related().all()
|
||||
members = tirage_elt.participant_set.all()
|
||||
choices = ChoixSpectacle.objects.filter(spectacle__tirage=tirage_elt) \
|
||||
.order_by('participant', 'priority').select_related().all()
|
||||
algo = Algorithm(shows, members, choices)
|
||||
results = algo(form.cleaned_data["token"])
|
||||
total_slots = 0
|
||||
total_losers = 0
|
||||
data = {
|
||||
'shows': tirage_elt.spectacle_set.select_related().all(),
|
||||
'token': token,
|
||||
'members': tirage_elt.participant_set.all(),
|
||||
'total_slots': 0,
|
||||
'total_losers': 0,
|
||||
'total_sold': 0,
|
||||
'total_deficit': 0,
|
||||
'opera_deficit': 0,
|
||||
}
|
||||
|
||||
# On lance le tirage
|
||||
choices = (
|
||||
ChoixSpectacle.objects
|
||||
.filter(spectacle__tirage=tirage_elt)
|
||||
.order_by('participant', 'priority')
|
||||
.select_related().all()
|
||||
)
|
||||
results = Algorithm(data['shows'], data['members'], choices)(token)
|
||||
|
||||
# On compte les places attribuées et les déçus
|
||||
for (_, members, losers) in results:
|
||||
total_slots += len(members)
|
||||
total_losers += len(losers)
|
||||
data["total_slots"] = total_slots
|
||||
data["total_losers"] = total_losers
|
||||
data["shows"] = shows
|
||||
data["token"] = form.cleaned_data["token"]
|
||||
data["members"] = members
|
||||
data["results"] = results
|
||||
total_sold = 0
|
||||
total_deficit = 0
|
||||
opera_deficit = 0
|
||||
data['total_slots'] += len(members)
|
||||
data['total_losers'] += len(losers)
|
||||
|
||||
# On calcule le déficit et les bénéfices pour le BdA
|
||||
# FIXME: le traitement de l'opéra est sale
|
||||
for (show, members, _) in results:
|
||||
deficit = (show.slots - len(members)) * show.price
|
||||
total_sold += show.slots * show.price
|
||||
data['total_sold'] += show.slots * show.price
|
||||
if deficit >= 0:
|
||||
if "Opéra" in show.location.name:
|
||||
opera_deficit += deficit
|
||||
total_deficit += deficit
|
||||
data["total_sold"] = total_sold - total_deficit
|
||||
data["total_deficit"] = total_deficit
|
||||
data["opera_deficit"] = opera_deficit
|
||||
data['opera_deficit'] += deficit
|
||||
data['total_deficit'] += deficit
|
||||
data["total_sold"] -= data['total_deficit']
|
||||
|
||||
# Participant objects are not shared accross spectacle results,
|
||||
# so assign a single object for each Participant id
|
||||
members_uniq = {}
|
||||
members2 = {}
|
||||
for (show, members, _) in results:
|
||||
for (member, _, _, _) in members:
|
||||
if member.id not in members_uniq:
|
||||
members_uniq[member.id] = member
|
||||
members2[member] = []
|
||||
member.total = 0
|
||||
member = members_uniq[member.id]
|
||||
members2[member].append(show)
|
||||
member.total += show.price
|
||||
members2 = members2.items()
|
||||
data["members2"] = sorted(members2, key=lambda m: m[0].user.last_name)
|
||||
|
||||
# ---
|
||||
# À partir d'ici, le tirage devient effectif
|
||||
# ---
|
||||
|
||||
# On suppression les vieilles attributions, on sauvegarde le token et on
|
||||
# désactive le tirage
|
||||
Attribution.objects.filter(spectacle__tirage=tirage_elt).delete()
|
||||
tirage_elt.tokens += '{:s}\n"""{:s}"""\n'.format(
|
||||
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
|
||||
token)
|
||||
tirage_elt.enable_do_tirage = False
|
||||
tirage_elt.save()
|
||||
|
||||
# On enregistre les nouvelles attributions
|
||||
Attribution.objects.bulk_create([
|
||||
Attribution(spectacle=show, participant=member)
|
||||
for show, members, _ in results
|
||||
for member, _, _, _ in members
|
||||
])
|
||||
|
||||
# On inscrit à BdA-Revente ceux qui n'ont pas eu les places voulues
|
||||
for (show, _, losers) in results:
|
||||
for (loser, _, _, _) in losers:
|
||||
loser.choicesrevente.add(show)
|
||||
loser.save()
|
||||
|
||||
data["duration"] = time.time() - start
|
||||
if request.user.is_authenticated():
|
||||
members2 = {}
|
||||
# Participant objects are not shared accross spectacle results,
|
||||
# so assign a single object for each Participant id
|
||||
members_uniq = {}
|
||||
for (show, members, _) in results:
|
||||
for (member, _, _, _) in members:
|
||||
if member.id not in members_uniq:
|
||||
members_uniq[member.id] = member
|
||||
members2[member] = []
|
||||
member.total = 0
|
||||
member = members_uniq[member.id]
|
||||
members2[member].append(show)
|
||||
member.total += show.price
|
||||
members2 = members2.items()
|
||||
data["members2"] = sorted(members2, key=lambda m: m[0].user.last_name)
|
||||
# À partir d'ici, le tirage devient effectif
|
||||
Attribution.objects.filter(spectacle__tirage=tirage_elt).delete()
|
||||
tirage_elt.tokens += "%s\n\"\"\"%s\"\"\"\n" % (
|
||||
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
|
||||
form.cleaned_data['token'])
|
||||
tirage_elt.enable_do_tirage = False
|
||||
tirage_elt.save()
|
||||
Attribution.objects.bulk_create([
|
||||
Attribution(spectacle=show, participant=member)
|
||||
for show, members, _ in results
|
||||
for member, _, _, _ in members])
|
||||
# On inscrit à BdA-Revente ceux qui n'ont pas eu les places voulues
|
||||
for (show, _, losers) in results:
|
||||
for (loser, _, _, _) in losers:
|
||||
loser.choicesrevente.add(show)
|
||||
loser.save()
|
||||
return render(request, "bda-attrib-extra.html", data)
|
||||
else:
|
||||
return render(request, "bda-attrib.html", data)
|
||||
data["results"] = results
|
||||
return data
|
||||
|
||||
|
||||
@buro_required
|
||||
|
@ -263,7 +280,8 @@ def tirage(request, tirage_id):
|
|||
if request.POST:
|
||||
form = TokenForm(request.POST)
|
||||
if form.is_valid():
|
||||
return do_tirage(request, tirage_id)
|
||||
results = do_tirage(tirage_elt, form.cleaned_data['token'])
|
||||
return render(request, "bda-attrib-extra.html", results)
|
||||
else:
|
||||
form = TokenForm()
|
||||
return render(request, "bda-token.html", {"form": form})
|
||||
|
|
|
@ -9,10 +9,6 @@ For the full list of settings and their values, see
|
|||
https://docs.djangoproject.com/en/1.8/ref/settings/
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
import os
|
||||
|
||||
|
@ -163,6 +159,8 @@ AUTHENTICATION_BACKENDS = (
|
|||
'kfet.backends.GenericTeamBackend',
|
||||
)
|
||||
|
||||
# LDAP_SERVER_URL = 'ldaps://ldap.spi.ens.fr:636'
|
||||
|
||||
# EMAIL_HOST="nef.ens.fr"
|
||||
|
||||
RECAPTCHA_PUBLIC_KEY = "DUMMY"
|
||||
|
|
|
@ -4,10 +4,6 @@
|
|||
Fichier principal de configuration des urls du projet GestioCOF
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import autocomplete_light
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -61,7 +57,8 @@ urlpatterns = [
|
|||
name='password_change_done'),
|
||||
# Inscription d'un nouveau membre
|
||||
url(r'^registration$', gestioncof_views.registration),
|
||||
url(r'^registration/clipper/(?P<login_clipper>[\w-]+)$',
|
||||
url(r'^registration/clipper/(?P<login_clipper>[\w-]+)/'
|
||||
r'(?P<fullname>.*)$',
|
||||
gestioncof_views.registration_form2, name="clipper-registration"),
|
||||
url(r'^registration/user/(?P<username>.+)$',
|
||||
gestioncof_views.registration_form2, name="user-registration"),
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from ldap3 import Connection
|
||||
|
||||
from django import shortcuts
|
||||
from django.http import Http404
|
||||
from django.db.models import Q
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from gestioncof.models import CofProfile, Clipper
|
||||
from django.conf import settings
|
||||
|
||||
from gestioncof.models import CofProfile
|
||||
from gestioncof.decorators import buro_required
|
||||
|
||||
|
||||
class Clipper(object):
|
||||
def __init__(self, clipper, fullname):
|
||||
self.clipper = clipper
|
||||
self.fullname = fullname
|
||||
|
||||
|
||||
@buro_required
|
||||
def autocomplete(request):
|
||||
if "q" not in request.GET:
|
||||
|
@ -25,37 +30,51 @@ def autocomplete(request):
|
|||
queries = {}
|
||||
bits = q.split()
|
||||
|
||||
queries['members'] = CofProfile.objects.filter(Q(is_cof=True))
|
||||
queries['users'] = User.objects.filter(Q(profile__is_cof=False))
|
||||
queries['clippers'] = Clipper.objects
|
||||
# Fetching data from User and CofProfile tables
|
||||
queries['members'] = CofProfile.objects.filter(is_cof=True)
|
||||
queries['users'] = User.objects.filter(profile__is_cof=False)
|
||||
for bit in bits:
|
||||
queries['members'] = queries['members'].filter(
|
||||
Q(user__first_name__icontains=bit)
|
||||
| Q(user__last_name__icontains=bit)
|
||||
| Q(user__username__icontains=bit)
|
||||
| Q(login_clipper__icontains=bit))
|
||||
Q(user__first_name__icontains=bit)
|
||||
| Q(user__last_name__icontains=bit)
|
||||
| Q(user__username__icontains=bit)
|
||||
| Q(login_clipper__icontains=bit))
|
||||
queries['users'] = queries['users'].filter(
|
||||
Q(first_name__icontains=bit)
|
||||
| Q(last_name__icontains=bit)
|
||||
| Q(username__icontains=bit))
|
||||
queries['clippers'] = queries['clippers'].filter(
|
||||
Q(fullname__icontains=bit)
|
||||
| Q(username__icontains=bit))
|
||||
Q(first_name__icontains=bit)
|
||||
| Q(last_name__icontains=bit)
|
||||
| Q(username__icontains=bit))
|
||||
queries['members'] = queries['members'].distinct()
|
||||
queries['users'] = queries['users'].distinct()
|
||||
usernames = list(queries['members'].values_list('login_clipper',
|
||||
flat='True')) \
|
||||
+ list(queries['users'].values_list('profile__login_clipper',
|
||||
flat='True'))
|
||||
queries['clippers'] = queries['clippers'] \
|
||||
.exclude(username__in=usernames).distinct()
|
||||
# add clippers
|
||||
|
||||
# Clearing redundancies
|
||||
usernames = (
|
||||
set(queries['members'].values_list('login_clipper', flat='True'))
|
||||
| set(queries['users'].values_list('profile__login_clipper',
|
||||
flat='True'))
|
||||
)
|
||||
|
||||
# Fetching data from the SPI
|
||||
if hasattr(settings, 'LDAP_SERVER_URL'):
|
||||
# Fetching
|
||||
ldap_query = '(|{:s})'.format(''.join(
|
||||
['(cn=*{bit:s}*)(uid=*{bit:s}*)'.format(**{"bit": bit})
|
||||
for bit in bits]
|
||||
))
|
||||
with Connection(settings.LDAP_SERVER_URL) as conn:
|
||||
conn.search(
|
||||
'dc=spi,dc=ens,dc=fr', ldap_query,
|
||||
attributes=['uid', 'cn']
|
||||
)
|
||||
queries['clippers'] = conn.entries
|
||||
# Clearing redundancies
|
||||
queries['clippers'] = [
|
||||
Clipper(clipper.uid, clipper.cn)
|
||||
for clipper in queries['clippers']
|
||||
if str(clipper.uid) not in usernames
|
||||
]
|
||||
|
||||
# Resulting data
|
||||
data.update(queries)
|
||||
|
||||
options = 0
|
||||
for query in queries.values():
|
||||
options += len(query)
|
||||
data['options'] = options
|
||||
data['options'] = sum(len(query) for query in queries)
|
||||
|
||||
return shortcuts.render(request, "autocomplete_user.html", data)
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import autocomplete_light
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
autocomplete_light.register(
|
||||
User, search_fields=('username', 'first_name', 'last_name'),
|
||||
autocomplete_js_attributes={'placeholder': 'membre...'})
|
||||
attrs={'placeholder': 'membre...'}
|
||||
)
|
||||
|
|
|
@ -152,156 +152,6 @@
|
|||
"model": "gestioncof.petitcourssubject",
|
||||
"pk": 4
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "college",
|
||||
"user": 11,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 1
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "lycee",
|
||||
"user": 11,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 2
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "prepa1styear",
|
||||
"user": 11,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 3
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "prepa2ndyear",
|
||||
"user": 11,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 4
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "licence3",
|
||||
"user": 11,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 5
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "prepa1styear",
|
||||
"user": 43,
|
||||
"agrege": true
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 6
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "prepa2ndyear",
|
||||
"user": 43,
|
||||
"agrege": true
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 7
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "licence3",
|
||||
"user": 43,
|
||||
"agrege": true
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 8
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 2,
|
||||
"niveau": "college",
|
||||
"user": 43,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 9
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 2,
|
||||
"niveau": "lycee",
|
||||
"user": 43,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 10
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 3,
|
||||
"niveau": "lycee",
|
||||
"user": 48,
|
||||
"agrege": true
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 11
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 3,
|
||||
"niveau": "prepa1styear",
|
||||
"user": 48,
|
||||
"agrege": true
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 12
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 3,
|
||||
"niveau": "prepa2ndyear",
|
||||
"user": 48,
|
||||
"agrege": true
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 13
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 3,
|
||||
"niveau": "licence3",
|
||||
"user": 48,
|
||||
"agrege": true
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 14
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 4,
|
||||
"niveau": "college",
|
||||
"user": 10,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 15
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"traitee": false,
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
[
|
||||
{
|
||||
"fields": {
|
||||
"username": "root",
|
||||
"first_name": "super",
|
||||
"last_name": "user",
|
||||
"is_active": true,
|
||||
"is_superuser": true,
|
||||
"is_staff": true,
|
||||
"last_login": null,
|
||||
"groups": [],
|
||||
"user_permissions": [],
|
||||
"password": "pbkdf2_sha256$12000$yRpkPuayQ8De$h6bDe+Q4kMikzwEbLNw2I9/V/1/v3F3yLIjEZIFSHrY=",
|
||||
"email": "root@localhost",
|
||||
"date_joined": "2016-06-15T17:50:57Z"
|
||||
},
|
||||
"model": "auth.user",
|
||||
"pk": 62
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"departement": "",
|
||||
"type_cotiz": "normalien",
|
||||
"petits_cours_remarques": "",
|
||||
"is_buro": true,
|
||||
"is_cof": true,
|
||||
"mailing_cof": true,
|
||||
"comments": "Super utilisateur",
|
||||
"login_clipper": "",
|
||||
"phone": "",
|
||||
"num": 62,
|
||||
"mailing_bda_revente": true,
|
||||
"user": 62,
|
||||
"petits_cours_accept": false,
|
||||
"mailing_bda": true,
|
||||
"occupation": "1A"
|
||||
},
|
||||
"model": "gestioncof.cofprofile",
|
||||
"pk": 62
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load diff
|
@ -378,12 +378,12 @@ EventFormset = formset_factory(AdminEventForm, BaseEventRegistrationFormset)
|
|||
class CalendarForm(forms.ModelForm):
|
||||
subscribe_to_events = forms.BooleanField(
|
||||
initial=True,
|
||||
label="Événements du COF.")
|
||||
label="Événements du COF")
|
||||
subscribe_to_my_shows = forms.BooleanField(
|
||||
initial=True,
|
||||
label="Les spectacles pour lesquels j'ai obtenu une place.")
|
||||
label="Les spectacles pour lesquels j'ai obtenu une place")
|
||||
other_shows = forms.ModelMultipleChoiceField(
|
||||
label="Spectacles supplémentaires.",
|
||||
label="Spectacles supplémentaires",
|
||||
queryset=Spectacle.objects.filter(tirage__active=True),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False)
|
||||
|
|
41
gestioncof/management/base.py
Normal file
41
gestioncof/management/base.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
"""
|
||||
Un mixin à utiliser avec BaseCommand pour charger des objets depuis un json
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
class MyBaseCommand(BaseCommand):
|
||||
"""
|
||||
Ajoute une méthode ``from_json`` qui charge des objets à partir d'un json.
|
||||
"""
|
||||
|
||||
def from_json(self, filename, data_dir, klass,
|
||||
callback=lambda obj: obj):
|
||||
"""
|
||||
Charge les objets contenus dans le fichier json référencé par
|
||||
``filename`` dans la base de donnée. La fonction callback est appelées
|
||||
sur chaque objet avant enregistrement.
|
||||
"""
|
||||
self.stdout.write("Chargement de {:s}".format(filename))
|
||||
with open(os.path.join(data_dir, filename), 'r') as file:
|
||||
descriptions = json.load(file)
|
||||
objects = []
|
||||
nb_new = 0
|
||||
for description in descriptions:
|
||||
qset = klass.objects.filter(**description)
|
||||
try:
|
||||
objects.append(qset.get())
|
||||
except klass.DoesNotExist:
|
||||
obj = klass(**description)
|
||||
obj = callback(obj)
|
||||
obj.save()
|
||||
objects.append(obj)
|
||||
nb_new += 1
|
||||
self.stdout.write("- {:d} objets créés".format(nb_new))
|
||||
self.stdout.write("- {:d} objets gardés en l'état"
|
||||
.format(len(objects)-nb_new))
|
||||
return objects
|
109
gestioncof/management/commands/loaddevdata.py
Normal file
109
gestioncof/management/commands/loaddevdata.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
"""
|
||||
Charge des données de test dans la BDD
|
||||
- Utilisateurs
|
||||
- Sondage
|
||||
- Événement
|
||||
- Petits cours
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import random
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management import call_command
|
||||
|
||||
from gestioncof.management.base import MyBaseCommand
|
||||
from gestioncof.petits_cours_models import (
|
||||
PetitCoursAbility, PetitCoursSubject, LEVELS_CHOICES,
|
||||
PetitCoursAttributionCounter
|
||||
)
|
||||
|
||||
# Où sont stockés les fichiers json
|
||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
||||
'data')
|
||||
|
||||
|
||||
class Command(MyBaseCommand):
|
||||
help = "Charge des données de test dans la BDD"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""
|
||||
Permet de ne pas créer l'utilisateur "root".
|
||||
"""
|
||||
parser.add_argument(
|
||||
'--no-root',
|
||||
action='store_true',
|
||||
dest='no-root',
|
||||
default=False,
|
||||
help='Ne crée pas l\'utilisateur "root"'
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# ---
|
||||
# Utilisateurs
|
||||
# ---
|
||||
|
||||
# Gaulois
|
||||
gaulois = self.from_json('gaulois.json', DATA_DIR, User)
|
||||
for user in gaulois:
|
||||
user.profile.is_cof = True
|
||||
user.profile.save()
|
||||
|
||||
# Romains
|
||||
self.from_json('romains.json', DATA_DIR, User)
|
||||
|
||||
# Root
|
||||
no_root = options.get('no-root', False)
|
||||
if not no_root:
|
||||
self.stdout.write("Création de l'utilisateur root")
|
||||
root, _ = User.objects.get_or_create(
|
||||
username='root',
|
||||
first_name='super',
|
||||
last_name='user',
|
||||
email='root@localhost')
|
||||
root.set_password('root')
|
||||
root.is_staff = True
|
||||
root.is_superuser = True
|
||||
root.profile.is_cof = True
|
||||
root.profile.is_buro = True
|
||||
root.profile.save()
|
||||
root.save()
|
||||
|
||||
# ---
|
||||
# Petits cours
|
||||
# ---
|
||||
|
||||
self.stdout.write("Inscriptions au système des petits cours")
|
||||
levels = [id for (id, verbose) in LEVELS_CHOICES]
|
||||
subjects = list(PetitCoursSubject.objects.all())
|
||||
nb_of_teachers = 0
|
||||
for user in gaulois:
|
||||
if random.randint(0, 1):
|
||||
nb_of_teachers += 1
|
||||
# L'utilisateur reçoit les demandes de petits cours
|
||||
user.profile.petits_cours_accept = True
|
||||
user.save()
|
||||
# L'utilisateur est compétent dans une matière
|
||||
subject = random.choice(subjects)
|
||||
if not PetitCoursAbility.objects.filter(
|
||||
user=user,
|
||||
matiere=subject).exists():
|
||||
PetitCoursAbility.objects.create(
|
||||
user=user,
|
||||
matiere=subject,
|
||||
niveau=random.choice(levels),
|
||||
agrege=bool(random.randint(0, 1))
|
||||
)
|
||||
# On initialise son compteur d'attributions
|
||||
PetitCoursAttributionCounter.objects.get_or_create(
|
||||
user=user,
|
||||
matiere=subject
|
||||
)
|
||||
self.stdout.write("- {:d} inscriptions".format(nb_of_teachers))
|
||||
|
||||
# ---
|
||||
# Le BdA
|
||||
# ---
|
||||
|
||||
call_command('loadbdadevdata')
|
368
gestioncof/management/data/gaulois.json
Normal file
368
gestioncof/management/data/gaulois.json
Normal file
|
@ -0,0 +1,368 @@
|
|||
[
|
||||
{
|
||||
"username": "Abraracourcix",
|
||||
"email": "Abraracourcix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Abraracourcix"
|
||||
},
|
||||
{
|
||||
"username": "Acidenitrix",
|
||||
"email": "Acidenitrix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Acidenitrix"
|
||||
},
|
||||
{
|
||||
"username": "Agecanonix",
|
||||
"email": "Agecanonix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Agecanonix"
|
||||
},
|
||||
{
|
||||
"username": "Alambix",
|
||||
"email": "Alambix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Alambix"
|
||||
},
|
||||
{
|
||||
"username": "Amerix",
|
||||
"email": "Amerix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Amerix"
|
||||
},
|
||||
{
|
||||
"username": "Amnesix",
|
||||
"email": "Amnesix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Amnesix"
|
||||
},
|
||||
{
|
||||
"username": "Aniline",
|
||||
"email": "Aniline.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Aniline"
|
||||
},
|
||||
{
|
||||
"username": "Aplusbegalix",
|
||||
"email": "Aplusbegalix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Aplusbegalix"
|
||||
},
|
||||
{
|
||||
"username": "Archeopterix",
|
||||
"email": "Archeopterix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Archeopterix"
|
||||
},
|
||||
{
|
||||
"username": "Assurancetourix",
|
||||
"email": "Assurancetourix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Assurancetourix"
|
||||
},
|
||||
{
|
||||
"username": "Asterix",
|
||||
"email": "Asterix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Asterix"
|
||||
},
|
||||
{
|
||||
"username": "Astronomix",
|
||||
"email": "Astronomix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Astronomix"
|
||||
},
|
||||
{
|
||||
"username": "Avoranfix",
|
||||
"email": "Avoranfix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Avoranfix"
|
||||
},
|
||||
{
|
||||
"username": "Barometrix",
|
||||
"email": "Barometrix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Barometrix"
|
||||
},
|
||||
{
|
||||
"username": "Beaufix",
|
||||
"email": "Beaufix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Beaufix"
|
||||
},
|
||||
{
|
||||
"username": "Berlix",
|
||||
"email": "Berlix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Berlix"
|
||||
},
|
||||
{
|
||||
"username": "Bonemine",
|
||||
"email": "Bonemine.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Bonemine"
|
||||
},
|
||||
{
|
||||
"username": "Boufiltre",
|
||||
"email": "Boufiltre.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Boufiltre"
|
||||
},
|
||||
{
|
||||
"username": "Catedralgotix",
|
||||
"email": "Catedralgotix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Catedralgotix"
|
||||
},
|
||||
{
|
||||
"username": "CesarLabeldecadix",
|
||||
"email": "CesarLabeldecadix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "CesarLabeldecadix"
|
||||
},
|
||||
{
|
||||
"username": "Cetautomatix",
|
||||
"email": "Cetautomatix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Cetautomatix"
|
||||
},
|
||||
{
|
||||
"username": "Cetyounix",
|
||||
"email": "Cetyounix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Cetyounix"
|
||||
},
|
||||
{
|
||||
"username": "Changeledix",
|
||||
"email": "Changeledix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Changeledix"
|
||||
},
|
||||
{
|
||||
"username": "Chanteclairix",
|
||||
"email": "Chanteclairix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Chanteclairix"
|
||||
},
|
||||
{
|
||||
"username": "Cicatrix",
|
||||
"email": "Cicatrix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Cicatrix"
|
||||
},
|
||||
{
|
||||
"username": "Comix",
|
||||
"email": "Comix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Comix"
|
||||
},
|
||||
{
|
||||
"username": "Diagnostix",
|
||||
"email": "Diagnostix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Diagnostix"
|
||||
},
|
||||
{
|
||||
"username": "Doublepolemix",
|
||||
"email": "Doublepolemix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Doublepolemix"
|
||||
},
|
||||
{
|
||||
"username": "Eponine",
|
||||
"email": "Eponine.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Eponine"
|
||||
},
|
||||
{
|
||||
"username": "Falbala",
|
||||
"email": "Falbala.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Falbala"
|
||||
},
|
||||
{
|
||||
"username": "Fanzine",
|
||||
"email": "Fanzine.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Fanzine"
|
||||
},
|
||||
{
|
||||
"username": "Gelatine",
|
||||
"email": "Gelatine.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Gelatine"
|
||||
},
|
||||
{
|
||||
"username": "Goudurix",
|
||||
"email": "Goudurix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Goudurix"
|
||||
},
|
||||
{
|
||||
"username": "Homeopatix",
|
||||
"email": "Homeopatix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Homeopatix"
|
||||
},
|
||||
{
|
||||
"username": "Idefix",
|
||||
"email": "Idefix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Idefix"
|
||||
},
|
||||
{
|
||||
"username": "Ielosubmarine",
|
||||
"email": "Ielosubmarine.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Ielosubmarine"
|
||||
},
|
||||
{
|
||||
"username": "Keskonrix",
|
||||
"email": "Keskonrix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Keskonrix"
|
||||
},
|
||||
{
|
||||
"username": "Lentix",
|
||||
"email": "Lentix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Lentix"
|
||||
},
|
||||
{
|
||||
"username": "Maestria",
|
||||
"email": "Maestria.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Maestria"
|
||||
},
|
||||
{
|
||||
"username": "MaitrePanix",
|
||||
"email": "MaitrePanix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "MaitrePanix"
|
||||
},
|
||||
{
|
||||
"username": "MmeAgecanonix",
|
||||
"email": "MmeAgecanonix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "MmeAgecanonix"
|
||||
},
|
||||
{
|
||||
"username": "Moralelastix",
|
||||
"email": "Moralelastix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Moralelastix"
|
||||
},
|
||||
{
|
||||
"username": "Obelix",
|
||||
"email": "Obelix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Obelix"
|
||||
},
|
||||
{
|
||||
"username": "Obelodalix",
|
||||
"email": "Obelodalix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Obelodalix"
|
||||
},
|
||||
{
|
||||
"username": "Odalix",
|
||||
"email": "Odalix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Odalix"
|
||||
},
|
||||
{
|
||||
"username": "Ordralfabetix",
|
||||
"email": "Ordralfabetix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Ordralfabetix"
|
||||
},
|
||||
{
|
||||
"username": "Orthopedix",
|
||||
"email": "Orthopedix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Orthopedix"
|
||||
},
|
||||
{
|
||||
"username": "Panoramix",
|
||||
"email": "Panoramix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Panoramix"
|
||||
},
|
||||
{
|
||||
"username": "Plaintcontrix",
|
||||
"email": "Plaintcontrix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Plaintcontrix"
|
||||
},
|
||||
{
|
||||
"username": "Praline",
|
||||
"email": "Praline.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Praline"
|
||||
},
|
||||
{
|
||||
"username": "Prefix",
|
||||
"email": "Prefix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Prefix"
|
||||
},
|
||||
{
|
||||
"username": "Prolix",
|
||||
"email": "Prolix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Prolix"
|
||||
},
|
||||
{
|
||||
"username": "Pronostix",
|
||||
"email": "Pronostix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Pronostix"
|
||||
},
|
||||
{
|
||||
"username": "Quatredeusix",
|
||||
"email": "Quatredeusix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Quatredeusix"
|
||||
},
|
||||
{
|
||||
"username": "Saingesix",
|
||||
"email": "Saingesix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Saingesix"
|
||||
},
|
||||
{
|
||||
"username": "Segregationnix",
|
||||
"email": "Segregationnix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Segregationnix"
|
||||
},
|
||||
{
|
||||
"username": "Septantesix",
|
||||
"email": "Septantesix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Septantesix"
|
||||
},
|
||||
{
|
||||
"username": "Tournedix",
|
||||
"email": "Tournedix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Tournedix"
|
||||
},
|
||||
{
|
||||
"username": "Tragicomix",
|
||||
"email": "Tragicomix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Tragicomix"
|
||||
},
|
||||
{
|
||||
"username": "Coriza",
|
||||
"email": "Coriza.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Coriza"
|
||||
},
|
||||
{
|
||||
"username": "Zerozerosix",
|
||||
"email": "Zerozerosix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Zerozerosix"
|
||||
}
|
||||
]
|
614
gestioncof/management/data/romains.json
Normal file
614
gestioncof/management/data/romains.json
Normal file
|
@ -0,0 +1,614 @@
|
|||
[
|
||||
{
|
||||
"username": "Abel",
|
||||
"email": "Abel.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Abel"
|
||||
},
|
||||
{
|
||||
"username": "Abelardus",
|
||||
"email": "Abelardus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Abelardus"
|
||||
},
|
||||
{
|
||||
"username": "Abrahamus",
|
||||
"email": "Abrahamus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Abrahamus"
|
||||
},
|
||||
{
|
||||
"username": "Acacius",
|
||||
"email": "Acacius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Acacius"
|
||||
},
|
||||
{
|
||||
"username": "Accius",
|
||||
"email": "Accius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Accius"
|
||||
},
|
||||
{
|
||||
"username": "Achaicus",
|
||||
"email": "Achaicus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Achaicus"
|
||||
},
|
||||
{
|
||||
"username": "Achill",
|
||||
"email": "Achill.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Achill"
|
||||
},
|
||||
{
|
||||
"username": "Achilles",
|
||||
"email": "Achilles.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Achilles"
|
||||
},
|
||||
{
|
||||
"username": "Achilleus",
|
||||
"email": "Achilleus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Achilleus"
|
||||
},
|
||||
{
|
||||
"username": "Acrisius",
|
||||
"email": "Acrisius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Acrisius"
|
||||
},
|
||||
{
|
||||
"username": "Actaeon",
|
||||
"email": "Actaeon.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Actaeon"
|
||||
},
|
||||
{
|
||||
"username": "Acteon",
|
||||
"email": "Acteon.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Acteon"
|
||||
},
|
||||
{
|
||||
"username": "Adalricus",
|
||||
"email": "Adalricus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adalricus"
|
||||
},
|
||||
{
|
||||
"username": "Adelfonsus",
|
||||
"email": "Adelfonsus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adelfonsus"
|
||||
},
|
||||
{
|
||||
"username": "Adelphus",
|
||||
"email": "Adelphus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adelphus"
|
||||
},
|
||||
{
|
||||
"username": "Adeodatus",
|
||||
"email": "Adeodatus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adeodatus"
|
||||
},
|
||||
{
|
||||
"username": "Adolfus",
|
||||
"email": "Adolfus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adolfus"
|
||||
},
|
||||
{
|
||||
"username": "Adolphus",
|
||||
"email": "Adolphus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adolphus"
|
||||
},
|
||||
{
|
||||
"username": "Adrastus",
|
||||
"email": "Adrastus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adrastus"
|
||||
},
|
||||
{
|
||||
"username": "Adrianus",
|
||||
"email": "Adrianus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adrianus"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6gidius",
|
||||
"email": "\u00c6gidius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6gidius"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6lia",
|
||||
"email": "\u00c6lia.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6lia"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6lianus",
|
||||
"email": "\u00c6lianus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6lianus"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6milianus",
|
||||
"email": "\u00c6milianus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6milianus"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6milius",
|
||||
"email": "\u00c6milius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6milius"
|
||||
},
|
||||
{
|
||||
"username": "Aeneas",
|
||||
"email": "Aeneas.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Aeneas"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6olus",
|
||||
"email": "\u00c6olus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6olus"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6schylus",
|
||||
"email": "\u00c6schylus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6schylus"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6son",
|
||||
"email": "\u00c6son.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6son"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6sop",
|
||||
"email": "\u00c6sop.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6sop"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6ther",
|
||||
"email": "\u00c6ther.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6ther"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6tius",
|
||||
"email": "\u00c6tius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6tius"
|
||||
},
|
||||
{
|
||||
"username": "Agapetus",
|
||||
"email": "Agapetus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Agapetus"
|
||||
},
|
||||
{
|
||||
"username": "Agapitus",
|
||||
"email": "Agapitus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Agapitus"
|
||||
},
|
||||
{
|
||||
"username": "Agapius",
|
||||
"email": "Agapius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Agapius"
|
||||
},
|
||||
{
|
||||
"username": "Agathangelus",
|
||||
"email": "Agathangelus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Agathangelus"
|
||||
},
|
||||
{
|
||||
"username": "Aigidius",
|
||||
"email": "Aigidius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Aigidius"
|
||||
},
|
||||
{
|
||||
"username": "Aiolus",
|
||||
"email": "Aiolus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Aiolus"
|
||||
},
|
||||
{
|
||||
"username": "Ajax",
|
||||
"email": "Ajax.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Ajax"
|
||||
},
|
||||
{
|
||||
"username": "Alair",
|
||||
"email": "Alair.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alair"
|
||||
},
|
||||
{
|
||||
"username": "Alaricus",
|
||||
"email": "Alaricus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alaricus"
|
||||
},
|
||||
{
|
||||
"username": "Albanus",
|
||||
"email": "Albanus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Albanus"
|
||||
},
|
||||
{
|
||||
"username": "Alberic",
|
||||
"email": "Alberic.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alberic"
|
||||
},
|
||||
{
|
||||
"username": "Albericus",
|
||||
"email": "Albericus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Albericus"
|
||||
},
|
||||
{
|
||||
"username": "Albertus",
|
||||
"email": "Albertus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Albertus"
|
||||
},
|
||||
{
|
||||
"username": "Albinus",
|
||||
"email": "Albinus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Albinus"
|
||||
},
|
||||
{
|
||||
"username": "Albus",
|
||||
"email": "Albus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Albus"
|
||||
},
|
||||
{
|
||||
"username": "Alcaeus",
|
||||
"email": "Alcaeus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alcaeus"
|
||||
},
|
||||
{
|
||||
"username": "Alcander",
|
||||
"email": "Alcander.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alcander"
|
||||
},
|
||||
{
|
||||
"username": "Alcimus",
|
||||
"email": "Alcimus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alcimus"
|
||||
},
|
||||
{
|
||||
"username": "Alcinder",
|
||||
"email": "Alcinder.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alcinder"
|
||||
},
|
||||
{
|
||||
"username": "Alerio",
|
||||
"email": "Alerio.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alerio"
|
||||
},
|
||||
{
|
||||
"username": "Alexandrus",
|
||||
"email": "Alexandrus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alexandrus"
|
||||
},
|
||||
{
|
||||
"username": "Alexis",
|
||||
"email": "Alexis.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alexis"
|
||||
},
|
||||
{
|
||||
"username": "Alexius",
|
||||
"email": "Alexius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alexius"
|
||||
},
|
||||
{
|
||||
"username": "Alexus",
|
||||
"email": "Alexus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alexus"
|
||||
},
|
||||
{
|
||||
"username": "Alfonsus",
|
||||
"email": "Alfonsus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alfonsus"
|
||||
},
|
||||
{
|
||||
"username": "Alfredus",
|
||||
"email": "Alfredus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alfredus"
|
||||
},
|
||||
{
|
||||
"username": "Almericus",
|
||||
"email": "Almericus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Almericus"
|
||||
},
|
||||
{
|
||||
"username": "Aloisius",
|
||||
"email": "Aloisius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Aloisius"
|
||||
},
|
||||
{
|
||||
"username": "Aloysius",
|
||||
"email": "Aloysius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Aloysius"
|
||||
},
|
||||
{
|
||||
"username": "Alphaeus",
|
||||
"email": "Alphaeus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alphaeus"
|
||||
},
|
||||
{
|
||||
"username": "Alpheaus",
|
||||
"email": "Alpheaus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alpheaus"
|
||||
},
|
||||
{
|
||||
"username": "Alpheus",
|
||||
"email": "Alpheus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alpheus"
|
||||
},
|
||||
{
|
||||
"username": "Alphoeus",
|
||||
"email": "Alphoeus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alphoeus"
|
||||
},
|
||||
{
|
||||
"username": "Alphonsus",
|
||||
"email": "Alphonsus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alphonsus"
|
||||
},
|
||||
{
|
||||
"username": "Alphonzus",
|
||||
"email": "Alphonzus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alphonzus"
|
||||
},
|
||||
{
|
||||
"username": "Alvinius",
|
||||
"email": "Alvinius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alvinius"
|
||||
},
|
||||
{
|
||||
"username": "Alvredus",
|
||||
"email": "Alvredus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alvredus"
|
||||
},
|
||||
{
|
||||
"username": "Amadeus",
|
||||
"email": "Amadeus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amadeus"
|
||||
},
|
||||
{
|
||||
"username": "Amaliricus",
|
||||
"email": "Amaliricus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amaliricus"
|
||||
},
|
||||
{
|
||||
"username": "Amandus",
|
||||
"email": "Amandus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amandus"
|
||||
},
|
||||
{
|
||||
"username": "Amantius",
|
||||
"email": "Amantius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amantius"
|
||||
},
|
||||
{
|
||||
"username": "Amarandus",
|
||||
"email": "Amarandus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amarandus"
|
||||
},
|
||||
{
|
||||
"username": "Amaranthus",
|
||||
"email": "Amaranthus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amaranthus"
|
||||
},
|
||||
{
|
||||
"username": "Amatus",
|
||||
"email": "Amatus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amatus"
|
||||
},
|
||||
{
|
||||
"username": "Ambrosianus",
|
||||
"email": "Ambrosianus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Ambrosianus"
|
||||
},
|
||||
{
|
||||
"username": "Ambrosius",
|
||||
"email": "Ambrosius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Ambrosius"
|
||||
},
|
||||
{
|
||||
"username": "Amedeus",
|
||||
"email": "Amedeus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amedeus"
|
||||
},
|
||||
{
|
||||
"username": "Americus",
|
||||
"email": "Americus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Americus"
|
||||
},
|
||||
{
|
||||
"username": "Amlethus",
|
||||
"email": "Amlethus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amlethus"
|
||||
},
|
||||
{
|
||||
"username": "Amletus",
|
||||
"email": "Amletus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amletus"
|
||||
},
|
||||
{
|
||||
"username": "Amor",
|
||||
"email": "Amor.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amor"
|
||||
},
|
||||
{
|
||||
"username": "Ampelius",
|
||||
"email": "Ampelius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Ampelius"
|
||||
},
|
||||
{
|
||||
"username": "Amphion",
|
||||
"email": "Amphion.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amphion"
|
||||
},
|
||||
{
|
||||
"username": "Anacletus",
|
||||
"email": "Anacletus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Anacletus"
|
||||
},
|
||||
{
|
||||
"username": "Anastasius",
|
||||
"email": "Anastasius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Anastasius"
|
||||
},
|
||||
{
|
||||
"username": "Anastatius",
|
||||
"email": "Anastatius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Anastatius"
|
||||
},
|
||||
{
|
||||
"username": "Anastius",
|
||||
"email": "Anastius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Anastius"
|
||||
},
|
||||
{
|
||||
"username": "Anatolius",
|
||||
"email": "Anatolius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Anatolius"
|
||||
},
|
||||
{
|
||||
"username": "Androcles",
|
||||
"email": "Androcles.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Androcles"
|
||||
},
|
||||
{
|
||||
"username": "Andronicus",
|
||||
"email": "Andronicus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Andronicus"
|
||||
},
|
||||
{
|
||||
"username": "Anencletus",
|
||||
"email": "Anencletus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Anencletus"
|
||||
},
|
||||
{
|
||||
"username": "Angelicus",
|
||||
"email": "Angelicus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Angelicus"
|
||||
},
|
||||
{
|
||||
"username": "Angelus",
|
||||
"email": "Angelus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Angelus"
|
||||
},
|
||||
{
|
||||
"username": "Anicetus",
|
||||
"email": "Anicetus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Anicetus"
|
||||
},
|
||||
{
|
||||
"username": "Antigonus",
|
||||
"email": "Antigonus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Antigonus"
|
||||
},
|
||||
{
|
||||
"username": "Antipater",
|
||||
"email": "Antipater.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Antipater"
|
||||
},
|
||||
{
|
||||
"username": "Antoninus",
|
||||
"email": "Antoninus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Antoninus"
|
||||
},
|
||||
{
|
||||
"username": "Antonius",
|
||||
"email": "Antonius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Antonius"
|
||||
},
|
||||
{
|
||||
"username": "Aphrodisius",
|
||||
"email": "Aphrodisius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Aphrodisius"
|
||||
},
|
||||
{
|
||||
"username": "Apollinaris",
|
||||
"email": "Apollinaris.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Apollinaris"
|
||||
}
|
||||
]
|
17
gestioncof/migrations/0009_delete_clipper.py
Normal file
17
gestioncof/migrations/0009_delete_clipper.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0008_py3'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='Clipper',
|
||||
),
|
||||
]
|
|
@ -6,7 +6,7 @@ from django.db import migrations
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0008_py3'),
|
||||
('gestioncof', '0009_delete_clipper'),
|
||||
]
|
||||
|
||||
operations = [
|
|
@ -1,9 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
from django.contrib.auth.models import User
|
||||
|
@ -248,15 +244,6 @@ class SurveyAnswer(models.Model):
|
|||
self.survey.title)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Clipper(models.Model):
|
||||
username = models.CharField("Identifiant", max_length=20)
|
||||
fullname = models.CharField("Nom complet", max_length=200)
|
||||
|
||||
def __str__(self):
|
||||
return "Clipper %s" % self.username
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CalendarSubscription(models.Model):
|
||||
token = models.UUIDField()
|
||||
|
|
54
gestioncof/petits_cours_forms.py
Normal file
54
gestioncof/petits_cours_forms.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from captcha.fields import ReCaptchaField
|
||||
|
||||
from django import forms
|
||||
from django.forms import ModelForm
|
||||
from django.forms.models import inlineformset_factory, BaseInlineFormSet
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from gestioncof.petits_cours_models import PetitCoursDemande, PetitCoursAbility
|
||||
|
||||
|
||||
class BaseMatieresFormSet(BaseInlineFormSet):
|
||||
def clean(self):
|
||||
super(BaseMatieresFormSet, self).clean()
|
||||
if any(self.errors):
|
||||
# Don't bother validating the formset unless each form is
|
||||
# valid on its own
|
||||
return
|
||||
matieres = []
|
||||
for i in range(0, self.total_form_count()):
|
||||
form = self.forms[i]
|
||||
if not form.cleaned_data:
|
||||
continue
|
||||
matiere = form.cleaned_data['matiere']
|
||||
niveau = form.cleaned_data['niveau']
|
||||
delete = form.cleaned_data['DELETE']
|
||||
if not delete and (matiere, niveau) in matieres:
|
||||
raise forms.ValidationError(
|
||||
"Vous ne pouvez pas vous inscrire deux fois pour la "
|
||||
"même matiere avec le même niveau.")
|
||||
matieres.append((matiere, niveau))
|
||||
|
||||
|
||||
class DemandeForm(ModelForm):
|
||||
captcha = ReCaptchaField(attrs={'theme': 'clean', 'lang': 'fr'})
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DemandeForm, self).__init__(*args, **kwargs)
|
||||
self.fields['matieres'].help_text = ''
|
||||
|
||||
class Meta:
|
||||
model = PetitCoursDemande
|
||||
fields = ('name', 'email', 'phone', 'quand', 'freq', 'lieu',
|
||||
'matieres', 'agrege_requis', 'niveau', 'remarques')
|
||||
widgets = {'matieres': forms.CheckboxSelectMultiple}
|
||||
|
||||
|
||||
MatieresFormSet = inlineformset_factory(
|
||||
User,
|
||||
PetitCoursAbility,
|
||||
fields=("matiere", "niveau", "agrege"),
|
||||
formset=BaseMatieresFormSet
|
||||
)
|
|
@ -1,14 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from functools import reduce
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Min
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.six.moves import reduce
|
||||
|
||||
|
||||
def choices_length(choices):
|
||||
|
@ -24,7 +21,6 @@ LEVELS_CHOICES = (
|
|||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class PetitCoursSubject(models.Model):
|
||||
name = models.CharField(_("Matière"), max_length=30)
|
||||
users = models.ManyToManyField(User, related_name="petits_cours_matieres",
|
||||
|
@ -38,7 +34,6 @@ class PetitCoursSubject(models.Model):
|
|||
return self.name
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class PetitCoursAbility(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matière"))
|
||||
|
@ -52,11 +47,11 @@ class PetitCoursAbility(models.Model):
|
|||
verbose_name_plural = "Compétences des petits cours"
|
||||
|
||||
def __str__(self):
|
||||
return "%s - %s - %s" % (self.user.username,
|
||||
self.matiere, self.niveau)
|
||||
return "{:s} - {!s} - {:s}".format(
|
||||
self.user.username, self.matiere, self.niveau
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class PetitCoursDemande(models.Model):
|
||||
name = models.CharField(_("Nom/prénom"), max_length=200)
|
||||
email = models.CharField(_("Adresse email"), max_length=300)
|
||||
|
@ -70,7 +65,7 @@ class PetitCoursDemande(models.Model):
|
|||
freq = models.CharField(
|
||||
_("Fréquence"),
|
||||
help_text=_("Indiquez ici la fréquence envisagée "
|
||||
+ "(hebdomadaire, 2 fois par semaine, ...)"),
|
||||
"(hebdomadaire, 2 fois par semaine, ...)"),
|
||||
max_length=300, blank=True)
|
||||
lieu = models.CharField(
|
||||
_("Lieu (si préférence)"),
|
||||
|
@ -94,16 +89,42 @@ class PetitCoursDemande(models.Model):
|
|||
blank=True, null=True)
|
||||
created = models.DateTimeField(_("Date de création"), auto_now_add=True)
|
||||
|
||||
def get_candidates(self, redo=False):
|
||||
"""
|
||||
Donne la liste des profs disponibles pour chaque matière de la demande.
|
||||
- On ne donne que les agrégés si c'est demandé
|
||||
- Si ``redo`` vaut ``True``, cela signifie qu'on retraite la demande et
|
||||
il ne faut pas proposer à nouveau des noms qui ont déjà été proposés
|
||||
"""
|
||||
for matiere in self.matieres.all():
|
||||
candidates = PetitCoursAbility.objects.filter(
|
||||
matiere=matiere,
|
||||
niveau=self.niveau,
|
||||
user__profile__is_cof=True,
|
||||
user__profile__petits_cours_accept=True
|
||||
)
|
||||
if self.agrege_requis:
|
||||
candidates = candidates.filter(agrege=True)
|
||||
if redo:
|
||||
attrs = self.petitcoursattribution_set.filter(matiere=matiere)
|
||||
already_proposed = [
|
||||
attr.user
|
||||
for attr in attrs
|
||||
]
|
||||
candidates = candidates.exclude(user__in=already_proposed)
|
||||
candidates = candidates.order_by('?').select_related().all()
|
||||
yield (matiere, candidates)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Demande de petits cours"
|
||||
verbose_name_plural = "Demandes de petits cours"
|
||||
|
||||
def __str__(self):
|
||||
return "Demande %d du %s" % (self.id,
|
||||
self.created.strftime("%d %b %Y"))
|
||||
return "Demande {:d} du {:s}".format(
|
||||
self.id, self.created.strftime("%d %b %Y")
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class PetitCoursAttribution(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
demande = models.ForeignKey(PetitCoursDemande, verbose_name=_("Demande"))
|
||||
|
@ -118,20 +139,40 @@ class PetitCoursAttribution(models.Model):
|
|||
verbose_name_plural = "Attributions de petits cours"
|
||||
|
||||
def __str__(self):
|
||||
return "Attribution de la demande %d à %s pour %s" \
|
||||
% (self.demande.id, self.user.username, self.matiere)
|
||||
return "Attribution de la demande {:d} à {:s} pour {!s}".format(
|
||||
self.demande.id, self.user.username, self.matiere
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class PetitCoursAttributionCounter(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matiere"))
|
||||
count = models.IntegerField("Nombre d'envois", default=0)
|
||||
|
||||
@classmethod
|
||||
def get_uptodate(cls, user, matiere):
|
||||
"""
|
||||
Donne le compteur de l'utilisateur pour cette matière. Si le compteur
|
||||
n'existe pas encore, il est initialisé avec le minimum des valeurs des
|
||||
compteurs de tout le monde.
|
||||
"""
|
||||
counter, created = cls.objects.get_or_create(
|
||||
user=user, matiere=matiere)
|
||||
if created:
|
||||
mincount = (
|
||||
cls.objects.filter(matiere=matiere).exclude(user=user)
|
||||
.aggregate(Min('count'))
|
||||
['count__min']
|
||||
)
|
||||
counter.count = mincount
|
||||
counter.save()
|
||||
return counter
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Compteur d'attribution de petits cours"
|
||||
verbose_name_plural = "Compteurs d'attributions de petits cours"
|
||||
|
||||
def __str__(self):
|
||||
return "%d demandes envoyées à %s pour %s" \
|
||||
% (self.count, self.user.username, self.matiere)
|
||||
return "{:d} demandes envoyées à {:s} pour {!s}".format(
|
||||
self.count, self.user.username, self.matiere
|
||||
)
|
||||
|
|
|
@ -1,28 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import base64
|
||||
import json
|
||||
from datetime import datetime
|
||||
from captcha.fields import ReCaptchaField
|
||||
from custommail.shortcuts import render_custom_mail
|
||||
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.core import mail
|
||||
from django.forms import ModelForm
|
||||
from django import forms
|
||||
from django.forms.models import inlineformset_factory, BaseInlineFormSet
|
||||
from django.contrib.auth.models import User
|
||||
from django.views.generic import ListView
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.generic import ListView, DetailView
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Min
|
||||
|
||||
from gestioncof.models import CofProfile
|
||||
from gestioncof.petits_cours_models import PetitCoursDemande, \
|
||||
PetitCoursAttribution, PetitCoursAttributionCounter, PetitCoursAbility, \
|
||||
PetitCoursSubject
|
||||
from gestioncof.petits_cours_models import (
|
||||
PetitCoursDemande, PetitCoursAttribution, PetitCoursAttributionCounter,
|
||||
PetitCoursAbility, PetitCoursSubject
|
||||
)
|
||||
from gestioncof.petits_cours_forms import DemandeForm, MatieresFormSet
|
||||
from gestioncof.decorators import buro_required
|
||||
from gestioncof.shared import lock_table, unlock_tables
|
||||
|
||||
|
@ -35,47 +30,17 @@ class DemandeListView(ListView):
|
|||
def get_queryset(self):
|
||||
return PetitCoursDemande.objects.order_by('traitee', '-id').all()
|
||||
|
||||
@method_decorator(buro_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(DemandeListView, self).dispatch(*args, **kwargs)
|
||||
|
||||
class DemandeDetailView(DetailView):
|
||||
model = PetitCoursDemande
|
||||
template_name = "gestioncof/details_demande_petit_cours.html"
|
||||
context_object_name = "demande"
|
||||
|
||||
@buro_required
|
||||
def details(request, demande_id):
|
||||
demande = get_object_or_404(PetitCoursDemande, id=demande_id)
|
||||
attributions = PetitCoursAttribution.objects.filter(demande=demande).all()
|
||||
return render(request, "details_demande_petit_cours.html",
|
||||
{"demande": demande,
|
||||
"attributions": attributions})
|
||||
|
||||
|
||||
def _get_attrib_counter(user, matiere):
|
||||
counter, created = PetitCoursAttributionCounter \
|
||||
.objects.get_or_create(user=user, matiere=matiere)
|
||||
if created:
|
||||
mincount = PetitCoursAttributionCounter.objects \
|
||||
.filter(matiere=matiere).exclude(user=user).all() \
|
||||
.aggregate(Min('count'))
|
||||
counter.count = mincount['count__min']
|
||||
counter.save()
|
||||
return counter
|
||||
|
||||
|
||||
def _get_demande_candidates(demande, redo=False):
|
||||
for matiere in demande.matieres.all():
|
||||
candidates = PetitCoursAbility.objects.filter(matiere=matiere,
|
||||
niveau=demande.niveau)
|
||||
candidates = candidates.filter(user__profile__is_cof=True,
|
||||
user__profile__petits_cours_accept=True)
|
||||
if demande.agrege_requis:
|
||||
candidates = candidates.filter(agrege=True)
|
||||
if redo:
|
||||
attributions = PetitCoursAttribution.objects \
|
||||
.filter(demande=demande, matiere=matiere).all()
|
||||
for attrib in attributions:
|
||||
candidates = candidates.exclude(user=attrib.user)
|
||||
candidates = candidates.order_by('?').select_related().all()
|
||||
yield (matiere, candidates)
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DemandeDetailView, self).get_context_data(**kwargs)
|
||||
obj = self.object
|
||||
context['attributions'] = obj.petitcoursattribution_set.all()
|
||||
return context
|
||||
|
||||
|
||||
@buro_required
|
||||
|
@ -89,12 +54,15 @@ def traitement(request, demande_id, redo=False):
|
|||
proposed_for = {}
|
||||
unsatisfied = []
|
||||
attribdata = {}
|
||||
for matiere, candidates in _get_demande_candidates(demande, redo):
|
||||
for matiere, candidates in demande.get_candidates(redo):
|
||||
if candidates:
|
||||
tuples = []
|
||||
for candidate in candidates:
|
||||
user = candidate.user
|
||||
tuples.append((candidate, _get_attrib_counter(user, matiere)))
|
||||
tuples.append((
|
||||
candidate,
|
||||
PetitCoursAttributionCounter.get_uptodate(user, matiere)
|
||||
))
|
||||
tuples = sorted(tuples, key=lambda c: c[1].count)
|
||||
candidates, _ = zip(*tuples)
|
||||
candidates = candidates[0:min(3, len(candidates))]
|
||||
|
@ -166,7 +134,7 @@ def _traitement_other_preparing(request, demande):
|
|||
proposed_for = {}
|
||||
attribdata = {}
|
||||
errors = []
|
||||
for matiere, candidates in _get_demande_candidates(demande, redo):
|
||||
for matiere, candidates in demande.get_candidates(redo):
|
||||
if candidates:
|
||||
candidates = dict([(candidate.user.id, candidate.user)
|
||||
for candidate in candidates])
|
||||
|
@ -174,17 +142,19 @@ def _traitement_other_preparing(request, demande):
|
|||
proposals[matiere] = []
|
||||
for choice_id in range(min(3, len(candidates))):
|
||||
choice = int(
|
||||
request.POST["proposal-%d-%d" % (matiere.id, choice_id)])
|
||||
request.POST["proposal-{:d}-{:d}"
|
||||
.format(matiere.id, choice_id)]
|
||||
)
|
||||
if choice == -1:
|
||||
continue
|
||||
if choice not in candidates:
|
||||
errors.append("Choix invalide pour la proposition %d"
|
||||
"en %s" % (choice_id + 1, matiere))
|
||||
errors.append("Choix invalide pour la proposition {:d}"
|
||||
"en {!s}".format(choice_id + 1, matiere))
|
||||
continue
|
||||
user = candidates[choice]
|
||||
if user in proposals[matiere]:
|
||||
errors.append("La proposition %d en %s est un doublon"
|
||||
% (choice_id + 1, matiere))
|
||||
errors.append("La proposition {:d} en {!s} est un doublon"
|
||||
.format(choice_id + 1, matiere))
|
||||
continue
|
||||
proposals[matiere].append(user)
|
||||
attribdata[matiere.id].append(user.id)
|
||||
|
@ -193,12 +163,13 @@ def _traitement_other_preparing(request, demande):
|
|||
else:
|
||||
proposed_for[user].append(matiere)
|
||||
if not proposals[matiere]:
|
||||
errors.append("Aucune proposition pour %s" % (matiere,))
|
||||
errors.append("Aucune proposition pour {!s}".format(matiere))
|
||||
elif len(proposals[matiere]) < 3:
|
||||
errors.append("Seulement %d proposition%s pour %s"
|
||||
% (len(proposals[matiere]),
|
||||
"s" if len(proposals[matiere]) > 1 else "",
|
||||
matiere))
|
||||
errors.append("Seulement {:d} proposition{:s} pour {!s}"
|
||||
.format(
|
||||
len(proposals[matiere]),
|
||||
"s" if len(proposals[matiere]) > 1 else "",
|
||||
matiere))
|
||||
else:
|
||||
unsatisfied.append(matiere)
|
||||
return _finalize_traitement(request, demande, proposals, proposed_for,
|
||||
|
@ -215,12 +186,15 @@ def _traitement_other(request, demande, redo):
|
|||
proposed_for = {}
|
||||
unsatisfied = []
|
||||
attribdata = {}
|
||||
for matiere, candidates in _get_demande_candidates(demande, redo):
|
||||
for matiere, candidates in demande.get_candidates(redo):
|
||||
if candidates:
|
||||
tuples = []
|
||||
for candidate in candidates:
|
||||
user = candidate.user
|
||||
tuples.append((candidate, _get_attrib_counter(user, matiere)))
|
||||
tuples.append((
|
||||
candidate,
|
||||
PetitCoursAttributionCounter.get_uptodate(user, matiere)
|
||||
))
|
||||
tuples = sorted(tuples, key=lambda c: c[1].count)
|
||||
candidates, _ = zip(*tuples)
|
||||
attribdata[matiere.id] = []
|
||||
|
@ -309,37 +283,11 @@ def _traitement_post(request, demande):
|
|||
})
|
||||
|
||||
|
||||
class BaseMatieresFormSet(BaseInlineFormSet):
|
||||
def clean(self):
|
||||
super(BaseMatieresFormSet, self).clean()
|
||||
if any(self.errors):
|
||||
# Don't bother validating the formset unless each form is
|
||||
# valid on its own
|
||||
return
|
||||
matieres = []
|
||||
for i in range(0, self.total_form_count()):
|
||||
form = self.forms[i]
|
||||
if not form.cleaned_data:
|
||||
continue
|
||||
matiere = form.cleaned_data['matiere']
|
||||
niveau = form.cleaned_data['niveau']
|
||||
delete = form.cleaned_data['DELETE']
|
||||
if not delete and (matiere, niveau) in matieres:
|
||||
raise forms.ValidationError(
|
||||
"Vous ne pouvez pas vous inscrire deux fois pour la "
|
||||
"même matiere avec le même niveau.")
|
||||
matieres.append((matiere, niveau))
|
||||
|
||||
|
||||
@login_required
|
||||
def inscription(request):
|
||||
profile, created = CofProfile.objects.get_or_create(user=request.user)
|
||||
if not profile.is_cof:
|
||||
return redirect("cof-denied")
|
||||
MatieresFormSet = inlineformset_factory(User, PetitCoursAbility,
|
||||
fields=("matiere", "niveau",
|
||||
"agrege",),
|
||||
formset=BaseMatieresFormSet)
|
||||
success = False
|
||||
if request.method == "POST":
|
||||
formset = MatieresFormSet(request.POST, instance=request.user)
|
||||
|
@ -350,10 +298,14 @@ def inscription(request):
|
|||
profile.save()
|
||||
lock_table(PetitCoursAttributionCounter, PetitCoursAbility, User,
|
||||
PetitCoursSubject)
|
||||
abilities = PetitCoursAbility.objects \
|
||||
.filter(user=request.user).all()
|
||||
abilities = (
|
||||
PetitCoursAbility.objects.filter(user=request.user).all()
|
||||
)
|
||||
for ability in abilities:
|
||||
_get_attrib_counter(ability.user, ability.matiere)
|
||||
PetitCoursAttributionCounter.get_uptodate(
|
||||
ability.user,
|
||||
ability.matiere
|
||||
)
|
||||
unlock_tables()
|
||||
success = True
|
||||
formset = MatieresFormSet(instance=request.user)
|
||||
|
@ -365,20 +317,6 @@ def inscription(request):
|
|||
"remarques": profile.petits_cours_remarques})
|
||||
|
||||
|
||||
class DemandeForm(ModelForm):
|
||||
captcha = ReCaptchaField(attrs={'theme': 'clean', 'lang': 'fr'})
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DemandeForm, self).__init__(*args, **kwargs)
|
||||
self.fields['matieres'].help_text = ''
|
||||
|
||||
class Meta:
|
||||
model = PetitCoursDemande
|
||||
fields = ('name', 'email', 'phone', 'quand', 'freq', 'lieu',
|
||||
'matieres', 'agrege_requis', 'niveau', 'remarques')
|
||||
widgets = {'matieres': forms.CheckboxSelectMultiple}
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def demande(request):
|
||||
success = False
|
||||
|
|
|
@ -1088,3 +1088,8 @@ tr.awesome{
|
|||
color: white;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.petitcours-raw {
|
||||
padding:20px;
|
||||
background:#fff;
|
||||
}
|
||||
|
|
7
gestioncof/static/css/jquery-ui.min.css
vendored
Normal file
7
gestioncof/static/css/jquery-ui.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
13
gestioncof/static/js/jquery-ui.min.js
vendored
Normal file
13
gestioncof/static/js/jquery-ui.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11
gestioncof/static/js/jquery.ui.touch-punch.min.js
vendored
Normal file
11
gestioncof/static/js/jquery.ui.touch-punch.min.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*!
|
||||
* jQuery UI Touch Punch 0.2.3
|
||||
*
|
||||
* Copyright 2011–2014, Dave Furfero
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
*
|
||||
* Depends:
|
||||
* jquery.ui.widget.js
|
||||
* jquery.ui.mouse.js
|
||||
*/
|
||||
!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);
|
|
@ -15,7 +15,7 @@
|
|||
{% if clippers %}
|
||||
<li class="autocomplete-header">Utilisateurs <tt>clipper</tt></li>
|
||||
{% for clipper in clippers %}{% if forloop.counter < 5 %}
|
||||
<li class="autocomplete-value"><a href="{% url 'clipper-registration' clipper.username %}">{{ clipper|highlight_clipper:q }}</a></li>
|
||||
<li class="autocomplete-value"><a href="{% url 'clipper-registration' clipper.clipper clipper.fullname %}">{{ clipper|highlight_clipper:q }}</a></li>
|
||||
{% elif forloop.counter == 5 %}<li class="autocomplete-more">...</a>{% endif %}{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block content %}
|
||||
<div class="petitcours-raw">
|
||||
{% if success %}
|
||||
<p class="success">Votre demande a été enregistrée avec succès !</p>
|
||||
{% else %}
|
||||
<form id="demandecours" method="post" action="{% url "gestioncof.petits_cours_views.demande_raw" %}">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
{{ form | bootstrap }}
|
||||
</table>
|
||||
<input type="submit" class="btn-submit" value="Enregistrer" />
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block realcontent %}
|
||||
|
||||
|
@ -36,8 +37,21 @@ souscrire aux événements du COF et/ou aux spectacles BdA.
|
|||
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="Enregistrer" class="btn btn-primary" />
|
||||
{{ form | bootstrap }}
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" onClick="select(true)">Tout sélectionner</button>
|
||||
<button type="button" class="btn btn-default" onClick="select(false)">Tout désélectionner</button>
|
||||
</p>
|
||||
|
||||
<input type="submit" value="Enregistrer" class="btn btn-primary center-block" />
|
||||
</form>
|
||||
|
||||
<script language="JavaScript">
|
||||
function select(check) {
|
||||
checkboxes = document.getElementsByName("other_shows");
|
||||
for(var i=0, n=checkboxes.length;i<n;i++) {
|
||||
checkboxes[i].checked = check;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -17,4 +17,8 @@
|
|||
<li><a href="{% url 'gestioncof.views.export_mega_orgas' %}">Export des orgas uniquement</a></li>
|
||||
<li><a href="{% url 'gestioncof.views.export_mega' %}">Export de tout le monde</a></li>
|
||||
</ul>
|
||||
|
||||
<p>Note : pour ouvrir les fichiers .csv avec Excel, il faut
|
||||
passer par <tt>Fichier > Importer</tt> et sélectionner la
|
||||
virgule comme séparateur.</p>
|
||||
{% endblock %}
|
|
@ -2,11 +2,11 @@
|
|||
{% load staticfiles %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link href="{% static "grappelli/jquery/ui/css/custom-theme/jquery-ui-1.8.custom.css" %}" rel="stylesheet" type="text/css" media="screen" title="no title" charset="utf-8" />
|
||||
<script src="{% static "grappelli/jquery/jquery-1.6.2.min.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "grappelli/jquery/ui/js/jquery-ui-1.8.15.custom.min.js" %}" type="text/javascript"></script>
|
||||
<link href="{% static "grappelli/css/tools.css" %}" rel="stylesheet" type="text/css" />
|
||||
<link href="{% static "grappelli/css/jquery-ui-grappelli-extensions.css" %}" rel="stylesheet" type="text/css" />
|
||||
<script src="{% static "js/jquery.min.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "js/jquery-ui.min.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "js/jquery.ui.touch-punch.min.js" %}" type="text/javascript"></script>
|
||||
<link type="text/css" rel="stylesheet" href="{% static "css/jquery-ui.min.css" %}" />
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block realcontent %}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
$(document).ready(function() {
|
||||
$('input#search_autocomplete').yourlabsAutocomplete({
|
||||
url: '{% url 'gestioncof.autocomplete.autocomplete' %}',
|
||||
minimumCharacters: 1,
|
||||
minimumCharacters: 3,
|
||||
id: 'search_autocomplete',
|
||||
choiceSelector: 'li:has(a)',
|
||||
placeholder: "Chercher un utilisateur par nom, prénom ou identifiant clipper",
|
||||
|
|
|
@ -43,7 +43,7 @@ def highlight_user(user, q):
|
|||
@register.filter
|
||||
def highlight_clipper(clipper, q):
|
||||
if clipper.fullname:
|
||||
text = "%s (<tt>%s</tt>)" % (clipper.fullname, clipper.username)
|
||||
text = "%s (<tt>%s</tt>)" % (clipper.fullname, clipper.clipper)
|
||||
else:
|
||||
text = clipper.username
|
||||
text = clipper.clipper
|
||||
return highlight_text(text, q)
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import url
|
||||
from gestioncof.petits_cours_views import DemandeListView
|
||||
from gestioncof.petits_cours_views import DemandeListView, DemandeDetailView
|
||||
from gestioncof import views, petits_cours_views
|
||||
from gestioncof.decorators import buro_required
|
||||
|
||||
export_patterns = [
|
||||
url(r'^members$', views.export_members),
|
||||
|
@ -24,10 +21,11 @@ petitcours_patterns = [
|
|||
name='petits-cours-demande'),
|
||||
url(r'^demande-raw$', petits_cours_views.demande_raw,
|
||||
name='petits-cours-demande-raw'),
|
||||
url(r'^demandes$', DemandeListView.as_view(),
|
||||
url(r'^demandes$',
|
||||
buro_required(DemandeListView.as_view()),
|
||||
name='petits-cours-demandes-list'),
|
||||
url(r'^demandes/(?P<demande_id>\d+)$',
|
||||
petits_cours_views.details,
|
||||
url(r'^demandes/(?P<pk>\d+)$',
|
||||
buro_required(DemandeDetailView.as_view()),
|
||||
name='petits-cours-demande-details'),
|
||||
url(r'^demandes/(?P<demande_id>\d+)/traitement$',
|
||||
petits_cours_views.traitement,
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unicodecsv
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
|
@ -25,7 +21,7 @@ from gestioncof.models import Event, EventRegistration, EventOption, \
|
|||
EventOptionChoice
|
||||
from gestioncof.models import EventCommentField, EventCommentValue, \
|
||||
CalendarSubscription
|
||||
from gestioncof.models import CofProfile, Clipper, Club
|
||||
from gestioncof.models import CofProfile, Club
|
||||
from gestioncof.decorators import buro_required, cof_required
|
||||
from gestioncof.forms import UserProfileForm, EventStatusFilterForm, \
|
||||
SurveyForm, SurveyStatusFilterForm, RegistrationUserForm, \
|
||||
|
@ -321,11 +317,11 @@ def registration_set_ro_fields(user_form, profile_form):
|
|||
|
||||
|
||||
@buro_required
|
||||
def registration_form2(request, login_clipper=None, username=None):
|
||||
def registration_form2(request, login_clipper=None, username=None,
|
||||
fullname=None):
|
||||
events = Event.objects.filter(old=False).all()
|
||||
member = None
|
||||
if login_clipper:
|
||||
clipper = get_object_or_404(Clipper, username=login_clipper)
|
||||
try: # check if the given user is already registered
|
||||
member = User.objects.get(username=login_clipper)
|
||||
username = member.username
|
||||
|
@ -336,8 +332,8 @@ def registration_form2(request, login_clipper=None, username=None):
|
|||
user_form = RegistrationUserForm(initial={
|
||||
'username': login_clipper,
|
||||
'email': "%s@clipper.ens.fr" % login_clipper})
|
||||
if clipper.fullname:
|
||||
bits = clipper.fullname.split(" ")
|
||||
if fullname:
|
||||
bits = fullname.split(" ")
|
||||
user_form.fields['first_name'].initial = bits[0]
|
||||
if len(bits) > 1:
|
||||
user_form.fields['last_name'].initial = " ".join(bits[1:])
|
||||
|
@ -412,12 +408,12 @@ def registration(request):
|
|||
try:
|
||||
member = User.objects.get(username=username)
|
||||
user_form = RegistrationUserForm(request_dict, instance=member)
|
||||
except User.DoesNotExist:
|
||||
try:
|
||||
clipper = Clipper.objects.get(username=username)
|
||||
login_clipper = clipper.username
|
||||
except Clipper.DoesNotExist:
|
||||
if member.profile.login_clipper:
|
||||
login_clipper = member.profile.login_clipper
|
||||
else:
|
||||
user_form.force_long_username()
|
||||
except User.DoesNotExist:
|
||||
user_form.force_long_username()
|
||||
else:
|
||||
user_form.force_long_username()
|
||||
|
||||
|
@ -644,7 +640,7 @@ def export_mega(request):
|
|||
|
||||
@buro_required
|
||||
def utile_cof(request):
|
||||
return render(request, "utile_cof.html", {})
|
||||
return render(request, "gestioncof/utile_cof.html", {})
|
||||
|
||||
|
||||
@buro_required
|
||||
|
@ -693,15 +689,15 @@ def calendar(request):
|
|||
subscription.token = uuid.uuid4()
|
||||
subscription.save()
|
||||
form.save_m2m()
|
||||
return render(request, "calendar_subscription.html",
|
||||
return render(request, "gestioncof/calendar_subscription.html",
|
||||
{'form': form,
|
||||
'success': True,
|
||||
'token': str(subscription.token)})
|
||||
else:
|
||||
return render(request, "calendar_subscription.html",
|
||||
return render(request, "gestioncof/calendar_subscription.html",
|
||||
{'form': form, 'error': "Formulaire incorrect"})
|
||||
else:
|
||||
return render(request, "calendar_subscription.html",
|
||||
return render(request, "gestioncof/calendar_subscription.html",
|
||||
{'form': CalendarForm(instance=instance),
|
||||
'token': instance.token if instance else None})
|
||||
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (absolute_import, division,
|
||||
print_function, unicode_literals)
|
||||
from builtins import *
|
||||
import ldap3
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.http import Http404
|
||||
from django.db.models import Q
|
||||
from gestioncof.models import User, Clipper
|
||||
from django.conf import settings
|
||||
|
||||
from gestioncof.models import User
|
||||
from kfet.decorators import teamkfet_required
|
||||
from kfet.models import Account
|
||||
|
||||
|
||||
class Clipper(object):
|
||||
def __init__(self, clipper, fullname):
|
||||
self.clipper = clipper
|
||||
self.fullname = fullname
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def account_create(request):
|
||||
if "q" not in request.GET:
|
||||
|
@ -25,58 +32,67 @@ def account_create(request):
|
|||
queries = {}
|
||||
search_words = q.split()
|
||||
|
||||
# Fetching data from User, CofProfile and Account tables
|
||||
queries['kfet'] = Account.objects
|
||||
queries['users_cof'] = User.objects.filter(Q(profile__is_cof = True))
|
||||
queries['users_notcof'] = User.objects.filter(Q(profile__is_cof = False))
|
||||
queries['clippers'] = Clipper.objects
|
||||
queries['users_cof'] = User.objects.filter(profile__is_cof = True)
|
||||
queries['users_notcof'] = User.objects.filter(profile__is_cof = False)
|
||||
|
||||
for word in search_words:
|
||||
queries['kfet'] = queries['kfet'].filter(
|
||||
Q(cofprofile__user__username__icontains = word)
|
||||
| Q(cofprofile__user__first_name__icontains = word)
|
||||
| Q(cofprofile__user__last_name__icontains = word)
|
||||
)
|
||||
Q(cofprofile__user__username__icontains = word)
|
||||
| Q(cofprofile__user__first_name__icontains = word)
|
||||
| Q(cofprofile__user__last_name__icontains = word)
|
||||
)
|
||||
queries['users_cof'] = queries['users_cof'].filter(
|
||||
Q(username__icontains = word)
|
||||
| Q(first_name__icontains = word)
|
||||
| Q(last_name__icontains = word)
|
||||
)
|
||||
Q(username__icontains = word)
|
||||
| Q(first_name__icontains = word)
|
||||
| Q(last_name__icontains = word)
|
||||
)
|
||||
queries['users_notcof'] = queries['users_notcof'].filter(
|
||||
Q(username__icontains = word)
|
||||
| Q(first_name__icontains = word)
|
||||
| Q(last_name__icontains = word)
|
||||
)
|
||||
queries['clippers'] = queries['clippers'].filter(
|
||||
Q(username__icontains = word)
|
||||
| Q(fullname__icontains = word)
|
||||
)
|
||||
Q(username__icontains = word)
|
||||
| Q(first_name__icontains = word)
|
||||
| Q(last_name__icontains = word)
|
||||
)
|
||||
|
||||
# Clearing redundancies
|
||||
queries['kfet'] = queries['kfet'].distinct()
|
||||
|
||||
usernames = list( \
|
||||
usernames = set(
|
||||
queries['kfet'].values_list('cofprofile__user__username', flat=True))
|
||||
queries['kfet'] = [
|
||||
(account, account.cofprofile.user)
|
||||
for account in queries['kfet']
|
||||
]
|
||||
|
||||
queries['kfet'] = [ (account, account.cofprofile.user) \
|
||||
for account in queries['kfet'] ]
|
||||
|
||||
queries['users_cof'] = \
|
||||
queries['users_cof'] = \
|
||||
queries['users_cof'].exclude(username__in=usernames).distinct()
|
||||
queries['users_notcof'] = \
|
||||
queries['users_notcof'] = \
|
||||
queries['users_notcof'].exclude(username__in=usernames).distinct()
|
||||
|
||||
usernames += list( \
|
||||
usernames |= set(
|
||||
queries['users_cof'].values_list('username', flat=True))
|
||||
usernames += list( \
|
||||
usernames |= set(
|
||||
queries['users_notcof'].values_list('username', flat=True))
|
||||
|
||||
queries['clippers'] = \
|
||||
queries['clippers'].exclude(username__in=usernames).distinct()
|
||||
# Fetching data from the SPI
|
||||
if hasattr(settings, 'LDAP_SERVER_URL'):
|
||||
# Fetching
|
||||
ldap_query = '(|{:s})'.format(''.join(
|
||||
['(cn=*{bit:s}*)(uid=*{bit:s}*)'.format(**{"bit": bit}) for bit in bits]
|
||||
))
|
||||
with Connection(settings.LDAP_SERVER_URL) as conn:
|
||||
conn.search(
|
||||
'dc=spi,dc=ens,dc=fr', ldap_query,
|
||||
attributes=['uid', 'cn']
|
||||
)
|
||||
queries['clippers'] = conn.entries
|
||||
# Clearing redundancies
|
||||
queries['clippers'] = [
|
||||
Clipper(clipper.uid, clipper.cn)
|
||||
for clipper in queries['clippers']
|
||||
if str(clipper.uid) not in usernames
|
||||
]
|
||||
|
||||
# Resulting data
|
||||
data.update(queries)
|
||||
|
||||
options = 0
|
||||
for query in queries.values():
|
||||
options += len(query)
|
||||
data['options'] = options
|
||||
data['options'] = sum([len(query) for query in queries])
|
||||
|
||||
return render(request, "kfet/account_create_autocomplete.html", data)
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
<li class="user_category"><span class="text">Utilisateurs clipper</span></li>
|
||||
{% for clipper in clippers %}
|
||||
<li>
|
||||
<a href="{% url "kfet.account.create.fromclipper" clipper.username %}">
|
||||
<a href="{% url "kfet.account.create.fromclipper" clipper.clipper clipper.fullname%}">
|
||||
{{ clipper|highlight_clipper:q }}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -35,7 +35,8 @@ urlpatterns = [
|
|||
name = 'kfet.account.create_special'),
|
||||
url(r'^accounts/new/user/(?P<username>.+)$', views.account_create_ajax,
|
||||
name = 'kfet.account.create.fromuser'),
|
||||
url(r'^accounts/new/clipper/(?P<login_clipper>.+)$', views.account_create_ajax,
|
||||
url(r'^accounts/new/clipper/(?P<login_clipper>[\w-]+)/(?P<fullname>.*)$',
|
||||
views.account_create_ajax,
|
||||
name = 'kfet.account.create.fromclipper'),
|
||||
url(r'^accounts/new/empty$', views.account_create_ajax,
|
||||
name = 'kfet.account.create.empty'),
|
||||
|
|
|
@ -22,7 +22,7 @@ from django.db.models import F, Sum, Prefetch, Count, Func
|
|||
from django.db.models.functions import Coalesce
|
||||
from django.utils import timezone
|
||||
from django.utils.crypto import get_random_string
|
||||
from gestioncof.models import CofProfile, Clipper
|
||||
from gestioncof.models import CofProfile
|
||||
from kfet.decorators import teamkfet_required
|
||||
from kfet.models import (Account, Checkout, Article, Settings, AccountNegative,
|
||||
CheckoutStatement, GenericTeamToken, Supplier, SupplierArticle, Inventory,
|
||||
|
@ -222,19 +222,20 @@ def account_form_set_readonly_fields(user_form, cof_form):
|
|||
cof_form.fields['login_clipper'].widget.attrs['readonly'] = True
|
||||
cof_form.fields['is_cof'].widget.attrs['disabled'] = True
|
||||
|
||||
def get_account_create_forms(request=None, username=None, login_clipper=None):
|
||||
def get_account_create_forms(request=None, username=None, login_clipper=None,
|
||||
fullname=None):
|
||||
user = None
|
||||
clipper = None
|
||||
clipper = False
|
||||
if login_clipper and (login_clipper == username or not username):
|
||||
# à partir d'un clipper
|
||||
# le user associé à ce clipper ne devrait pas encore exister
|
||||
clipper = get_object_or_404(Clipper, username = login_clipper)
|
||||
clipper = True
|
||||
try:
|
||||
# Vérification que clipper ne soit pas déjà dans User
|
||||
user = User.objects.get(username=login_clipper)
|
||||
# Ici, on nous a menti, le user existe déjà
|
||||
username = user.username
|
||||
clipper = None
|
||||
clipper = False
|
||||
except User.DoesNotExist:
|
||||
# Clipper (sans user déjà existant)
|
||||
|
||||
|
@ -242,9 +243,9 @@ def get_account_create_forms(request=None, username=None, login_clipper=None):
|
|||
user_initial = {
|
||||
'username' : login_clipper,
|
||||
'email' : "%s@clipper.ens.fr" % login_clipper}
|
||||
if clipper.fullname:
|
||||
if fullname:
|
||||
# Prefill du nom et prénom
|
||||
names = clipper.fullname.split()
|
||||
names = fullname.split()
|
||||
# Le premier, c'est le prénom
|
||||
user_initial['first_name'] = names[0]
|
||||
if len(names) > 1:
|
||||
|
@ -308,8 +309,11 @@ def get_account_create_forms(request=None, username=None, login_clipper=None):
|
|||
|
||||
@login_required
|
||||
@teamkfet_required
|
||||
def account_create_ajax(request, username=None, login_clipper=None):
|
||||
forms = get_account_create_forms(request=None, username=username, login_clipper=login_clipper)
|
||||
def account_create_ajax(request, username=None, login_clipper=None,
|
||||
fullname=None):
|
||||
forms = get_account_create_forms(
|
||||
request=None, username=username, login_clipper=login_clipper,
|
||||
fullname=fullname)
|
||||
return render(request, "kfet/account_create_form.html", {
|
||||
'account_form' : forms['account_form'],
|
||||
'cof_form' : forms['cof_form'],
|
||||
|
|
|
@ -4,5 +4,6 @@
|
|||
source ~/venv/bin/activate
|
||||
python manage.py migrate
|
||||
python manage.py syncmails
|
||||
python manage.py loaddata users root bda gestion sites
|
||||
python manage.py loaddata gestion sites
|
||||
python manage.py loaddevdata
|
||||
python manage.py collectstatic --noinput
|
||||
|
|
|
@ -17,5 +17,6 @@ asgi-redis==0.14.0
|
|||
statistics==1.0.3.5
|
||||
future==0.15.2
|
||||
django-widget-tweaks==1.4.1
|
||||
git+https://github.com/Aureplop/channels.git#egg=channel
|
||||
git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail
|
||||
ldap3
|
||||
git+https://github.com/Aureplop/channels.git#egg=channels
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cof.settings")
|
||||
|
||||
from gestioncof.models import Clipper
|
||||
current = {}
|
||||
print("[ FETCHING ]")
|
||||
for clipper in Clipper.objects.all():
|
||||
current[clipper.username] = clipper
|
||||
print("[ SYNCING ]")
|
||||
for line in sys.stdin:
|
||||
bits = line.split(":")
|
||||
username = bits[0]
|
||||
fullname = bits[4]
|
||||
if username in current:
|
||||
clipper = current[username]
|
||||
if clipper.fullname != fullname:
|
||||
clipper.fullname = fullname
|
||||
clipper.save()
|
||||
print("Updated", username)
|
||||
else:
|
||||
clipper = Clipper(username=username, fullname=fullname)
|
||||
clipper.save()
|
||||
print("Created", username)
|
||||
print("[ DONE ]")
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/sh
|
||||
ssh cof@sas.eleves.ens.fr ypcat passwd | python sync_clipper.py
|
Loading…
Reference in a new issue