forked from DGNum/gestioCOF
Merge branch 'master' into aureplop/site-cof
This commit is contained in:
commit
84c88dfd5e
37 changed files with 1706 additions and 244 deletions
|
@ -20,7 +20,7 @@ from gestioncof.models import (
|
|||
SurveyQuestion,
|
||||
SurveyQuestionAnswer,
|
||||
)
|
||||
from gestioncof.petits_cours_models import (
|
||||
from petitscours.models import (
|
||||
PetitCoursAbility,
|
||||
PetitCoursAttribution,
|
||||
PetitCoursAttributionCounter,
|
||||
|
|
|
@ -1,23 +1,55 @@
|
|||
from django.contrib.auth.decorators import user_passes_test
|
||||
from functools import wraps
|
||||
|
||||
from django.contrib.auth.decorators import login_required, user_passes_test
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.shortcuts import render
|
||||
|
||||
|
||||
def is_cof(user):
|
||||
try:
|
||||
profile = user.profile
|
||||
return profile.is_cof
|
||||
except Exception:
|
||||
return False
|
||||
def cof_required(view_func):
|
||||
"""Décorateur qui vérifie que l'utilisateur est connecté et membre du COF.
|
||||
|
||||
- Si l'utilisteur n'est pas connecté, il est redirigé vers la page de
|
||||
connexion
|
||||
- Si l'utilisateur est connecté mais pas membre du COF, il obtient une
|
||||
page d'erreur lui demandant de s'inscrire au COF
|
||||
"""
|
||||
|
||||
def is_cof(user):
|
||||
try:
|
||||
return user.profile.is_cof
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
@wraps(view_func)
|
||||
def _wrapped_view(request, *args, **kwargs):
|
||||
if is_cof(request.user):
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
return render(request, "cof-denied.html", status=403)
|
||||
|
||||
return login_required(_wrapped_view)
|
||||
|
||||
|
||||
cof_required = user_passes_test(is_cof)
|
||||
def buro_required(view_func):
|
||||
"""Décorateur qui vérifie que l'utilisateur est connecté et membre du burô.
|
||||
|
||||
- Si l'utilisateur n'est pas connecté, il est redirigé vers la page de
|
||||
connexion
|
||||
- Si l'utilisateur est connecté mais pas membre du burô, il obtient une
|
||||
page d'erreur 403 Forbidden
|
||||
"""
|
||||
|
||||
def is_buro(user):
|
||||
try:
|
||||
profile = user.profile
|
||||
return profile.is_buro
|
||||
except Exception:
|
||||
return False
|
||||
def is_buro(user):
|
||||
try:
|
||||
return user.profile.is_buro
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
@wraps(view_func)
|
||||
def _wrapped_view(request, *args, **kwargs):
|
||||
if is_buro(request.user):
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
buro_required = user_passes_test(is_buro)
|
||||
return render(request, "buro-denied.html", status=403)
|
||||
|
||||
return login_required(_wrapped_view)
|
||||
|
|
|
@ -14,7 +14,7 @@ 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 (
|
||||
from petitscours.models import (
|
||||
LEVELS_CHOICES,
|
||||
PetitCoursAbility,
|
||||
PetitCoursAttributionCounter,
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.dispatch import receiver
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from bda.models import Spectacle
|
||||
from gestioncof.petits_cours_models import choices_length
|
||||
from petitscours.models import choices_length
|
||||
|
||||
TYPE_COMMENT_FIELD = (("text", _("Texte long")), ("char", _("Texte court")))
|
||||
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
from captcha.fields import ReCaptchaField
|
||||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
from django.forms import ModelForm
|
||||
from django.forms.models import BaseInlineFormSet, inlineformset_factory
|
||||
|
||||
from gestioncof.petits_cours_models import PetitCoursAbility, PetitCoursDemande
|
||||
|
||||
|
||||
class BaseMatieresFormSet(BaseInlineFormSet):
|
||||
def clean(self):
|
||||
super().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().__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,191 +0,0 @@
|
|||
from functools import reduce
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.db.models import Min
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
def choices_length(choices):
|
||||
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
|
||||
|
||||
|
||||
LEVELS_CHOICES = (
|
||||
("college", _("Collège")),
|
||||
("lycee", _("Lycée")),
|
||||
("prepa1styear", _("Prépa 1ère année / L1")),
|
||||
("prepa2ndyear", _("Prépa 2ème année / L2")),
|
||||
("licence3", _("Licence 3")),
|
||||
("other", _("Autre (préciser dans les commentaires)")),
|
||||
)
|
||||
|
||||
|
||||
class PetitCoursSubject(models.Model):
|
||||
name = models.CharField(_("Matière"), max_length=30)
|
||||
users = models.ManyToManyField(
|
||||
User, related_name="petits_cours_matieres", through="PetitCoursAbility"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Matière de petits cours"
|
||||
verbose_name_plural = "Matières des petits cours"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class PetitCoursAbility(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
matiere = models.ForeignKey(
|
||||
PetitCoursSubject, on_delete=models.CASCADE, verbose_name=_("Matière")
|
||||
)
|
||||
niveau = models.CharField(
|
||||
_("Niveau"), choices=LEVELS_CHOICES, max_length=choices_length(LEVELS_CHOICES)
|
||||
)
|
||||
agrege = models.BooleanField(_("Agrégé"), default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Compétence petits cours"
|
||||
verbose_name_plural = "Compétences des petits cours"
|
||||
|
||||
def __str__(self):
|
||||
return "{:s} - {!s} - {:s}".format(
|
||||
self.user.username, self.matiere, self.niveau
|
||||
)
|
||||
|
||||
|
||||
class PetitCoursDemande(models.Model):
|
||||
name = models.CharField(_("Nom/prénom"), max_length=200)
|
||||
email = models.CharField(_("Adresse email"), max_length=300)
|
||||
phone = models.CharField(_("Téléphone (facultatif)"), max_length=20, blank=True)
|
||||
quand = models.CharField(
|
||||
_("Quand ?"),
|
||||
help_text=_(
|
||||
"Indiquez ici la période désirée pour les petits"
|
||||
" cours (vacances scolaires, semaine, week-end)."
|
||||
),
|
||||
max_length=300,
|
||||
blank=True,
|
||||
)
|
||||
freq = models.CharField(
|
||||
_("Fréquence"),
|
||||
help_text=_(
|
||||
"Indiquez ici la fréquence envisagée "
|
||||
"(hebdomadaire, 2 fois par semaine, ...)"
|
||||
),
|
||||
max_length=300,
|
||||
blank=True,
|
||||
)
|
||||
lieu = models.CharField(
|
||||
_("Lieu (si préférence)"),
|
||||
help_text=_("Si vous avez avez une préférence sur le lieu."),
|
||||
max_length=300,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
matieres = models.ManyToManyField(
|
||||
PetitCoursSubject, verbose_name=_("Matières"), related_name="demandes"
|
||||
)
|
||||
agrege_requis = models.BooleanField(_("Agrégé requis"), default=False)
|
||||
niveau = models.CharField(
|
||||
_("Niveau"),
|
||||
default="",
|
||||
choices=LEVELS_CHOICES,
|
||||
max_length=choices_length(LEVELS_CHOICES),
|
||||
)
|
||||
|
||||
remarques = models.TextField(_("Remarques et précisions"), blank=True)
|
||||
|
||||
traitee = models.BooleanField(_("Traitée"), default=False)
|
||||
traitee_par = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE, blank=True, null=True
|
||||
)
|
||||
processed = models.DateTimeField(_("Date de traitement"), 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}".format(self.id, self.created.strftime("%d %b %Y"))
|
||||
|
||||
|
||||
class PetitCoursAttribution(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
demande = models.ForeignKey(
|
||||
PetitCoursDemande, on_delete=models.CASCADE, verbose_name=_("Demande")
|
||||
)
|
||||
matiere = models.ForeignKey(
|
||||
PetitCoursSubject, on_delete=models.CASCADE, verbose_name=_("Matière")
|
||||
)
|
||||
date = models.DateTimeField(_("Date d'attribution"), auto_now_add=True)
|
||||
rank = models.IntegerField("Rang dans l'email")
|
||||
selected = models.BooleanField(_("Sélectionné par le demandeur"), default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Attribution de petits cours"
|
||||
verbose_name_plural = "Attributions de petits cours"
|
||||
|
||||
def __str__(self):
|
||||
return "Attribution de la demande {:d} à {:s} pour {!s}".format(
|
||||
self.demande.id, self.user.username, self.matiere
|
||||
)
|
||||
|
||||
|
||||
class PetitCoursAttributionCounter(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
matiere = models.ForeignKey(
|
||||
PetitCoursSubject, on_delete=models.CASCADE, 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 or 0
|
||||
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}".format(
|
||||
self.count, self.user.username, self.matiere
|
||||
)
|
|
@ -1,401 +0,0 @@
|
|||
import json
|
||||
|
||||
from custommail.shortcuts import render_custom_mail
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import mail
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils import timezone
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic import DetailView, ListView
|
||||
|
||||
from gestioncof.decorators import buro_required
|
||||
from gestioncof.models import CofProfile
|
||||
from gestioncof.petits_cours_forms import DemandeForm, MatieresFormSet
|
||||
from gestioncof.petits_cours_models import (
|
||||
PetitCoursAbility,
|
||||
PetitCoursAttribution,
|
||||
PetitCoursAttributionCounter,
|
||||
PetitCoursDemande,
|
||||
)
|
||||
|
||||
|
||||
class DemandeListView(ListView):
|
||||
queryset = PetitCoursDemande.objects.prefetch_related("matieres").order_by(
|
||||
"traitee", "-id"
|
||||
)
|
||||
template_name = "petits_cours_demandes_list.html"
|
||||
paginate_by = 20
|
||||
|
||||
|
||||
class DemandeDetailView(DetailView):
|
||||
model = PetitCoursDemande
|
||||
queryset = PetitCoursDemande.objects.prefetch_related(
|
||||
"petitcoursattribution_set", "matieres"
|
||||
)
|
||||
template_name = "gestioncof/details_demande_petit_cours.html"
|
||||
context_object_name = "demande"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
obj = self.object
|
||||
context["attributions"] = obj.petitcoursattribution_set.all()
|
||||
return context
|
||||
|
||||
|
||||
@buro_required
|
||||
def traitement(request, demande_id, redo=False):
|
||||
demande = get_object_or_404(PetitCoursDemande, id=demande_id)
|
||||
if demande.niveau == "other":
|
||||
return _traitement_other(request, demande, redo)
|
||||
if request.method == "POST":
|
||||
return _traitement_post(request, demande)
|
||||
proposals = {}
|
||||
proposed_for = {}
|
||||
unsatisfied = []
|
||||
attribdata = {}
|
||||
for matiere, candidates in demande.get_candidates(redo):
|
||||
if candidates:
|
||||
tuples = []
|
||||
for candidate in candidates:
|
||||
user = candidate.user
|
||||
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))]
|
||||
attribdata[matiere.id] = []
|
||||
proposals[matiere] = []
|
||||
for candidate in candidates:
|
||||
user = candidate.user
|
||||
proposals[matiere].append(user)
|
||||
attribdata[matiere.id].append(user.id)
|
||||
if user not in proposed_for:
|
||||
proposed_for[user] = [matiere]
|
||||
else:
|
||||
proposed_for[user].append(matiere)
|
||||
else:
|
||||
unsatisfied.append(matiere)
|
||||
return _finalize_traitement(
|
||||
request, demande, proposals, proposed_for, unsatisfied, attribdata, redo
|
||||
)
|
||||
|
||||
|
||||
@buro_required
|
||||
def retraitement(request, demande_id):
|
||||
return traitement(request, demande_id, redo=True)
|
||||
|
||||
|
||||
def _finalize_traitement(
|
||||
request,
|
||||
demande,
|
||||
proposals,
|
||||
proposed_for,
|
||||
unsatisfied,
|
||||
attribdata,
|
||||
redo=False,
|
||||
errors=None,
|
||||
):
|
||||
proposals = proposals.items()
|
||||
proposed_for = proposed_for.items()
|
||||
attribdata = list(attribdata.items())
|
||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||
mainmail = render_custom_mail(
|
||||
"petits-cours-mail-demandeur",
|
||||
{
|
||||
"proposals": proposals,
|
||||
"unsatisfied": unsatisfied,
|
||||
"extra": '<textarea name="extra" '
|
||||
'style="width:99%; height: 90px;">'
|
||||
"</textarea>",
|
||||
},
|
||||
)
|
||||
if errors is not None:
|
||||
for error in errors:
|
||||
messages.error(request, error)
|
||||
return render(
|
||||
request,
|
||||
"gestioncof/traitement_demande_petit_cours.html",
|
||||
{
|
||||
"demande": demande,
|
||||
"unsatisfied": unsatisfied,
|
||||
"proposals": proposals,
|
||||
"proposed_for": proposed_for,
|
||||
"proposed_mails": proposed_mails,
|
||||
"mainmail": mainmail,
|
||||
"attribdata": json.dumps(attribdata),
|
||||
"redo": redo,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _generate_eleve_email(demande, proposed_for):
|
||||
return [
|
||||
(
|
||||
user,
|
||||
render_custom_mail(
|
||||
"petit-cours-mail-eleve", {"demande": demande, "matieres": matieres}
|
||||
),
|
||||
)
|
||||
for user, matieres in proposed_for
|
||||
]
|
||||
|
||||
|
||||
def _traitement_other_preparing(request, demande):
|
||||
redo = "redo" in request.POST
|
||||
unsatisfied = []
|
||||
proposals = {}
|
||||
proposed_for = {}
|
||||
attribdata = {}
|
||||
errors = []
|
||||
for matiere, candidates in demande.get_candidates(redo):
|
||||
if candidates:
|
||||
candidates = dict(
|
||||
[(candidate.user.id, candidate.user) for candidate in candidates]
|
||||
)
|
||||
attribdata[matiere.id] = []
|
||||
proposals[matiere] = []
|
||||
for choice_id in range(min(3, len(candidates))):
|
||||
choice = int(
|
||||
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}".format(choice_id + 1, matiere)
|
||||
)
|
||||
continue
|
||||
user = candidates[choice]
|
||||
if user in proposals[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)
|
||||
if user not in proposed_for:
|
||||
proposed_for[user] = [matiere]
|
||||
else:
|
||||
proposed_for[user].append(matiere)
|
||||
if not proposals[matiere]:
|
||||
errors.append("Aucune proposition pour {!s}".format(matiere))
|
||||
elif len(proposals[matiere]) < 3:
|
||||
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,
|
||||
unsatisfied,
|
||||
attribdata,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
|
||||
def _traitement_other(request, demande, redo):
|
||||
if request.method == "POST":
|
||||
if "preparing" in request.POST:
|
||||
return _traitement_other_preparing(request, demande)
|
||||
else:
|
||||
return _traitement_post(request, demande)
|
||||
proposals = {}
|
||||
proposed_for = {}
|
||||
unsatisfied = []
|
||||
attribdata = {}
|
||||
for matiere, candidates in demande.get_candidates(redo):
|
||||
if candidates:
|
||||
tuples = []
|
||||
for candidate in candidates:
|
||||
user = candidate.user
|
||||
tuples.append(
|
||||
(
|
||||
candidate,
|
||||
PetitCoursAttributionCounter.get_uptodate(user, matiere),
|
||||
)
|
||||
)
|
||||
tuples = sorted(tuples, key=lambda c: c[1].count)
|
||||
candidates, _ = zip(*tuples)
|
||||
attribdata[matiere.id] = []
|
||||
proposals[matiere] = []
|
||||
for candidate in candidates:
|
||||
user = candidate.user
|
||||
proposals[matiere].append(user)
|
||||
attribdata[matiere.id].append(user.id)
|
||||
if user not in proposed_for:
|
||||
proposed_for[user] = [matiere]
|
||||
else:
|
||||
proposed_for[user].append(matiere)
|
||||
else:
|
||||
unsatisfied.append(matiere)
|
||||
proposals = proposals.items()
|
||||
proposed_for = proposed_for.items()
|
||||
return render(
|
||||
request,
|
||||
"gestioncof/traitement_demande_petit_cours_autre_niveau.html",
|
||||
{
|
||||
"demande": demande,
|
||||
"unsatisfied": unsatisfied,
|
||||
"proposals": proposals,
|
||||
"proposed_for": proposed_for,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _traitement_post(request, demande):
|
||||
proposals = {}
|
||||
proposed_for = {}
|
||||
unsatisfied = []
|
||||
extra = request.POST["extra"].strip()
|
||||
redo = "redo" in request.POST
|
||||
attribdata = request.POST["attribdata"]
|
||||
attribdata = dict(json.loads(attribdata))
|
||||
for matiere in demande.matieres.all():
|
||||
if matiere.id not in attribdata:
|
||||
unsatisfied.append(matiere)
|
||||
else:
|
||||
proposals[matiere] = []
|
||||
for user_id in attribdata[matiere.id]:
|
||||
user = User.objects.get(pk=user_id)
|
||||
proposals[matiere].append(user)
|
||||
if user not in proposed_for:
|
||||
proposed_for[user] = [matiere]
|
||||
else:
|
||||
proposed_for[user].append(matiere)
|
||||
proposals_list = proposals.items()
|
||||
proposed_for = proposed_for.items()
|
||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||
mainmail_object, mainmail_body = render_custom_mail(
|
||||
"petits-cours-mail-demandeur",
|
||||
{"proposals": proposals_list, "unsatisfied": unsatisfied, "extra": extra},
|
||||
)
|
||||
frommail = settings.MAIL_DATA["petits_cours"]["FROM"]
|
||||
bccaddress = settings.MAIL_DATA["petits_cours"]["BCC"]
|
||||
replyto = settings.MAIL_DATA["petits_cours"]["REPLYTO"]
|
||||
mails_to_send = []
|
||||
for (user, (mail_object, body)) in proposed_mails:
|
||||
msg = mail.EmailMessage(
|
||||
mail_object,
|
||||
body,
|
||||
frommail,
|
||||
[user.email],
|
||||
[bccaddress],
|
||||
headers={"Reply-To": replyto},
|
||||
)
|
||||
mails_to_send.append(msg)
|
||||
mails_to_send.append(
|
||||
mail.EmailMessage(
|
||||
mainmail_object,
|
||||
mainmail_body,
|
||||
frommail,
|
||||
[demande.email],
|
||||
[bccaddress],
|
||||
headers={"Reply-To": replyto},
|
||||
)
|
||||
)
|
||||
connection = mail.get_connection(fail_silently=False)
|
||||
connection.send_messages(mails_to_send)
|
||||
with transaction.atomic():
|
||||
for matiere in proposals:
|
||||
for rank, user in enumerate(proposals[matiere]):
|
||||
counter = PetitCoursAttributionCounter.objects.get(
|
||||
user=user, matiere=matiere
|
||||
)
|
||||
counter.count += 1
|
||||
counter.save()
|
||||
attrib = PetitCoursAttribution(
|
||||
user=user, matiere=matiere, demande=demande, rank=rank + 1
|
||||
)
|
||||
attrib.save()
|
||||
demande.traitee = True
|
||||
demande.traitee_par = request.user
|
||||
demande.processed = timezone.now()
|
||||
demande.save()
|
||||
return render(
|
||||
request,
|
||||
"gestioncof/traitement_demande_petit_cours_success.html",
|
||||
{"demande": demande, "redo": redo},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def inscription(request):
|
||||
profile, created = CofProfile.objects.get_or_create(user=request.user)
|
||||
if not profile.is_cof:
|
||||
return redirect("cof-denied")
|
||||
success = False
|
||||
if request.method == "POST":
|
||||
formset = MatieresFormSet(request.POST, instance=request.user)
|
||||
if formset.is_valid():
|
||||
formset.save()
|
||||
profile.petits_cours_accept = "receive_proposals" in request.POST
|
||||
profile.petits_cours_remarques = request.POST["remarques"]
|
||||
profile.save()
|
||||
with transaction.atomic():
|
||||
abilities = PetitCoursAbility.objects.filter(user=request.user).all()
|
||||
for ability in abilities:
|
||||
PetitCoursAttributionCounter.get_uptodate(
|
||||
ability.user, ability.matiere
|
||||
)
|
||||
success = True
|
||||
formset = MatieresFormSet(instance=request.user)
|
||||
else:
|
||||
formset = MatieresFormSet(instance=request.user)
|
||||
return render(
|
||||
request,
|
||||
"inscription-petit-cours.html",
|
||||
{
|
||||
"formset": formset,
|
||||
"success": success,
|
||||
"receive_proposals": profile.petits_cours_accept,
|
||||
"remarques": profile.petits_cours_remarques,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def demande(request):
|
||||
success = False
|
||||
if request.method == "POST":
|
||||
form = DemandeForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
success = True
|
||||
else:
|
||||
form = DemandeForm()
|
||||
return render(
|
||||
request, "demande-petit-cours.html", {"form": form, "success": success}
|
||||
)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def demande_raw(request):
|
||||
success = False
|
||||
if request.method == "POST":
|
||||
form = DemandeForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
success = True
|
||||
else:
|
||||
form = DemandeForm()
|
||||
return render(
|
||||
request, "demande-petit-cours-raw.html", {"form": form, "success": success}
|
||||
)
|
|
@ -15,7 +15,7 @@ def messages_on_out_login(request, user, **kwargs):
|
|||
|
||||
|
||||
@receiver(cas_user_authenticated)
|
||||
def mesagges_on_cas_login(request, user, **kwargs):
|
||||
def messages_on_cas_login(request, user, **kwargs):
|
||||
msg = _("Connexion à GestioCOF par CAS réussie. Bienvenue {}.").format(
|
||||
user.get_short_name()
|
||||
)
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
5
gestioncof/templates/buro-denied.html
Normal file
5
gestioncof/templates/buro-denied.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Section réservée au Burô.</h2>
|
||||
{% endblock %}
|
|
@ -1,19 +0,0 @@
|
|||
{% 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 "petits-cours-demande-raw" %}">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
{{ form | bootstrap }}
|
||||
</table>
|
||||
<input type="submit" class="btn-submit" value="Enregistrer" />
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,16 +0,0 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Demande de petits cours</h2>
|
||||
{% if success %}
|
||||
<p class="success">Votre demande a été enregistrée avec succès !</p>
|
||||
{% else %}
|
||||
<form id="demandecours" method="post" action="{% url "petits-cours-demande" %}">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
<input type="submit" class="btn-submit" value="Enregistrer" />
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,14 +0,0 @@
|
|||
{% load staticfiles %}
|
||||
<table class="table table-striped">
|
||||
<tr class="danger"><td><strong>Date</strong></td><td> {{ demande.created }}</td></tr>
|
||||
<tr class="warning"><td><strong>Nom/prénom</strong></td><td> {{ demande.name }}</td></tr>
|
||||
<tr><td><strong>Email</strong></td><td> {{ demande.email }}</td></tr>
|
||||
<tr><td><strong>Téléphone</strong></td><td> {{ demande.phone }}</td></tr>
|
||||
<tr><td><strong>Lieu</strong></td><td> {{ demande.lieu }}</td></tr>
|
||||
<tr><td><strong>Quand</strong></td><td> {{ demande.quand }}</td></tr>
|
||||
<tr><td><strong>Fréquence</strong></td><td> {{ demande.freq }}</td></tr>
|
||||
<tr><td><strong>Matières</strong></td><td> {% for matiere in demande.matieres.all %}{% if forloop.counter0 > 0 %}, {% endif %}{{ matiere }}{% endfor %}</td></tr>
|
||||
<tr><td><strong>Niveau souhaité</strong></td><td> {{ demande.get_niveau_display }}</td></tr>
|
||||
<tr><td><strong>Agrégé requis</strong></td><td> <img src="{% if demande.agrege_requis %}{% static "images/yes.png" %}{% else %}{% static "images/no.png" %}{% endif %}" /></td></tr>
|
||||
<tr><td><strong>Remarques</strong></td><td> {{ demande.remarques }}</td></tr>
|
||||
</table>
|
|
@ -1,39 +0,0 @@
|
|||
{% extends "base_title_petitscours.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block page_size %}col-sm-8{% endblock %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Demande de petits cours</h2>
|
||||
{% include "details_demande_petit_cours_infos.html" %}
|
||||
<hr />
|
||||
<table class="table table-striped">
|
||||
<tr><td><strong>Traitée</strong></td><td> <img src="{% if demande.traitee %}{% static "images/yes.png" %}{% else %}{% static "images/no.png" %}{% endif %}" /></td></tr>
|
||||
{% if demande.traitee %}
|
||||
<tr><td><strong>Traitée par</strong></td><td> {{ demande.traitee_par }}</td></tr>
|
||||
<tr><td><strong>Traitée le</strong></td><td> {{ demande.processed }}</td></tr>
|
||||
<tr><td>
|
||||
<strong>Attributions</strong></td><td>
|
||||
<ul>
|
||||
{% for attrib in attributions %}
|
||||
<li>{{ attrib.user.get_full_name }} pour {{ attrib.matiere }}, {{ attrib.date|date:"d E Y" }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
{% if demande.traitee %}
|
||||
<div style="text-align: right;">
|
||||
<form action="{% url "petits-cours-demande-retraitement" demande.id %}">
|
||||
<input class="btn btn-primary" type="submit" value="Retraiter">
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="text-align: right;">
|
||||
<form action="{% url "petits-cours-demande-traitement" demande.id %}">
|
||||
<input class="btn btn-primary" type="submit" value="Traiter">
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,51 +0,0 @@
|
|||
{% extends "base_title_petitscours.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Traitement de la demande de petits cours {{ demande.id }}</h2>
|
||||
{% include "details_demande_petit_cours_infos.html" %}
|
||||
<hr />
|
||||
{% if errors %}
|
||||
<div class="error">
|
||||
Attention:
|
||||
<ul>{% for error in errors %}<li>{{ error }}</li>{% endfor %}</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if unsatisfied %}
|
||||
<div class="error">
|
||||
Attention: Impossible de trouver des propositions pour les matières suivantes:
|
||||
<ul>
|
||||
{% for matiere in unsatisfied %}<li>{{ matiere }}</li>{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if proposals %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
Propositions:
|
||||
<ul>
|
||||
{% for proposeduser, matieres in proposed_for %}
|
||||
<li>{{ proposeduser }} pour {% for matiere in matieres %}{% if forloop.counter0 > 0 %}, {% endif %}{{ matiere }}{% endfor %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<h4>Mails pour les membres proposés :</h4>
|
||||
{% for proposeduser, mail in proposed_mails %}
|
||||
<h5>Pour {{ proposeduser }}:</h5>
|
||||
{% with object=mail.0 content=mail.1 %}
|
||||
<pre>{{ object }}</pre>
|
||||
<pre>{{ content }}</pre>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
<h4>Mail pour l'auteur de la demande :</h4>
|
||||
{% with object=mainmail.0 content=mainmail.1 %}
|
||||
<pre style="margin-top: 15px;">{{ object }}</pre>
|
||||
<pre style="margin-top: 15px;">{{ content|safe }}</pre>
|
||||
{% endwith %}
|
||||
<input type="hidden" name="attribdata" value="{{ attribdata }}" />
|
||||
{% if redo %}<input type="hidden" name="redo" value="1" />{% endif %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="Valider le {% if redo %}re{% endif %}traitement de la demande" />
|
||||
</form>
|
||||
{% else %}
|
||||
<h3>Impossible de trouver des propositions pour cette demande</h3>
|
||||
<div class="error" style="font-size: 1.6em; margin-top: 10px;">Traitement manuel obligatoire !</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,58 +0,0 @@
|
|||
{% extends "base_title_petitscours.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Traitement de la demande de petits cours {{ demande.id }}</h2>
|
||||
{% include "details_demande_petit_cours_infos.html" %}
|
||||
<hr />
|
||||
<div class="error">
|
||||
Attention: demande de petits cours spécifiant le niveau "Autre niveau": choisissez les candidats correspondant aux remarques de la demande. S'il y a moins de 3 candidats adaptés, ne mettre que ceux qui conviennent, pas besoin de faire du bourrage :)
|
||||
</div>
|
||||
{% if unsatisfied %}
|
||||
<div class="error">
|
||||
Attention: Impossible de trouver des propositions pour les matières suivantes:
|
||||
<ul>
|
||||
{% for matiere in unsatisfied %}<li>{{ matiere }}</li>{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if proposals %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% for matiere, users in proposals %}
|
||||
<h4>{{ matiere }}</h4>
|
||||
{% for i in "012"|make_list %}{% if i|add:"0" < users|length %}
|
||||
<div style="float: left; width: 340px;">
|
||||
Proposition {{ i|add:"1" }}:
|
||||
<select id="proposal-{{ matiere.id }}-{{ i }}" name="proposal-{{ matiere.id }}-{{ i }}">
|
||||
<option value="-1" data-description="">-------</option>
|
||||
{% for user in users %}
|
||||
<option value="{{ user.id }}" data-description="{{ user.profile.petits_cours_remarques }}">{{ user }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div style="float: right; width: 580px;"><p id="proposal-description-{{ matiere.id }}-{{ i }}"></p></div>
|
||||
<div class="spacer"></div>
|
||||
<script type="text/javascript">
|
||||
$('#proposal-{{ matiere.id }}-{{ i }}').change(function(){
|
||||
var selected = $(this).find(':selected');
|
||||
var content = "";
|
||||
if (selected.val() != "-1")
|
||||
content = (selected.data('description')) ? selected.data('description'): "Pas de remarques";
|
||||
|
||||
$('#proposal-description-{{ matiere.id }}-{{ i }}').html(content);
|
||||
}).trigger('change');
|
||||
</script>
|
||||
<hr />
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
<input type="hidden" name="preparing" value="1" />
|
||||
{% if redo %}<input type="hidden" name="redo" value="1" />{% endif %}
|
||||
<input type="submit" value="Préparer le {% if redo %}re{% endif %}traitement de la demande" />
|
||||
</form>
|
||||
{% else %}
|
||||
<h3>Impossible de trouver des propositions pour cette demande</h3>
|
||||
<div class="error" style="font-size: 1.6em; margin-top: 10px;">Traitement manuel obligatoire !</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||
{% extends "base_title_petitscours.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Traitement de la demande de petits cours {{ demande.id }}</h2>
|
||||
<div class="success" style="margin: 15px 0px; font-size: 1.4em;">Demande {{ demande.id }} de {{ demande.name }} {% if redo %}re{% endif %}traitée avec succès !</div>
|
||||
<a href="{% url "petits-cours-demandes-list" %}">Retour à la liste des demandes</a>
|
||||
{% endblock %}
|
|
@ -1,40 +0,0 @@
|
|||
{% load bootstrap %}
|
||||
{{ formset.non_form_errors.as_ul }}
|
||||
<table id="bda_formset" class="form table">
|
||||
{{ formset.management_form }}
|
||||
{% for form in formset.forms %}
|
||||
{% if forloop.first %}
|
||||
<thead><tr>
|
||||
{% for field in form.visible_fields %}
|
||||
<th class="bda-field-{{ field.name }}">
|
||||
{% if field.name != "DELETE" %}
|
||||
{{ field.label|safe|capfirst }}
|
||||
{% endif %}
|
||||
<sup>{{ forloop.counter }}</sup>
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr></thead>
|
||||
<tbody class="bda_formset_content">
|
||||
{% endif %}
|
||||
<tr class="{% cycle 'row1' 'row2' %} dynamic-form {% if form.instance.pk %}has_original{% endif %}">
|
||||
{% for field in form.visible_fields %}
|
||||
{% if field.name != "DELETE" and field.name != "priority" %}
|
||||
<td class="bda-field-{{ field.name }}">
|
||||
{% if forloop.first %}
|
||||
{{ form.non_field_errors }}
|
||||
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
|
||||
{% endif %}
|
||||
{{ field | bootstrap }}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<td class="tools-cell"><div class="tools">
|
||||
<input type="checkbox" name="{{ form.DELETE.html_name }}" style="display: none;" />
|
||||
<a href="javascript://" class="glyphicon glyphicon-remove remove-btn" title="Supprimer"></a>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
|
@ -1,117 +0,0 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block extra_head %}
|
||||
<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 %}
|
||||
<script type="text/javascript">
|
||||
var django = {
|
||||
"jQuery": jQuery.noConflict(true)
|
||||
};
|
||||
|
||||
(function($) {
|
||||
cloneMore = function(selector, type) {
|
||||
var newElement = $(selector).clone(true);
|
||||
var total = $('#id_' + type + '-TOTAL_FORMS').val();
|
||||
newElement.find(':input').each(function() {
|
||||
var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
|
||||
var id = 'id_' + name;
|
||||
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
|
||||
});
|
||||
total++;
|
||||
$('#id_' + type + '-TOTAL_FORMS').val(total);
|
||||
$(selector).after(newElement);
|
||||
}
|
||||
deleteButtonHandler = function(elem) {
|
||||
elem.bind("click", function() {
|
||||
var deleteInput = $(this).prev(),
|
||||
form = $(this).parents(".dynamic-form").first();
|
||||
// callback
|
||||
// toggle options.predeleteCssClass and toggle checkbox
|
||||
if (form.hasClass("has_original")) {
|
||||
form.toggleClass("predelete");
|
||||
if (deleteInput.attr("checked")) {
|
||||
deleteInput.attr("checked", false);
|
||||
} else {
|
||||
deleteInput.attr("checked", true);
|
||||
}
|
||||
}
|
||||
// callback
|
||||
});
|
||||
};
|
||||
$(document).ready(function($) {
|
||||
deleteButtonHandler($("table#bda_formset tbody.bda_formset_content").find("a.remove-btn"));
|
||||
$("table#bda_formset tbody.bda_formset_content").sortable({
|
||||
handle: "a.drag-handler",
|
||||
items: "tr",
|
||||
axis: "y",
|
||||
appendTo: 'body',
|
||||
forceHelperSize: true,
|
||||
placeholder: 'ui-sortable-placeholder',
|
||||
forcePlaceholderSize: true,
|
||||
containment: 'form#bda_form',
|
||||
tolerance: 'pointer',
|
||||
start: function(evt, ui) {
|
||||
var template = "",
|
||||
len = ui.item.children("td").length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
template += "<td style='height:" + (ui.item.outerHeight() + 12 ) + "px' class='placeholder-cell'> </td>"
|
||||
}
|
||||
template += "";
|
||||
ui.placeholder.html(template);
|
||||
},
|
||||
stop: function(evt, ui) {
|
||||
// Toggle div.table twice to remove webkits border-spacing bug
|
||||
$("table#bda_formset").toggle().toggle();
|
||||
},
|
||||
});
|
||||
$("#bda_form").bind("submit", function(){
|
||||
var sortable_field_name = "priority";
|
||||
var i = 1;
|
||||
$(".bda_formset_content").find("tr").each(function(){
|
||||
var fields = $(this).find("td :input[value]"),
|
||||
select = $(this).find("td select");
|
||||
if (select.val() && fields.serialize()) {
|
||||
$(this).find("input[name$='"+sortable_field_name+"']").val(i);
|
||||
i++;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
})(django.jQuery);
|
||||
</script>
|
||||
|
||||
<h2 class="no-bottom-margin">Inscription pour donner des cours particuliers</h2>
|
||||
{% if success %}
|
||||
<p class="success table-top">Votre inscription a été mise à jour avec succès !</p>
|
||||
{% endif %}
|
||||
<form class="form-horizontal petit-cours_form" id="bda_form" method="post" action="{% url 'petits-cours-inscription' %}">
|
||||
{% csrf_token %}
|
||||
<div class="table-top" style="margin-left:0px; margin-right:0px; font-size: 1.25em; font-weight: bold; color: #DE826B;"><input type="checkbox" name="receive_proposals" {% if receive_proposals %}checked="checked"{% endif %} /> Recevoir des propositions de petits cours</div>
|
||||
{% include "inscription-petit-cours-formset.html" %}
|
||||
<div class="inscription-bottom">
|
||||
<input type="button" class="btn btn-default pull-right" value="Ajouter une autre matière" id="add_more" />
|
||||
<script>
|
||||
django.jQuery('#add_more').click(function() {
|
||||
cloneMore('tbody.bda_formset_content tr:last-child', 'petitcoursability_set');
|
||||
});
|
||||
</script><br />
|
||||
<div style="margin: 10px 0px;">
|
||||
<span style="vertical-align: top; font-weight: bold;">Remarques:</span> <textarea name="remarques" style="width: 60%; height: 60px;">{{ remarques }}</textarea>
|
||||
</div>
|
||||
<input type="submit" class="btn btn-primary pull-right" value="Enregistrer" />
|
||||
<p class="footnotes">
|
||||
<sup>1</sup>: spécifiez les matières pour lesquelles vous êtes compétent<br />
|
||||
<sup>2</sup>: spécifiez pour chaque matière le ou les niveaux pour lesquels vous souhaitez recevoir des demandes<br />
|
||||
<sup>3</sup>: spécifiez si vous êtes titulaire de l'agrégation pour cette matière<br />
|
||||
<sup>4</sup>: pour supprimer une ligne, cliquez sur la croix puis appuyer sur <tt>Enregistrer</tt><br />
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,47 +0,0 @@
|
|||
{% extends "base_title_petitscours.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Demandes de petits cours</h2>
|
||||
{% if object_list %}
|
||||
<table class="table table-stripped">
|
||||
<thead>
|
||||
<th>Nom</th>
|
||||
<th>Matières</th>
|
||||
<th>Date</th>
|
||||
<th>Traitée</th>
|
||||
<th>par</th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for demande in object_list %}
|
||||
<tr>
|
||||
<td>{{ demande.name }}</td>
|
||||
<td>{% for matiere in demande.matieres.all %}{% if forloop.counter0 > 0 %}, {% endif %}{{ matiere }}{% endfor %}</td>
|
||||
<td>{{ demande.created|date:"d E Y" }}</td>
|
||||
<td style="text-align: center;"><img src="{% if demande.traitee %}{% static "images/yes.png" %}{% else %}{% static "images/no.png" %}{% endif %}" /></td>
|
||||
<td>{% if demande.traitee_par %}{{ demande.traitee_par.username }}{% else %}<span class="glyphicon glyphicon-ban-circle"></span>{% endif %}</td>
|
||||
<td><a href="{% url "petits-cours-demande-details" demande.id %}" class="see_detail">Détails</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if is_paginated %}
|
||||
<div class="pagination">
|
||||
<span class="page-links">
|
||||
{% if page_obj.has_previous %}
|
||||
<a href="{% url "petits-cours-demandes-list" %}?page={{ page_obj.previous_page_number }}"><</a>
|
||||
{% endif %}
|
||||
<span class="page-current">
|
||||
Page {{ page_obj.number }} sur {{ page_obj.paginator.num_pages }}
|
||||
</span>
|
||||
{% if page_obj.has_next %}
|
||||
<a href="{% url "petits-cours-demandes-list" %}?page={{ page_obj.next_page_number }}">></a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p>Aucune demande :(</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -76,7 +76,7 @@ class RegistrationViewTests(ViewTestCaseMixin, TestCase):
|
|||
"last_name": "last",
|
||||
"email": "username@mail.net",
|
||||
"is_cof": "1",
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -111,7 +111,7 @@ class RegistrationViewTests(ViewTestCaseMixin, TestCase):
|
|||
"email": "user@mail.net",
|
||||
"is_cof": "1",
|
||||
"user_exists": "1",
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -137,7 +137,7 @@ class RegistrationViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
data = dict(
|
||||
self._minimal_data,
|
||||
**{"username": u.username, "email": "user@mail.net", "user_exists": "1"}
|
||||
**{"username": u.username, "email": "user@mail.net", "user_exists": "1"},
|
||||
)
|
||||
if is_cof:
|
||||
data["is_cof"] = "1"
|
||||
|
@ -197,7 +197,7 @@ class RegistrationViewTests(ViewTestCaseMixin, TestCase):
|
|||
"events-0-option_{}".format(o2.pk): [str(oc3.pk)],
|
||||
"events-0-comment_{}".format(cf1.pk): "comment 1",
|
||||
"events-0-comment_{}".format(cf2.pk): "",
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
from django.conf.urls import url
|
||||
|
||||
from gestioncof import petits_cours_views, views
|
||||
from gestioncof import views
|
||||
from gestioncof.decorators import buro_required
|
||||
from gestioncof.petits_cours_views import DemandeDetailView, DemandeListView
|
||||
|
||||
export_patterns = [
|
||||
url(r"^members$", views.export_members, name="cof.membres_export"),
|
||||
|
@ -21,40 +20,6 @@ export_patterns = [
|
|||
url(r"^mega$", views.export_mega, name="cof.mega_export"),
|
||||
]
|
||||
|
||||
petitcours_patterns = [
|
||||
url(
|
||||
r"^inscription$",
|
||||
petits_cours_views.inscription,
|
||||
name="petits-cours-inscription",
|
||||
),
|
||||
url(r"^demande$", petits_cours_views.demande, name="petits-cours-demande"),
|
||||
url(
|
||||
r"^demande-raw$",
|
||||
petits_cours_views.demande_raw,
|
||||
name="petits-cours-demande-raw",
|
||||
),
|
||||
url(
|
||||
r"^demandes$",
|
||||
buro_required(DemandeListView.as_view()),
|
||||
name="petits-cours-demandes-list",
|
||||
),
|
||||
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,
|
||||
name="petits-cours-demande-traitement",
|
||||
),
|
||||
url(
|
||||
r"^demandes/(?P<demande_id>\d+)/retraitement$",
|
||||
petits_cours_views.retraitement,
|
||||
name="petits-cours-demande-retraitement",
|
||||
),
|
||||
]
|
||||
|
||||
surveys_patterns = [
|
||||
url(
|
||||
r"^(?P<survey_id>\d+)/status$",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue