Grod commit dégueux avec une tonne de trucs. Berk.

This commit is contained in:
root 2014-08-19 12:54:22 +02:00
parent d5b3d3f958
commit 64b8ee4133
17 changed files with 536 additions and 209 deletions

0
apache/django.wsgi Executable file → Normal file
View file

View file

@ -4,7 +4,7 @@ from django.core.mail import send_mail
from django.contrib.contenttypes.models import ContentType
from django.contrib import admin
from django.db.models import Sum
from django.db.models import Sum, Count
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle, Attribution
class ChoixSpectacleInline(admin.TabularInline):
@ -17,13 +17,18 @@ class AttributionInline(admin.TabularInline):
class ParticipantAdmin(admin.ModelAdmin):
#inlines = [ChoixSpectacleInline]
inlines = [AttributionInline]
def queryset(self, request):
return Participant.objects.annotate(nb_places = Count('attributions'),
total = Sum('attributions__price'))
def nb_places(self, obj):
return len(obj.attribution_set.all())
return obj.nb_places
nb_places.admin_order_field = "nb_places"
nb_places.short_description = "Nombre de places"
def total(self, obj):
tot = obj.attributions.aggregate(total = Sum('price'))['total']
tot = obj.total
if tot: return u"%.02f" % tot
else: return u"0 €"
total.admin_order_field = "total"
total.short_description = "Total à payer"
list_display = ("user", "nb_places", "total", "paid", "paymenttype")
list_filter = ("paid",)
@ -56,7 +61,7 @@ Voici tes choix de spectacles tels que notre système les a enregistrés :\n\n""
Artistiquement,
Le BdA"""
send_mail ("Choix de spectacles (BdA du COF)", mail,
"bda@clipper.ens.fr", [member.user.email],
"bda@ens.fr", [member.user.email],
fail_silently = True)
count = len(queryset.all())
if count == 1:
@ -81,35 +86,48 @@ pour les spectacles suivants :
%s
*Paiement*
Ces spectacles sont à régler avant le vendredi 19 Octobre, pendant les
heures de permanences du COF (tous les jours de la semaine entre 12h et
14h, et entre 18h et 20h). Des facilités de paiement sont bien évidemment
possibles (encaissement échelonné des chèques).
L'intégralité de ces places de spectacles est à régler à partir du jeudi
10 octobre et AVANT le mercredi 23 octobre, au bureau du COF pendant les
heures de permanences (du lundi au vendredi entre 12h et 14h, et entre 18h
et 20h). Des facilités de paiement sont bien évidemment possibles : nous
pouvons ne pas encaisser le chèque immédiatement, ou bien découper votre
paiement en deux fois.
*Mode de retrait des places*
Pour l'Opéra de Paris, le théâtre de la Colline et le théâtre du Châtelet,
les places sont à retirer au COF le jour du paiement.
Au moment du paiement, une enveloppe vous sera remise, contenant les
places pour l'Opéra de Paris, pour les premiers spectacles de la Comédie
française, certains spectacles du Châtelet et du Théâtre de la Ville.
Pour les concerts Radio France, le théâtre des Champs-Élysées et la Salle
Pleyel, les places seront nominatives et à retirer au théâtre le soir de
la représentation au moins une demi-heure avant le début du spectacle.
Pour les concerts Radio France, le Théâtre des Champs-Élysées, le théâtre
du Rond-Point, le théâtre de la Colline, le théâtre de l'Athénée, l'IRCAM,
la Cité de la musique et le 104, le Studio-Théâtre de la Comédie
française, les places seront nominatives et à retirer au théâtre le soir
de la représentation au moins une demi-heure avant le début du spectacle.
Pour le théâtre de l'Odéon, la Comédie Française, le théâtre de la Ville,
le théâtre de Chaillot et l'IRCAM, les places seront distribuées dans vos
casiers environ une semaine avant la représentation (un mail vous en
avertira).
Pour le théâtre de l'Odéon, la salle Richelieu le théâtre du Vieux
colombier de la Comédie française, certains spectacles du théâtre de la
Ville et du théâtre de Châtelet ainsi que pour le théâtre de Chaillot, les
places seront distribuées environ une semaine avant la représentation (un
mail vous en avertira).
Culturellement vôtre,
Nous vous rappelons que l'obtention de places du BdA vous engage à
respecter les règles de fonctionnement :
http://www.cof.ens.fr/bda/?page_id=1370
Le système de revente des places via les mails BdA-revente sera très
prochainement disponible, directement sur votre compte GestioCOF.
En vous souhaitant de très beaux spectacles tout au long de l'année,
--
Le BdA"""
Le Bureau des Arts
(Chloé, Emilie, Jaime, Maxime, Olivier)
"""
attribs_text = ""
name = member.user.get_full_name()
for attrib in attribs:
attribs_text += u"- 1 place pour %s\n" % attrib
mail = mail % (name, attribs_text)
send_mail ("Places de spectacle (BdA du COF)", mail,
"bda@clipper.ens.fr", [member.user.email],
send_mail ("Résultats du tirage au sort", mail,
"bda@ens.fr", [member.user.email],
fail_silently = True)
count = len(queryset.all())
if count == 1:
@ -136,7 +154,13 @@ class ChoixSpectacleAdmin(admin.ModelAdmin):
list_filter = ("double", "autoquit")
search_fields = ('participant__user__username', 'participant__user__first_name', 'participant__user__last_name')
admin.site.register(Spectacle)
class SpectacleAdmin(admin.ModelAdmin):
model = Spectacle
list_display = ("title", "date", "location", "slots", "price")
list_filter = ("location",)
search_fields = ("title", "location__name")
admin.site.register(Spectacle, SpectacleAdmin)
admin.site.register(Salle)
admin.site.register(Participant, ParticipantAdmin)
admin.site.register(Attribution, AttributionAdmin)

View file

@ -29,25 +29,24 @@ class Algorithm(object):
self.ranks = {}
self.origranks = {}
self.choices = {}
next_rank = {}
member_shows = {}
for member in members:
ranks = {}
member_choices = {}
member_shows = {}
#next_priority = 1
next_rank = 1
for choice in member.choixspectacle_set.order_by('priority').all():
if choice.spectacle in member_shows: continue
else: member_shows[choice.spectacle] = True
#assert choice.priority == next_priority
#next_priority += 1
showdict[choice.spectacle].requests.append(member)
showdict[choice.spectacle].nrequests += 2 if choice.double else 1
ranks[choice.spectacle] = next_rank
next_rank += 2 if choice.double else 1
member_choices[choice.spectacle] = choice
self.ranks[member] = ranks
self.choices[member] = member_choices
self.origranks[member] = dict(ranks)
self.ranks[member] = {}
self.choices[member] = {}
next_rank[member] = 1
member_shows[member] = {}
for choice in choices:
member = choice.participant
if choice.spectacle in member_shows[member]: continue
else: member_shows[member][choice.spectacle] = True
showdict[choice.spectacle].requests.append(member)
showdict[choice.spectacle].nrequests += 2 if choice.double else 1
self.ranks[member][choice.spectacle] = next_rank[member]
next_rank[member] += 2 if choice.double else 1
self.choices[member][choice.spectacle] = choice
for member in members:
self.origranks[member] = dict(self.ranks[member])
def IncrementRanks(self, member, currank, increment = 1):
for show in self.ranks[member]:

View file

@ -1,5 +1,7 @@
# coding: utf-8
import calendar
from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
@ -29,11 +31,14 @@ class Spectacle (models.Model):
def __repr__ (self):
return u"[%s]" % self.__unicode__()
def timestamp(self):
return "%d" % calendar.timegm(self.date.utctimetuple())
def date_no_seconds(self):
return self.date.strftime('%d %b %Y %H:%M')
def __unicode__ (self):
return u"%s - %s @ %s, %.02f" % (self.title, self.date_no_seconds(), self.location, self.price)
return u"%s - %s, %s, %.02f" % (self.title, self.date_no_seconds(), self.location, self.price)
PAYMENT_TYPES = (
("cash",u"Cash"),

View file

@ -1,17 +1,22 @@
# coding: utf-8
from django.contrib.auth.models import User
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.db import models
from django.http import Http404
from django import forms
from django.forms.models import inlineformset_factory, BaseInlineFormSet
from django.core import serializers
import hashlib
from django.core.mail import send_mail
from datetime import datetime
import time
from gestioncof.decorators import cof_required, buro_required
from gestioncof.shared import send_custom_mail
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution
from bda.algorithm import Algorithm
@ -42,47 +47,93 @@ def etat_places(request):
total = 0
for spectacle in spectacles:
spectacle.total = 0
spectacle.ratio = -1.0
spectacles_dict[spectacle.id] = spectacle
for spectacle in spectacles1:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
spectacles_dict[spectacle["spectacle"]].ratio = spectacles_dict[spectacle["spectacle"]].total/float(spectacles_dict[spectacle["spectacle"]].slots)
total += spectacle["total"]
for spectacle in spectacles2:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
spectacles_dict[spectacle["spectacle"]].ratio = spectacles_dict[spectacle["spectacle"]].total/float(spectacles_dict[spectacle["spectacle"]].slots)
total += spectacle["total"]
return render(request, "etat-places.html", {"spectacles": spectacles, "total": total})
def _hash_queryset(queryset):
data = serializers.serialize("json", queryset)
hasher = hashlib.sha256()
hasher.update(data)
return hasher.hexdigest()
@cof_required
def places(request):
participant, created = Participant.objects.get_or_create(user = request.user)
places = participant.attribution_set.order_by("spectacle__date", "spectacle").all()
total = sum([place.spectacle.price for place in places])
filtered_places = []
places_dict = {}
spectacles = []
dates = []
warning = False
for place in places:
if place.spectacle in spectacles:
places_dict[place.spectacle].double = True
else:
place.double = False
places_dict[place.spectacle] = place
spectacles.append(place.spectacle)
filtered_places.append(place)
date = place.spectacle.date.date()
if date in dates:
warning = True
else:
dates.append(date)
return render(request, "resume_places.html",
{"participant": participant,
"places": filtered_places,
"total": total,
"warning": warning})
@cof_required
def inscription(request):
if False and time.time() > 1349474400:
return render(request, "error.html", {"error_title": "C'est fini !", "error_description": u"Tirage au sort le 6 octobre dans la soirée "})
if datetime.now() > datetime(2013, 10, 6, 23, 59):
participant, created = Participant.objects.get_or_create(user = request.user)
choices = participant.choixspectacle_set.order_by("priority").all()
return render(request, "resume_inscription.html", {"error_title": "C'est fini !", "error_description": u"Tirage au sort le 7 octobre !", "choices": choices})
BdaFormSet = inlineformset_factory(Participant, ChoixSpectacle, fields = ("spectacle","double","autoquit","priority",), formset = BaseBdaFormSet)
participant, created = Participant.objects.get_or_create(user = request.user)
success = False
stateerror = False
if request.method == "POST":
formset = BdaFormSet(request.POST, instance = participant)
if formset.is_valid():
#ChoixSpectacle.objects.filter(participant = participant).delete()
formset.save()
success = True
dbstate = _hash_queryset(participant.choixspectacle_set.all())
if "dbstate" in request.POST and dbstate != request.POST["dbstate"]:
stateerror = True
formset = BdaFormSet(instance = participant)
else:
formset = BdaFormSet(request.POST, instance = participant)
if formset.is_valid():
#ChoixSpectacle.objects.filter(participant = participant).delete()
formset.save()
success = True
formset = BdaFormSet(instance = participant)
else:
formset = BdaFormSet(instance = participant)
dbstate = _hash_queryset(participant.choixspectacle_set.all())
total_price = 0
for choice in participant.choixspectacle_set.all():
total_price += choice.spectacle.price
if choice.double: total_price += choice.spectacle.price
return render(request, "inscription-bda.html", {"formset": formset, "success": success, "total_price": total_price})
Spectacle.deficit = lambda x: (x.slots-x.nrequests)*x.price
return render(request, "inscription-bda.html", {"formset": formset, "success": success, "total_price": total_price, "dbstate": dbstate, "stateerror": stateerror})
def do_tirage(request):
form = TokenForm(request.POST)
if not form.is_valid():
return tirage(request)
start = time.time()
data = {}
shows = Spectacle.objects.all()
shows = Spectacle.objects.select_related().all()
members = Participant.objects.all()
choices = ChoixSpectacle.objects.all()
choices = ChoixSpectacle.objects.order_by('participant', 'priority').select_related().all()
algo = Algorithm(shows, members, choices)
results = algo(form.cleaned_data["token"])
total_slots = 0
@ -99,8 +150,8 @@ def do_tirage(request):
total_sold = 0
total_deficit = 0
opera_deficit = 0
for show in shows:
deficit = show.deficit()
for (show, members, _) in results:
deficit = (show.slots - len(members)) * show.price
total_sold += show.slots * show.price
if deficit >= 0:
if u"Opéra" in show.location.name:
@ -109,18 +160,23 @@ def do_tirage(request):
data["total_sold"] = total_sold - total_deficit
data["total_deficit"] = total_deficit
data["opera_deficit"] = opera_deficit
data["duration"] = time.time() - start
if request.user.is_authenticated():
members2 = {}
members_uniq = {} # Participant objects are not shared accross spectacle results,
# So assign a single object for each Participant id
for (show, members, _) in results:
for (member, _, _, _) in members:
if member not in members2:
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)
if False and request.user.username == "seguin":
if False and request.user.username in ["seguin", "harazi"]:
Attribution.objects.all().delete()
for (show, members, _) in results:
for (member, _, _, _) in members:
@ -159,15 +215,27 @@ def do_resell(request, form):
spectacle = form.cleaned_data["spectacle"]
count = form.cleaned_data["count"]
places = "2 places" if count == "2" else "une place"
"""
send_custom_mail("bda-revente@lists.ens.fr",
"bda-revente",
{"places": places,
"spectacle": spectacle.title,
"date": spectacle.date_no_seconds(),
"lieu": spectacle.location,
"prix": spectacle.price,
"revendeur": request.user.get_full_name(),
"revendeur_mail": request.user.email},
from_email = request.user.email)
"""
mail = u"""Bonjour,
Je souhaite revendre %s pour %s le %s (%s) à %.02f.
Contactez moi par email si vous êtes intéressés !
Contactez moi par email si vous êtes intéressé·e·s !
%s (%s)""" % (places, spectacle.title, spectacle.date_no_seconds(), spectacle.location, spectacle.price, request.user.get_full_name(), request.user.email)
send_mail("Revente de place: %s" % spectacle, mail,
send_mail("%s" % spectacle, mail,
request.user.email, ["bda-revente@lists.ens.fr"],
fail_silently = True)
fail_silently = False)
return render(request, "bda-success.html", {"show": spectacle, "places": places})
@login_required

View file

@ -55,6 +55,9 @@ class EventOptionChoiceInline(admin.TabularInline):
class EventOptionInline(admin.TabularInline):
model = EventOption
class EventCommentFieldInline(admin.TabularInline):
model = EventCommentField
class EventOptionAdmin(admin.ModelAdmin):
inlines = [
EventOptionChoiceInline,
@ -63,6 +66,7 @@ class EventOptionAdmin(admin.ModelAdmin):
class EventAdmin(admin.ModelAdmin):
inlines = [
EventOptionInline,
EventCommentFieldInline,
]
#from eav.forms import BaseDynamicEntityForm
@ -253,6 +257,8 @@ admin.site.register(EventOption, EventOptionAdmin)
admin.site.unregister(User)
admin.site.register(User, UserProfileAdmin)
admin.site.register(CofProfile)
admin.site.register(Club)
admin.site.register(CustomMail)
admin.site.register(PetitCoursSubject)
admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin)
admin.site.register(PetitCoursAttribution, PetitCoursAttributionAdmin)

View file

@ -24,6 +24,11 @@ TYPE_COTIZ_CHOICES = (
('exterieur', _(u"Extérieur")),
)
TYPE_COMMENT_FIELD = (
('text', _(u"Texte long")),
('char', _(u"Texte court")),
)
def choices_length (choices):
return reduce (lambda m, choice: max (m, len (choice[0])), choices, 0)
@ -45,6 +50,7 @@ class CofProfile(models.Model):
mailing_cof = models.BooleanField("Recevoir les mails COF", default = False)
mailing_bda = models.BooleanField("Recevoir les mails BdA", default = False)
mailing_bda_revente = models.BooleanField("Recevoir les mails de revente de places BdA", default = False)
comments = models.TextField("Commentaires visibles uniquement par le Buro", blank = True)
is_buro = models.BooleanField("Membre du Burô", default = False)
petits_cours_accept = models.BooleanField("Recevoir des petits cours", default = False)
petits_cours_remarques = models.TextField(_(u"Remarques et précisions pour les petits cours"),
@ -62,6 +68,24 @@ def create_user_profile(sender, instance, created, **kwargs):
CofProfile.objects.get_or_create(user = instance)
post_save.connect(create_user_profile, sender = User)
class Club(models.Model):
name = models.CharField("Nom", max_length = 200)
description = models.TextField("Description")
respos = models.ManyToManyField(User, related_name = "clubs_geres")
membres = models.ManyToManyField(User, related_name = "clubs")
class CustomMail(models.Model):
shortname = models.SlugField(max_length = 50, blank = False)
title = models.CharField("Titre", max_length = 200, blank = False)
content = models.TextField("Contenu", blank = False)
comments = models.TextField("Informations contextuelles sur le mail", blank = True)
class Meta:
verbose_name = "Mails personnalisables"
def __unicode__(self):
return u"%s: %s" % (self.shortname, self.title)
class Event(models.Model):
title = models.CharField("Titre", max_length = 200)
location = models.CharField("Lieu", max_length = 200)
@ -77,6 +101,23 @@ class Event(models.Model):
def __unicode__(self):
return unicode(self.title)
class EventCommentField(models.Model):
event = models.ForeignKey(Event, related_name = "commentfields")
name = models.CharField("Champ", max_length = 200)
fieldtype = models.CharField("Type", max_length = 10, choices = TYPE_COMMENT_FIELD, default = "text")
default = models.TextField("Valeur par défaut", blank = True)
class Meta:
verbose_name = "Champ"
def __unicode__(self):
return unicode(self.name)
class EventCommentValue(models.Model):
commentfield = models.ForeignKey(EventCommentField, related_name = "values")
registration = models.ForeignKey("EventRegistration", related_name = "comments")
content = models.TextField("Contenu", blank = True, null = True)
class EventOption(models.Model):
event = models.ForeignKey(Event, related_name = "options")
name = models.CharField("Option", max_length = 200)
@ -102,6 +143,7 @@ class EventRegistration(models.Model):
user = models.ForeignKey(User)
event = models.ForeignKey(Event)
options = models.ManyToManyField(EventOptionChoice)
filledcomments = models.ManyToManyField(EventCommentField, through = EventCommentValue)
paid = models.BooleanField("A payé", default = False)
class Meta:

View file

@ -60,38 +60,45 @@ def _get_attrib_counter(user, matiere):
counter.save()
return counter
@buro_required
def traitement(request, demande_id):
demande = get_object_or_404(PetitCoursDemande, id = demande_id)
if demande.niveau == "other":
return _traitement_other(request, demande)
if request.method == "POST":
return _traitement_post(request, demande)
proposals = {}
proposed_for = {}
unsatisfied = []
attribdata = {}
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)
candidates = candidates.values_list('user', flat = True).distinct().order_by('?').all()
tuples = []
for candidate in candidates:
user = User.objects.get(pk = candidate)
tuples.append((candidate, _get_attrib_counter(user, matiere)))
if tuples:
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
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 _get_demande_candidates(demande, redo):
if candidates:
tuples = []
for candidate in candidates:
user = candidate.user
tuples.append((candidate, _get_attrib_counter(user, matiere)))
tuples = sorted(tuples, key = lambda c: c[1].count)
candidates, _ = zip(*tuples)
attribdata[matiere.id] = []
candidates = candidates[0:min(3, len(candidates))]
attribdata[matiere.id] = []
proposals[matiere] = []
for candidate in candidates:
user = User.objects.get(pk = candidate)
user = candidate.user
proposals[matiere].append(user)
attribdata[matiere.id].append(user.id)
if user not in proposed_for:
@ -100,10 +107,15 @@ def traitement(request, demande_id):
proposed_for[user].append(matiere)
else:
unsatisfied.append(matiere)
return _finalize_traitement(request, demande, proposals, proposed_for, unsatisfied, attribdata)
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):
unsatisfied, attribdata, redo = False, errors = None):
proposals = proposals.items()
proposed_for = proposed_for.items()
attribdata = attribdata.items()
@ -121,6 +133,7 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
"mainmail": mainmail,
"attribdata": base64.b64encode(simplejson.dumps(attribdata)),
"redo": redo,
"errors": errors,
})
def _generate_eleve_email(demande, proposed_for):
@ -130,15 +143,45 @@ def _generate_eleve_email(demande, proposed_for):
proposed_mails.append((user, msg))
return proposed_mails
def _traitement_other_post(request, demande):
for matiere in demande.matieres.all():
for choice_id in range(3):
choice = request.POST["proposal-%d-%d"] % (matiere.id, choice_id)]:
pass
return None
return _finalize_traitement(request, demande, proposals, proposed_for, unsatisfied, attribdata)
def _traitement_other_preparing(request, demande):
redo = "redo" in request.POST
unsatisfied = []
proposals = {}
proposed_for = {}
attribdata = {}
errors = []
for matiere, candidates in _get_demande_candidates(demande, 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" % (matiere.id, choice_id)])
if choice == -1:
continue
if choice not in candidates:
errors.append(u"Choix invalide pour la proposition %d en %s" % (choice_id + 1, matiere))
continue
user = candidates[choice]
if user in proposals[matiere]:
errors.append(u"La proposition %d en %s est un doublon" % (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(u"Aucune proposition pour %s" % (matiere,))
elif len(proposals[matiere]) < 3:
errors.append(u"Seulement %d proposition%s pour %s" % (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):
def _traitement_other(request, demande, redo):
if request.method == "POST":
if "preparing" in request.POST:
return _traitement_other_preparing(request, demande)
@ -148,13 +191,7 @@ def _traitement_other(request, demande):
proposed_for = {}
unsatisfied = []
attribdata = {}
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)
candidates = candidates.order_by('?').select_related().all()
for matiere, candidates in _get_demande_candidates(demande, redo):
if candidates:
tuples = []
for candidate in candidates:
@ -243,49 +280,6 @@ def _traitement_post(request, demande):
"redo": redo,
})
@buro_required
def retraitement(request, demande_id):
demande = get_object_or_404(PetitCoursDemande, id = demande_id)
if request.method == "POST":
return _traitement_post(request, demande)
if demande.niveau == "other":
return _traitement_other(request, demande)
proposals = {}
proposed_for = {}
unsatisfied = []
attribdata = {}
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)
attributions = PetitCoursAttribution.objects.filter(demande = demande, matiere = matiere).all()
for attrib in attributions:
candidates = candidates.exclude(user = attrib.user)
candidates = candidates.values_list('user', flat = True).distinct().order_by('?').all()
tuples = []
for candidate in candidates:
user = User.objects.get(pk = candidate)
tuples.append((candidate, _get_attrib_counter(user, matiere)))
if tuples:
tuples = sorted(tuples, key = lambda c: c[1].count)
candidates, _ = zip(*tuples)
attribdata[matiere.id] = []
candidates = candidates[0:min(3, len(candidates))]
proposals[matiere] = []
for candidate in candidates:
user = User.objects.get(pk = candidate)
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 = True)
class BaseMatieresFormSet(BaseInlineFormSet):
def clean(self):
super(BaseMatieresFormSet, self).clean()

View file

@ -2,9 +2,12 @@ from django.contrib.sites.models import Site
from django.conf import settings
from django_cas.backends import CASBackend, _verify as CASverify
from django_cas.models import User
from django.contrib.auth.models import User as DjangoUser
from django.db import models, connection
from django.core.mail import send_mail
from django.template import Template, Context
from gestioncof.models import CofProfile
from gestioncof.models import CofProfile, CustomMail
class COFCASBackend(CASBackend):
def authenticate_cas(self, ticket, service, request):
@ -76,3 +79,16 @@ def unlock_tables(*models):
return row
unlock_table = unlock_tables
def send_custom_mail(to, shortname, context = None, from_email = "cof@ens.fr"):
if context is None: context = {}
if isinstance(to, DjangoUser):
context["nom"] = to.get_full_name()
context["prenom"] = to.first_name
to = to.email
mail = CustomMail.objects.get(shortname = shortname)
template = Template(mail.content)
message = template.render(Context(context))
send_mail (mail.title, message,
from_email, [to],
fail_silently = True)

View file

@ -15,10 +15,11 @@ from django.contrib.auth.views import login as django_login_view
from gestioncof.models import Survey, SurveyQuestion, SurveyQuestionAnswer, SurveyAnswer
from gestioncof.models import Event, EventOption, EventOptionChoice, EventRegistration
from gestioncof.models import EventCommentField, EventCommentValue
from gestioncof.models import CofProfile, Clipper
from gestioncof.decorators import buro_required, cof_required
from gestioncof.widgets import TriStateCheckbox
from gestioncof.shared import lock_table, unlock_table
from gestioncof.shared import lock_table, unlock_table, send_custom_mail
@login_required
def home(request):
@ -205,6 +206,17 @@ def get_event_form_choices(event, form):
all_choices.append(choice)
return all_choices
def update_event_form_comments(event, form, registration):
for commentfield_id, value in form.comments():
field = get_object_or_404(EventCommentField, id = commentfield_id,
event = event)
if value == field.default:
continue
(storage, _) = EventCommentValue.objects.get_or_create(commentfield = field,
registration = registration)
storage.content = value
storage.save()
@login_required
def event(request, event_id):
event = get_object_or_404(Event, id = event_id)
@ -426,6 +438,7 @@ class RegistrationProfileForm(forms.ModelForm):
'mailing_cof',
'mailing_bda',
'mailing_bda_revente',
'comments'
]
def save(self, *args, **kw):
@ -445,7 +458,7 @@ class RegistrationProfileForm(forms.ModelForm):
class Meta:
model = CofProfile
fields = ("login_clipper", "num", "phone", "occupation", "departement", "is_cof", "type_cotiz", "mailing_cof", "mailing_bda", "mailing_bda_revente",)
fields = ("login_clipper", "num", "phone", "occupation", "departement", "is_cof", "type_cotiz", "mailing_cof", "mailing_bda", "mailing_bda_revente", "comments")
def registration_set_ro_fields(user_form, profile_form):
user_form.fields['username'].widget.attrs['readonly'] = True
@ -487,15 +500,16 @@ def registration_form(request, login_clipper = None, username = None):
return render(request, "registration_form.html", {"user_form": user_form, "profile_form": profile_form, "member": member, "login_clipper": login_clipper})
STATUS_CHOICES = (('no','Non'),
('wait','Attente paiement'),
('paid','Payé'),)
('wait','Oui mais attente paiement'),
('paid','Oui payé'),)
class AdminEventForm(forms.Form):
status = forms.ChoiceField(label = "Inscription", choices = STATUS_CHOICES, widget = RadioSelect)
def __init__(self, *args, **kwargs):
event = kwargs.pop("event")
self.event = event
current_choices = kwargs.pop("current_choices", None)
registration = kwargs.pop("current_registration", None)
current_choices = registration.options.all() if registration is not None else []
paid = kwargs.pop("paid", None)
if paid == True:
kwargs["initial"] = {"status":"paid"}
@ -505,12 +519,12 @@ class AdminEventForm(forms.Form):
kwargs["initial"] = {"status":"no"}
super(AdminEventForm, self).__init__(*args, **kwargs)
choices = {}
if current_choices:
for choice in current_choices.all():
if choice.event_option.id not in choices:
choices[choice.event_option.id] = [choice.id]
else:
choices[choice.event_option.id].append(choice.id)
comments = {}
for choice in current_choices:
if choice.event_option.id not in choices:
choices[choice.event_option.id] = [choice.id]
else:
choices[choice.event_option.id].append(choice.id)
all_choices = choices
for option in event.options.all():
choices = [(choice.id, choice.value) for choice in option.choices.all()]
@ -530,12 +544,31 @@ class AdminEventForm(forms.Form):
initial = initial)
field.option_id = option.id
self.fields["option_%d" % option.id] = field
for commentfield in event.commentfields.all():
initial = commentfield.default
if registration is not None:
try:
initial = registration.comments.get(commentfield = commentfield).content
except EventCommentValue.DoesNotExist:
pass
widget = forms.Textarea if commentfield.fieldtype == "text" else forms.TextInput
field = forms.CharField(label = commentfield.name,
widget = widget,
required = False,
initial = initial)
field.comment_id = commentfield.id
self.fields["comment_%d" % commentfield.id] = field
def choices(self):
for name, value in self.cleaned_data.items():
if name.startswith('option_'):
yield (self.fields[name].option_id, value)
def comments(self):
for name, value in self.cleaned_data.items():
if name.startswith('comment_'):
yield (self.fields[name].comment_id, value)
@buro_required
def registration_form2(request, login_clipper = None, username = None):
events = Event.objects.filter(old = False).all()
@ -571,7 +604,7 @@ def registration_form2(request, login_clipper = None, username = None):
for event in events:
try:
current_registration = EventRegistration.objects.get(user = member, event = event)
form = AdminEventForm(event = event, current_choices = current_registration.options, paid = current_registration.paid)
form = AdminEventForm(event = event, current_registration = current_registration, paid = current_registration.paid)
except EventRegistration.DoesNotExist:
form = AdminEventForm(event = event)
event_forms.append(form)
@ -618,10 +651,14 @@ def registration(request):
if user_form.is_valid() and profile_form.is_valid() and not any([not form.is_valid() for form in event_forms]):
member = user_form.save()
(profile, _) = CofProfile.objects.get_or_create(user = member)
was_cof = profile.is_cof
request_dict["num"] = profile.num
profile_form = RegistrationProfileForm(request_dict, instance = profile)
profile_form.is_valid()
profile_form.save()
(profile, _) = CofProfile.objects.get_or_create(user = member)
if profile.is_cof and not was_cof:
send_custom_mail(member, "bienvenue")
for form in event_forms:
if form.cleaned_data['status'] == 'no':
try:
@ -631,10 +668,18 @@ def registration(request):
pass
continue
all_choices = get_event_form_choices(form.event, form)
(current_registration, _) = EventRegistration.objects.get_or_create(user = member, event = form.event)
(current_registration, created_reg) = EventRegistration.objects.get_or_create(user = member, event = form.event)
update_event_form_comments(event, form, current_registration)
current_registration.options = all_choices
current_registration.paid = (form.cleaned_data['status'] == 'paid')
current_registration.save()
if event.title == "Mega 2014" and created_reg:
field = EventCommentField.objects.get(event = event, name = "Commentaires")
try:
comments = EventCommentValue.objects.get(commentfield = field, registration = current_registration).content
except EventCommentValue.DoesNotExist:
comments = field.default
send_custom_mail(member, "mega", {"remarques": comments})
success = True
return render(request, "registration_post.html", {"success": success, "user_form": user_form, "profile_form": profile_form, "member": member, "login_clipper": login_clipper, "event_forms": event_forms})
else:
@ -653,54 +698,76 @@ def export_members(request):
return response
def csv_export_mega(filename, qs):
response = HttpResponse(mimetype = 'text/csv')
response['Content-Disposition'] = 'attachment; filename=' + filename
writer = unicodecsv.UnicodeWriter(response)
for reg in qs.all():
user = reg.user
profile = user.get_profile()
comments = "---".join([comment.content for comment in reg.comments.all()])
bits = [user.username, user.first_name, user.last_name, user.email, profile.phone, profile.num, profile.comments if profile.comments else "", comments]
writer.writerow([unicode(bit) for bit in bits])
return response
@buro_required
def export_mega_remarksonly(request):
filename = 'remarques_mega_2014.csv'
response = HttpResponse(mimetype = 'text/csv')
response['Content-Disposition'] = 'attachment; filename=' + filename
writer = unicodecsv.UnicodeWriter(response)
event = Event.objects.get(title = "Mega 2014")
commentfield = event.commentfields.get(name = "Commentaires")
for val in commentfield.values.all():
reg = val.registration
user = reg.user
profile = user.get_profile()
bits = [user.username, user.first_name, user.last_name, user.email, profile.phone, profile.num, profile.comments, val.content]
writer.writerow([unicode(bit) for bit in bits])
return response
@buro_required
def export_mega_bytype(request, type):
types = {"orga-actif": "Orga actif",
"orga-branleur": "Orga branleur",
"conscrit-eleve": "Conscrit élève",
"conscrit-etudiant": "Conscrit étudiant"}
if type not in types:
raise Http404
event = Event.objects.get(title = "Mega 2014")
type_option = event.options.get(name = "Type")
participant_type = type_option.choices.get(value = types[type]).id
qs = EventRegistration.objects.filter(event = event).filter(options__id__exact = participant_type)
return csv_export_mega(type + '_mega_2014.csv', qs)
@buro_required
def export_mega_orgas(request):
response = HttpResponse(mimetype = 'text/csv')
response['Content-Disposition'] = 'attachment; filename=orgas_mega.csv'
writer = unicodecsv.UnicodeWriter(response)
event = Event.objects.get(title = "MEGA")
event = Event.objects.get(title = "Mega 2014")
type_option = event.options.get(name = "Type")
participant_type = type_option.choices.get(value = "Participant").id
for reg in EventRegistration.objects.filter(event = event).exclude(options__id__exact = participant_type).all():
user = reg.user
profile = user.get_profile()
bits = [user.username, user.first_name, user.last_name, user.email, profile.phone, profile.num]
writer.writerow([unicode(bit) for bit in bits])
participant_type_a = type_option.choices.get(value = "Conscrit étudiant").id
participant_type_b = type_option.choices.get(value = "Conscrit élève").id
qs = EventRegistration.objects.filter(event = event).exclude(options__id__in = (participant_type_a, participant_type_b))
return csv_export_mega('orgas_mega_2014.csv', qs)
return response
@buro_required
def export_mega_participants(request):
response = HttpResponse(mimetype = 'text/csv')
response['Content-Disposition'] = 'attachment; filename=participants_mega.csv'
writer = unicodecsv.UnicodeWriter(response)
event = Event.objects.get(title = "MEGA")
event = Event.objects.get(title = "Mega 2014")
type_option = event.options.get(name = "Type")
participant_type = type_option.choices.get(value = "Participant").id
for reg in EventRegistration.objects.filter(event = event).filter(options__id__exact = participant_type).all():
user = reg.user
profile = user.get_profile()
bits = [user.username, user.first_name, user.last_name, user.email, profile.phone, profile.num]
writer.writerow([unicode(bit) for bit in bits])
return response
participant_type_a = type_option.choices.get(value = "Conscrit étudiant").id
participant_type_b = type_option.choices.get(value = "Conscrit élève").id
qs = EventRegistration.objects.filter(event = event).filter(options__id__in = (participant_type_a, participant_type_b))
return csv_export_mega('participants_mega_2014.csv', qs)
@buro_required
def export_mega(request):
response = HttpResponse(mimetype = 'text/csv')
response['Content-Disposition'] = 'attachment; filename=all_mega.csv'
writer = unicodecsv.UnicodeWriter(response)
event = Event.objects.filter(title = "MEGA")
for reg in EventRegistration.objects.filter(event = event).order_by("user__username").all():
user = reg.user
profile = user.get_profile()
bits = [user.username, user.first_name, user.last_name, user.email, profile.phone, profile.num]
writer.writerow([unicode(bit) for bit in bits])
return response
event = Event.objects.filter(title = "Mega 2014")
qs = EventRegistration.objects.filter(event = event).order_by("user__username")
return csv_export_mega('all_mega_2014.csv', qs)
@buro_required
def utile_cof(request):

0
manage.py Executable file → Normal file
View file

View file

@ -176,8 +176,8 @@ fieldset legend {
}
#main-container {
max-width: 90%;
width: 800px;
max-width: 95%;
width: 1000px;
margin: 7em auto;
display: block;
}
@ -588,3 +588,47 @@ pre code {
max-height: 340px;
overflow-y: scroll;
}
/* Louis pour etat places*/
.etat-bda td {
border:1px solid #666;
padding:4px;
}
.etat-bda tr:nth-child(even) {background: #CCC}
.greenratio {
background-color: #3F3;
border: 5px solid #ccc;
border: 5px solid rgba(0, 0, 0, 0.15);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.orangeratio {
background-color: #FF3;
border: 5px solid #ccc;
border: 5px solid rgba(0, 0, 0, 0.15);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.redratio {
background-color: #F33;
border: 5px solid #ccc;
border: 5px solid rgba(0, 0, 0, 0.15);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
th[data-sort]{
cursor:pointer;
}
tr.awesome{
color: red;
}

View file

@ -10,6 +10,7 @@
<h2>Token : {{ token }}</h2>
<h2>Placés : {{ total_slots }} ; Déçus : {{ total_losers }}</h2>
{% if user.get_profile.is_buro %}<h2>Déficit total: {{ total_deficit }} €, Opéra: {{ opera_deficit }} €, Attribué: {{ total_sold }} €</h2>{% endif %}
<h2>Temps de calcul : {{ duration|floatformat }}s</h2>
{% for show, members, losers in results %}
<div class="attribresult">

View file

@ -1,11 +1,49 @@
{% extends "base_title.html" %}
{% block realcontent %}
<h2>Etat des inscriptions BDA</h2>
<ul>
{% for spectacle in spectacles %}
<li>{{ spectacle.title }} (<span style="font-size: 0.5em;">{{ spectacle.date_no_seconds }}, {{ spectacle.location }}</span>, {{ spectacle.slots }} places) : <strong>{{ spectacle.total }} demandes</strong></li>
<h2>État des inscriptions BdA</h2>
<table class="etat-bda">
<thead>
<tr>
<th data-sort="string">Titre</th>
<th data-sort="int">Date</th>
<th data-sort="string">Lieu</th>
<th data-sort="int">Places</th>
<th data-sort="int">Demandes</th>
<th data-sort="float">Ratio</th>
</tr>
</thead>
<tbody>
{% for spectacle in spectacles %}
<tr>
<td>{{ spectacle.title }}</td>
<td data-sort-value="{{ spectacle.timestamp }}">{{ spectacle.date_no_seconds }}</td>
<td data-sort-value="{{ spectacle.location }}">{{ spectacle.location }}</td>
<td data-sort-value="{{ spectacle.slots }}">{{ spectacle.slots }} places</td>
<td data-sort-value="{{ spectacle.total }}">{{ spectacle.total }} demandes</td>
<td data-sort-value="{{ spectacle.ratio |stringformat:".3f" }}">
<div class={% if spectacle.ratio < 1.0 %}
"greenratio"
{% else %}
{% if spectacle.ratio < 2.5 %}
"orangeratio"
{% else %}
"redratio"
{% endif %}
{% endif %}>
{{ spectacle.ratio |floatformat }}
</div>
</td>
</tr>
{% endfor %}
</ul>
</tbody>
</table>
<strong>Total : <u>{{ total }} demandes</u></strong>
<script type="text/javascript" src="/gestion/media/js/jquery.min.js"></script>
<script type="text/javascript" src="/gestion/media/js/joequery-Stupid-Table-Plugin/stupidtable.js"></script>
<script type="text/javascript">
$(function(){
$("table.etat-bda").stupidtable();
});
</script>
{% endblock %}

View file

@ -91,10 +91,13 @@ var django = {
})(django.jQuery);
</script>
<h2>Inscription au tirage au sort du BDA</h2>
<h2>Inscription au tirage au sort du BdA</h2>
{% if success %}
<p class="success">Votre inscription a été mise à jour avec succès !</p>
{% endif %}
{% if stateerror %}
<p class="error">Impossible d'enregistrer vos modifications: vous avez apporté d'autres modifications entre temps</p>
{% endif %}
<form id="bda_form" method="post" action="{% url 'bda-tirage-inscription' %}">
{% csrf_token %}
{% include "inscription-formset.html" %}
@ -104,6 +107,7 @@ var django = {
cloneMore('tbody.bda_formset_content tr:last-child', 'choixspectacle_set');
});
</script>
<input type="hidden" name="dbstate" value="{{ dbstate }}" />
<input type="submit" class="btn-submit" value="Enregistrer" />
Prix total actuel : {{ total_price }}€
<hr />

View file

@ -22,14 +22,33 @@
</ul>
{% endif %}
{% if user.profile.is_cof %}
<h3>BdA</h3>
<ul>
<li><a href="{% url "bda-tirage-inscription" %}">Inscription au tirage au sort du BdA</a></li>
<li><a href="{% url "bda.views.etat_places" %}">Etat des demandes</a></li>
<!-- <li><a href="{% url "bda.views.revente" %}">Revente de places BdA (premier tirage)</a></li> -->
<!-- <li><a href="{% url "bda2.views.revente" %}">Revente de places BdA (second tirage)</a></li> -->
Premier tirage
<!-- <li><a href="{% url "bda-tirage-inscription" %}">Inscription au premier tirage au sort du BdA</a></li> -->
<li><a href="{% url "bda-etat-places" %}">État des demandes</a></li>
<li><a href="{% url "bda-places-attribuees" %}">Mes places du premier tirage</a></li>
<li><a href="{% url "bda-revente" %}">Revendre une place du premier tirage</a></li>
<br>
<!-- Second tirage
<li><a href="{% url "bda2-tirage-inscription" %}">Inscription au deuxième tirage au sort du BdA</a></li>
<li><a href="{% url "bda2-etat-places" %}">État des demandes</a></li>
<li><a href="{% url "bda2-places-attribuees" %}">Mes places du deuxième tirage</a></li>
<li><a href="{% url "bda2-revente" %}">Revendre une place du deuxième tirage</a></li>
<br>
-->
Troisième tirage
<li><a href="{% url "bda3-tirage-inscription" %}">Inscription au troisième tirage au sort du BdA</a></li>
<li><a href="{% url "bda3-etat-places" %}">État des demandes</a></li>
<li><a href="{% url "bda3-places-attribuees" %}">Mes places du troisième tirage</a></li>
<li><a href="{% url "bda3-revente" %}">Revendre une place du troisième tirage</a></li>
<br>
</ul>
{% endif %}
<h3>Divers</h3>
<ul>

View file

@ -12,8 +12,8 @@
<h3>Mega</h3>
<ul>
<li><a href="{% url 'gestioncof.views.export_mega_participants' %}">Export des participants</a></li>
<li><a href="{% url 'gestioncof.views.export_mega_orgas' %}">Export des orgas</a></li>
<li><a href="{% url 'gestioncof.views.export_mega_participants' %}">Export des non-orgas uniquement</a></li>
<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>
{% endblock %}