forked from DGNum/gestioCOF
Merge branch 'Kerl/modernize_petitscours'
This commit is contained in:
commit
5136e394d4
6 changed files with 179 additions and 152 deletions
21
README.md
21
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
|
Si vous optez pour une installation manuelle plutôt que d'utiliser Vagrant, il
|
||||||
est fortement conseillé d'utiliser un environnement virtuel pour Python.
|
est fortement conseillé d'utiliser un environnement virtuel pour Python.
|
||||||
|
|
||||||
Il vous faudra installer mercurial, pip, les librairies de développement de
|
Il vous faudra installer pip, les librairies de développement de python, un
|
||||||
python, un client et un serveur MySQL ainsi qu'un serveur redis ; sous Debian et
|
client et un serveur MySQL ainsi qu'un serveur redis ; sous Debian et dérivées
|
||||||
dérivées (Ubuntu, ...) :
|
(Ubuntu, ...) :
|
||||||
|
|
||||||
sudo apt-get install mercurial python-pip python-dev libmysqlclient-dev
|
sudo apt-get install python-pip python-dev libmysqlclient-dev redis-server
|
||||||
redis-server
|
|
||||||
|
|
||||||
Si vous décidez d'utiliser un environnement virtuel Python (virtualenv;
|
Si vous décidez d'utiliser un environnement virtuel Python (virtualenv;
|
||||||
fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF
|
fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF
|
||||||
(le dossier où se trouve ce README), et créez-le maintenant :
|
(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
|
. env/bin/activate
|
||||||
|
|
||||||
dans le même dossier.
|
dans le même dossier.
|
||||||
|
|
||||||
Vous pouvez maintenant installer les dépendances Python depuis les fichiers
|
Vous pouvez maintenant installer les dépendances Python depuis le fichier
|
||||||
`requirements.txt` et `requirements-devel.txt` :
|
`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`.
|
Copiez le fichier `cof/settings_dev.py` dans `cof/settings.py`.
|
||||||
|
|
||||||
|
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import division
|
from functools import reduce
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import Min
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.utils.translation import ugettext_lazy as _
|
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):
|
def choices_length(choices):
|
||||||
|
@ -24,7 +21,6 @@ LEVELS_CHOICES = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class PetitCoursSubject(models.Model):
|
class PetitCoursSubject(models.Model):
|
||||||
name = models.CharField(_("Matière"), max_length=30)
|
name = models.CharField(_("Matière"), max_length=30)
|
||||||
users = models.ManyToManyField(User, related_name="petits_cours_matieres",
|
users = models.ManyToManyField(User, related_name="petits_cours_matieres",
|
||||||
|
@ -38,7 +34,6 @@ class PetitCoursSubject(models.Model):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class PetitCoursAbility(models.Model):
|
class PetitCoursAbility(models.Model):
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User)
|
||||||
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matière"))
|
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matière"))
|
||||||
|
@ -52,11 +47,11 @@ class PetitCoursAbility(models.Model):
|
||||||
verbose_name_plural = "Compétences des petits cours"
|
verbose_name_plural = "Compétences des petits cours"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s - %s - %s" % (self.user.username,
|
return "{:s} - {!s} - {:s}".format(
|
||||||
self.matiere, self.niveau)
|
self.user.username, self.matiere, self.niveau
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class PetitCoursDemande(models.Model):
|
class PetitCoursDemande(models.Model):
|
||||||
name = models.CharField(_("Nom/prénom"), max_length=200)
|
name = models.CharField(_("Nom/prénom"), max_length=200)
|
||||||
email = models.CharField(_("Adresse email"), max_length=300)
|
email = models.CharField(_("Adresse email"), max_length=300)
|
||||||
|
@ -70,7 +65,7 @@ class PetitCoursDemande(models.Model):
|
||||||
freq = models.CharField(
|
freq = models.CharField(
|
||||||
_("Fréquence"),
|
_("Fréquence"),
|
||||||
help_text=_("Indiquez ici la fréquence envisagée "
|
help_text=_("Indiquez ici la fréquence envisagée "
|
||||||
+ "(hebdomadaire, 2 fois par semaine, ...)"),
|
"(hebdomadaire, 2 fois par semaine, ...)"),
|
||||||
max_length=300, blank=True)
|
max_length=300, blank=True)
|
||||||
lieu = models.CharField(
|
lieu = models.CharField(
|
||||||
_("Lieu (si préférence)"),
|
_("Lieu (si préférence)"),
|
||||||
|
@ -94,16 +89,42 @@ class PetitCoursDemande(models.Model):
|
||||||
blank=True, null=True)
|
blank=True, null=True)
|
||||||
created = models.DateTimeField(_("Date de création"), auto_now_add=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:
|
class Meta:
|
||||||
verbose_name = "Demande de petits cours"
|
verbose_name = "Demande de petits cours"
|
||||||
verbose_name_plural = "Demandes de petits cours"
|
verbose_name_plural = "Demandes de petits cours"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Demande %d du %s" % (self.id,
|
return "Demande {:d} du {:s}".format(
|
||||||
self.created.strftime("%d %b %Y"))
|
self.id, self.created.strftime("%d %b %Y")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class PetitCoursAttribution(models.Model):
|
class PetitCoursAttribution(models.Model):
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User)
|
||||||
demande = models.ForeignKey(PetitCoursDemande, verbose_name=_("Demande"))
|
demande = models.ForeignKey(PetitCoursDemande, verbose_name=_("Demande"))
|
||||||
|
@ -118,20 +139,40 @@ class PetitCoursAttribution(models.Model):
|
||||||
verbose_name_plural = "Attributions de petits cours"
|
verbose_name_plural = "Attributions de petits cours"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Attribution de la demande %d à %s pour %s" \
|
return "Attribution de la demande {:d} à {:s} pour {!s}".format(
|
||||||
% (self.demande.id, self.user.username, self.matiere)
|
self.demande.id, self.user.username, self.matiere
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class PetitCoursAttributionCounter(models.Model):
|
class PetitCoursAttributionCounter(models.Model):
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User)
|
||||||
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matiere"))
|
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matiere"))
|
||||||
count = models.IntegerField("Nombre d'envois", default=0)
|
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:
|
class Meta:
|
||||||
verbose_name = "Compteur d'attribution de petits cours"
|
verbose_name = "Compteur d'attribution de petits cours"
|
||||||
verbose_name_plural = "Compteurs d'attributions de petits cours"
|
verbose_name_plural = "Compteurs d'attributions de petits cours"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%d demandes envoyées à %s pour %s" \
|
return "{:d} demandes envoyées à {:s} pour {!s}".format(
|
||||||
% (self.count, self.user.username, self.matiere)
|
self.count, self.user.username, self.matiere
|
||||||
|
)
|
||||||
|
|
|
@ -1,37 +1,27 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import division
|
import json
|
||||||
from __future__ import print_function
|
from datetime import datetime
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.shortcuts import render, get_object_or_404, redirect
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
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.contrib.auth.models import User
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView, DetailView
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.db.models import Min
|
|
||||||
|
|
||||||
from gestioncof.models import CofProfile
|
from gestioncof.models import CofProfile
|
||||||
from gestioncof.petits_cours_models import PetitCoursDemande, \
|
from gestioncof.petits_cours_models import (
|
||||||
PetitCoursAttribution, PetitCoursAttributionCounter, PetitCoursAbility, \
|
PetitCoursDemande, PetitCoursAttribution, PetitCoursAttributionCounter,
|
||||||
PetitCoursSubject
|
PetitCoursAbility, PetitCoursSubject
|
||||||
|
)
|
||||||
|
from gestioncof.petits_cours_forms import DemandeForm, MatieresFormSet
|
||||||
from gestioncof.decorators import buro_required
|
from gestioncof.decorators import buro_required
|
||||||
from gestioncof.shared import lock_table, unlock_tables
|
from gestioncof.shared import lock_table, unlock_tables
|
||||||
|
|
||||||
from captcha.fields import ReCaptchaField
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
import base64
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
class DemandeListView(ListView):
|
class DemandeListView(ListView):
|
||||||
model = PetitCoursDemande
|
model = PetitCoursDemande
|
||||||
|
@ -41,47 +31,17 @@ class DemandeListView(ListView):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return PetitCoursDemande.objects.order_by('traitee', '-id').all()
|
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 get_context_data(self, **kwargs):
|
||||||
def details(request, demande_id):
|
context = super(DemandeDetailView, self).get_context_data(**kwargs)
|
||||||
demande = get_object_or_404(PetitCoursDemande, id=demande_id)
|
obj = self.object
|
||||||
attributions = PetitCoursAttribution.objects.filter(demande=demande).all()
|
context['attributions'] = obj.petitcoursattribution_set.all()
|
||||||
return render(request, "details_demande_petit_cours.html",
|
return context
|
||||||
{"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)
|
|
||||||
|
|
||||||
|
|
||||||
@buro_required
|
@buro_required
|
||||||
|
@ -95,12 +55,15 @@ def traitement(request, demande_id, redo=False):
|
||||||
proposed_for = {}
|
proposed_for = {}
|
||||||
unsatisfied = []
|
unsatisfied = []
|
||||||
attribdata = {}
|
attribdata = {}
|
||||||
for matiere, candidates in _get_demande_candidates(demande, redo):
|
for matiere, candidates in demande.get_candidates(redo):
|
||||||
if candidates:
|
if candidates:
|
||||||
tuples = []
|
tuples = []
|
||||||
for candidate in candidates:
|
for candidate in candidates:
|
||||||
user = candidate.user
|
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)
|
tuples = sorted(tuples, key=lambda c: c[1].count)
|
||||||
candidates, _ = zip(*tuples)
|
candidates, _ = zip(*tuples)
|
||||||
candidates = candidates[0:min(3, len(candidates))]
|
candidates = candidates[0:min(3, len(candidates))]
|
||||||
|
@ -170,7 +133,7 @@ def _traitement_other_preparing(request, demande):
|
||||||
proposed_for = {}
|
proposed_for = {}
|
||||||
attribdata = {}
|
attribdata = {}
|
||||||
errors = []
|
errors = []
|
||||||
for matiere, candidates in _get_demande_candidates(demande, redo):
|
for matiere, candidates in demande.get_candidates(redo):
|
||||||
if candidates:
|
if candidates:
|
||||||
candidates = dict([(candidate.user.id, candidate.user)
|
candidates = dict([(candidate.user.id, candidate.user)
|
||||||
for candidate in candidates])
|
for candidate in candidates])
|
||||||
|
@ -178,17 +141,19 @@ def _traitement_other_preparing(request, demande):
|
||||||
proposals[matiere] = []
|
proposals[matiere] = []
|
||||||
for choice_id in range(min(3, len(candidates))):
|
for choice_id in range(min(3, len(candidates))):
|
||||||
choice = int(
|
choice = int(
|
||||||
request.POST["proposal-%d-%d" % (matiere.id, choice_id)])
|
request.POST["proposal-{:d}-{:d}"
|
||||||
|
.format(matiere.id, choice_id)]
|
||||||
|
)
|
||||||
if choice == -1:
|
if choice == -1:
|
||||||
continue
|
continue
|
||||||
if choice not in candidates:
|
if choice not in candidates:
|
||||||
errors.append("Choix invalide pour la proposition %d"
|
errors.append("Choix invalide pour la proposition {:d}"
|
||||||
"en %s" % (choice_id + 1, matiere))
|
"en {!s}".format(choice_id + 1, matiere))
|
||||||
continue
|
continue
|
||||||
user = candidates[choice]
|
user = candidates[choice]
|
||||||
if user in proposals[matiere]:
|
if user in proposals[matiere]:
|
||||||
errors.append("La proposition %d en %s est un doublon"
|
errors.append("La proposition {:d} en {!s} est un doublon"
|
||||||
% (choice_id + 1, matiere))
|
.format(choice_id + 1, matiere))
|
||||||
continue
|
continue
|
||||||
proposals[matiere].append(user)
|
proposals[matiere].append(user)
|
||||||
attribdata[matiere.id].append(user.id)
|
attribdata[matiere.id].append(user.id)
|
||||||
|
@ -197,12 +162,13 @@ def _traitement_other_preparing(request, demande):
|
||||||
else:
|
else:
|
||||||
proposed_for[user].append(matiere)
|
proposed_for[user].append(matiere)
|
||||||
if not proposals[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:
|
elif len(proposals[matiere]) < 3:
|
||||||
errors.append("Seulement %d proposition%s pour %s"
|
errors.append("Seulement {:d} proposition{:s} pour {!s}"
|
||||||
% (len(proposals[matiere]),
|
.format(
|
||||||
"s" if len(proposals[matiere]) > 1 else "",
|
len(proposals[matiere]),
|
||||||
matiere))
|
"s" if len(proposals[matiere]) > 1 else "",
|
||||||
|
matiere))
|
||||||
else:
|
else:
|
||||||
unsatisfied.append(matiere)
|
unsatisfied.append(matiere)
|
||||||
return _finalize_traitement(request, demande, proposals, proposed_for,
|
return _finalize_traitement(request, demande, proposals, proposed_for,
|
||||||
|
@ -219,12 +185,15 @@ def _traitement_other(request, demande, redo):
|
||||||
proposed_for = {}
|
proposed_for = {}
|
||||||
unsatisfied = []
|
unsatisfied = []
|
||||||
attribdata = {}
|
attribdata = {}
|
||||||
for matiere, candidates in _get_demande_candidates(demande, redo):
|
for matiere, candidates in demande.get_candidates(redo):
|
||||||
if candidates:
|
if candidates:
|
||||||
tuples = []
|
tuples = []
|
||||||
for candidate in candidates:
|
for candidate in candidates:
|
||||||
user = candidate.user
|
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)
|
tuples = sorted(tuples, key=lambda c: c[1].count)
|
||||||
candidates, _ = zip(*tuples)
|
candidates, _ = zip(*tuples)
|
||||||
attribdata[matiere.id] = []
|
attribdata[matiere.id] = []
|
||||||
|
@ -313,37 +282,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
|
@login_required
|
||||||
def inscription(request):
|
def inscription(request):
|
||||||
profile, created = CofProfile.objects.get_or_create(user=request.user)
|
profile, created = CofProfile.objects.get_or_create(user=request.user)
|
||||||
if not profile.is_cof:
|
if not profile.is_cof:
|
||||||
return redirect("cof-denied")
|
return redirect("cof-denied")
|
||||||
MatieresFormSet = inlineformset_factory(User, PetitCoursAbility,
|
|
||||||
fields=("matiere", "niveau",
|
|
||||||
"agrege",),
|
|
||||||
formset=BaseMatieresFormSet)
|
|
||||||
success = False
|
success = False
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
formset = MatieresFormSet(request.POST, instance=request.user)
|
formset = MatieresFormSet(request.POST, instance=request.user)
|
||||||
|
@ -354,10 +297,14 @@ def inscription(request):
|
||||||
profile.save()
|
profile.save()
|
||||||
lock_table(PetitCoursAttributionCounter, PetitCoursAbility, User,
|
lock_table(PetitCoursAttributionCounter, PetitCoursAbility, User,
|
||||||
PetitCoursSubject)
|
PetitCoursSubject)
|
||||||
abilities = PetitCoursAbility.objects \
|
abilities = (
|
||||||
.filter(user=request.user).all()
|
PetitCoursAbility.objects.filter(user=request.user).all()
|
||||||
|
)
|
||||||
for ability in abilities:
|
for ability in abilities:
|
||||||
_get_attrib_counter(ability.user, ability.matiere)
|
PetitCoursAttributionCounter.get_uptodate(
|
||||||
|
ability.user,
|
||||||
|
ability.matiere
|
||||||
|
)
|
||||||
unlock_tables()
|
unlock_tables()
|
||||||
success = True
|
success = True
|
||||||
formset = MatieresFormSet(instance=request.user)
|
formset = MatieresFormSet(instance=request.user)
|
||||||
|
@ -369,20 +316,6 @@ def inscription(request):
|
||||||
"remarques": profile.petits_cours_remarques})
|
"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
|
@csrf_exempt
|
||||||
def demande(request):
|
def demande(request):
|
||||||
success = False
|
success = False
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.conf.urls import url
|
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 import views, petits_cours_views
|
||||||
|
from gestioncof.decorators import buro_required
|
||||||
|
|
||||||
export_patterns = [
|
export_patterns = [
|
||||||
url(r'^members$', views.export_members),
|
url(r'^members$', views.export_members),
|
||||||
|
@ -24,10 +21,11 @@ petitcours_patterns = [
|
||||||
name='petits-cours-demande'),
|
name='petits-cours-demande'),
|
||||||
url(r'^demande-raw$', petits_cours_views.demande_raw,
|
url(r'^demande-raw$', petits_cours_views.demande_raw,
|
||||||
name='petits-cours-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'),
|
name='petits-cours-demandes-list'),
|
||||||
url(r'^demandes/(?P<demande_id>\d+)$',
|
url(r'^demandes/(?P<pk>\d+)$',
|
||||||
petits_cours_views.details,
|
buro_required(DemandeDetailView.as_view()),
|
||||||
name='petits-cours-demande-details'),
|
name='petits-cours-demande-details'),
|
||||||
url(r'^demandes/(?P<demande_id>\d+)/traitement$',
|
url(r'^demandes/(?P<demande_id>\d+)/traitement$',
|
||||||
petits_cours_views.traitement,
|
petits_cours_views.traitement,
|
||||||
|
|
Loading…
Reference in a new issue