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.contenttypes.models import ContentType
from django.contrib import admin 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 from bda.models import Spectacle, Salle, Participant, ChoixSpectacle, Attribution
class ChoixSpectacleInline(admin.TabularInline): class ChoixSpectacleInline(admin.TabularInline):
@ -17,13 +17,18 @@ class AttributionInline(admin.TabularInline):
class ParticipantAdmin(admin.ModelAdmin): class ParticipantAdmin(admin.ModelAdmin):
#inlines = [ChoixSpectacleInline] #inlines = [ChoixSpectacleInline]
inlines = [AttributionInline] inlines = [AttributionInline]
def queryset(self, request):
return Participant.objects.annotate(nb_places = Count('attributions'),
total = Sum('attributions__price'))
def nb_places(self, obj): 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" nb_places.short_description = "Nombre de places"
def total(self, obj): def total(self, obj):
tot = obj.attributions.aggregate(total = Sum('price'))['total'] tot = obj.total
if tot: return u"%.02f" % tot if tot: return u"%.02f" % tot
else: return u"0 €" else: return u"0 €"
total.admin_order_field = "total"
total.short_description = "Total à payer" total.short_description = "Total à payer"
list_display = ("user", "nb_places", "total", "paid", "paymenttype") list_display = ("user", "nb_places", "total", "paid", "paymenttype")
list_filter = ("paid",) list_filter = ("paid",)
@ -56,7 +61,7 @@ Voici tes choix de spectacles tels que notre système les a enregistrés :\n\n""
Artistiquement, Artistiquement,
Le BdA""" Le BdA"""
send_mail ("Choix de spectacles (BdA du COF)", mail, 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) fail_silently = True)
count = len(queryset.all()) count = len(queryset.all())
if count == 1: if count == 1:
@ -81,35 +86,48 @@ pour les spectacles suivants :
%s %s
*Paiement* *Paiement*
Ces spectacles sont à régler avant le vendredi 19 Octobre, pendant les L'intégralité de ces places de spectacles est à régler à partir du jeudi
heures de permanences du COF (tous les jours de la semaine entre 12h et 10 octobre et AVANT le mercredi 23 octobre, au bureau du COF pendant les
14h, et entre 18h et 20h). Des facilités de paiement sont bien évidemment heures de permanences (du lundi au vendredi entre 12h et 14h, et entre 18h
possibles (encaissement échelonné des chèques). 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* *Mode de retrait des places*
Pour l'Opéra de Paris, le théâtre de la Colline et le théâtre du Châtelet, Au moment du paiement, une enveloppe vous sera remise, contenant les
les places sont à retirer au COF le jour du paiement. 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 Pour les concerts Radio France, le Théâtre des Champs-Élysées, le théâtre
Pleyel, les places seront nominatives et à retirer au théâtre le soir de du Rond-Point, le théâtre de la Colline, le théâtre de l'Athénée, l'IRCAM,
la représentation au moins une demi-heure avant le début du spectacle. 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, Pour le théâtre de l'Odéon, la salle Richelieu le théâtre du Vieux
le théâtre de Chaillot et l'IRCAM, les places seront distribuées dans vos colombier de la Comédie française, certains spectacles du théâtre de la
casiers environ une semaine avant la représentation (un mail vous en Ville et du théâtre de Châtelet ainsi que pour le théâtre de Chaillot, les
avertira). 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 = "" attribs_text = ""
name = member.user.get_full_name() name = member.user.get_full_name()
for attrib in attribs: for attrib in attribs:
attribs_text += u"- 1 place pour %s\n" % attrib attribs_text += u"- 1 place pour %s\n" % attrib
mail = mail % (name, attribs_text) mail = mail % (name, attribs_text)
send_mail ("Places de spectacle (BdA du COF)", mail, send_mail ("Résultats du tirage au sort", mail,
"bda@clipper.ens.fr", [member.user.email], "bda@ens.fr", [member.user.email],
fail_silently = True) fail_silently = True)
count = len(queryset.all()) count = len(queryset.all())
if count == 1: if count == 1:
@ -136,7 +154,13 @@ class ChoixSpectacleAdmin(admin.ModelAdmin):
list_filter = ("double", "autoquit") list_filter = ("double", "autoquit")
search_fields = ('participant__user__username', 'participant__user__first_name', 'participant__user__last_name') 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(Salle)
admin.site.register(Participant, ParticipantAdmin) admin.site.register(Participant, ParticipantAdmin)
admin.site.register(Attribution, AttributionAdmin) admin.site.register(Attribution, AttributionAdmin)

View file

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

View file

@ -1,5 +1,7 @@
# coding: utf-8 # coding: utf-8
import calendar
from django.db import models from django.db import models
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 _
@ -29,11 +31,14 @@ class Spectacle (models.Model):
def __repr__ (self): def __repr__ (self):
return u"[%s]" % self.__unicode__() return u"[%s]" % self.__unicode__()
def timestamp(self):
return "%d" % calendar.timegm(self.date.utctimetuple())
def date_no_seconds(self): def date_no_seconds(self):
return self.date.strftime('%d %b %Y %H:%M') return self.date.strftime('%d %b %Y %H:%M')
def __unicode__ (self): 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 = ( PAYMENT_TYPES = (
("cash",u"Cash"), ("cash",u"Cash"),

View file

@ -1,17 +1,22 @@
# coding: utf-8 # coding: utf-8
from django.contrib.auth.models import User
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.db import models from django.db import models
from django.http import Http404 from django.http import Http404
from django import forms from django import forms
from django.forms.models import inlineformset_factory, BaseInlineFormSet from django.forms.models import inlineformset_factory, BaseInlineFormSet
from django.core import serializers
import hashlib
from django.core.mail import send_mail from django.core.mail import send_mail
from datetime import datetime
import time import time
from gestioncof.decorators import cof_required, buro_required 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.models import Spectacle, Participant, ChoixSpectacle, Attribution
from bda.algorithm import Algorithm from bda.algorithm import Algorithm
@ -42,23 +47,69 @@ def etat_places(request):
total = 0 total = 0
for spectacle in spectacles: for spectacle in spectacles:
spectacle.total = 0 spectacle.total = 0
spectacle.ratio = -1.0
spectacles_dict[spectacle.id] = spectacle spectacles_dict[spectacle.id] = spectacle
for spectacle in spectacles1: for spectacle in spectacles1:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"] 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"] total += spectacle["total"]
for spectacle in spectacles2: for spectacle in spectacles2:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"] 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"] total += spectacle["total"]
return render(request, "etat-places.html", {"spectacles": spectacles, "total": 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 @cof_required
def inscription(request): def inscription(request):
if False and time.time() > 1349474400: if datetime.now() > datetime(2013, 10, 6, 23, 59):
return render(request, "error.html", {"error_title": "C'est fini !", "error_description": u"Tirage au sort le 6 octobre dans la soirée "}) 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) BdaFormSet = inlineformset_factory(Participant, ChoixSpectacle, fields = ("spectacle","double","autoquit","priority",), formset = BaseBdaFormSet)
participant, created = Participant.objects.get_or_create(user = request.user) participant, created = Participant.objects.get_or_create(user = request.user)
success = False success = False
stateerror = False
if request.method == "POST": if request.method == "POST":
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) formset = BdaFormSet(request.POST, instance = participant)
if formset.is_valid(): if formset.is_valid():
#ChoixSpectacle.objects.filter(participant = participant).delete() #ChoixSpectacle.objects.filter(participant = participant).delete()
@ -67,22 +118,22 @@ def inscription(request):
formset = BdaFormSet(instance = participant) formset = BdaFormSet(instance = participant)
else: else:
formset = BdaFormSet(instance = participant) formset = BdaFormSet(instance = participant)
dbstate = _hash_queryset(participant.choixspectacle_set.all())
total_price = 0 total_price = 0
for choice in participant.choixspectacle_set.all(): for choice in participant.choixspectacle_set.all():
total_price += choice.spectacle.price total_price += choice.spectacle.price
if choice.double: 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}) return render(request, "inscription-bda.html", {"formset": formset, "success": success, "total_price": total_price, "dbstate": dbstate, "stateerror": stateerror})
Spectacle.deficit = lambda x: (x.slots-x.nrequests)*x.price
def do_tirage(request): def do_tirage(request):
form = TokenForm(request.POST) form = TokenForm(request.POST)
if not form.is_valid(): if not form.is_valid():
return tirage(request) return tirage(request)
start = time.time()
data = {} data = {}
shows = Spectacle.objects.all() shows = Spectacle.objects.select_related().all()
members = Participant.objects.all() members = Participant.objects.all()
choices = ChoixSpectacle.objects.all() choices = ChoixSpectacle.objects.order_by('participant', 'priority').select_related().all()
algo = Algorithm(shows, members, choices) algo = Algorithm(shows, members, choices)
results = algo(form.cleaned_data["token"]) results = algo(form.cleaned_data["token"])
total_slots = 0 total_slots = 0
@ -99,8 +150,8 @@ def do_tirage(request):
total_sold = 0 total_sold = 0
total_deficit = 0 total_deficit = 0
opera_deficit = 0 opera_deficit = 0
for show in shows: for (show, members, _) in results:
deficit = show.deficit() deficit = (show.slots - len(members)) * show.price
total_sold += show.slots * show.price total_sold += show.slots * show.price
if deficit >= 0: if deficit >= 0:
if u"Opéra" in show.location.name: 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_sold"] = total_sold - total_deficit
data["total_deficit"] = total_deficit data["total_deficit"] = total_deficit
data["opera_deficit"] = opera_deficit data["opera_deficit"] = opera_deficit
data["duration"] = time.time() - start
if request.user.is_authenticated(): if request.user.is_authenticated():
members2 = {} 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 (show, members, _) in results:
for (member, _, _, _) in members: for (member, _, _, _) in members:
if member not in members2: if member.id not in members_uniq:
members_uniq[member.id] = member
members2[member] = [] members2[member] = []
member.total = 0 member.total = 0
member = members_uniq[member.id]
members2[member].append(show) members2[member].append(show)
member.total += show.price member.total += show.price
members2 = members2.items() members2 = members2.items()
data["members2"] = sorted(members2, key = lambda m: m[0].user.last_name) 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() Attribution.objects.all().delete()
for (show, members, _) in results: for (show, members, _) in results:
for (member, _, _, _) in members: for (member, _, _, _) in members:
@ -159,15 +215,27 @@ def do_resell(request, form):
spectacle = form.cleaned_data["spectacle"] spectacle = form.cleaned_data["spectacle"]
count = form.cleaned_data["count"] count = form.cleaned_data["count"]
places = "2 places" if count == "2" else "une place" 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, mail = u"""Bonjour,
Je souhaite revendre %s pour %s le %s (%s) à %.02f. 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) %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"], request.user.email, ["bda-revente@lists.ens.fr"],
fail_silently = True) fail_silently = False)
return render(request, "bda-success.html", {"show": spectacle, "places": places}) return render(request, "bda-success.html", {"show": spectacle, "places": places})
@login_required @login_required

View file

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

View file

@ -24,6 +24,11 @@ TYPE_COTIZ_CHOICES = (
('exterieur', _(u"Extérieur")), ('exterieur', _(u"Extérieur")),
) )
TYPE_COMMENT_FIELD = (
('text', _(u"Texte long")),
('char', _(u"Texte court")),
)
def choices_length (choices): def choices_length (choices):
return reduce (lambda m, choice: max (m, len (choice[0])), choices, 0) 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_cof = models.BooleanField("Recevoir les mails COF", default = False)
mailing_bda = models.BooleanField("Recevoir les mails BdA", 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) 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) is_buro = models.BooleanField("Membre du Burô", default = False)
petits_cours_accept = models.BooleanField("Recevoir des petits cours", 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"), 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) CofProfile.objects.get_or_create(user = instance)
post_save.connect(create_user_profile, sender = User) 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): class Event(models.Model):
title = models.CharField("Titre", max_length = 200) title = models.CharField("Titre", max_length = 200)
location = models.CharField("Lieu", max_length = 200) location = models.CharField("Lieu", max_length = 200)
@ -77,6 +101,23 @@ class Event(models.Model):
def __unicode__(self): def __unicode__(self):
return unicode(self.title) 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): class EventOption(models.Model):
event = models.ForeignKey(Event, related_name = "options") event = models.ForeignKey(Event, related_name = "options")
name = models.CharField("Option", max_length = 200) name = models.CharField("Option", max_length = 200)
@ -102,6 +143,7 @@ class EventRegistration(models.Model):
user = models.ForeignKey(User) user = models.ForeignKey(User)
event = models.ForeignKey(Event) event = models.ForeignKey(Event)
options = models.ManyToManyField(EventOptionChoice) options = models.ManyToManyField(EventOptionChoice)
filledcomments = models.ManyToManyField(EventCommentField, through = EventCommentValue)
paid = models.BooleanField("A payé", default = False) paid = models.BooleanField("A payé", default = False)
class Meta: class Meta:

View file

@ -60,38 +60,45 @@ def _get_attrib_counter(user, matiere):
counter.save() counter.save()
return counter return counter
def _get_demande_candidates(demande, redo = False):
@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 = {}
for matiere in demande.matieres.all(): for matiere in demande.matieres.all():
candidates = PetitCoursAbility.objects.filter(matiere = matiere, niveau = demande.niveau) candidates = PetitCoursAbility.objects.filter(matiere = matiere, niveau = demande.niveau)
candidates = candidates.filter(user__profile__is_cof = True, candidates = candidates.filter(user__profile__is_cof = True,
user__profile__petits_cours_accept = True) user__profile__petits_cours_accept = True)
if demande.agrege_requis: if demande.agrege_requis:
candidates = candidates.filter(agrege = True) candidates = candidates.filter(agrege = True)
candidates = candidates.values_list('user', flat = True).distinct().order_by('?').all() 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 = [] tuples = []
for candidate in candidates: for candidate in candidates:
user = User.objects.get(pk = candidate) user = candidate.user
tuples.append((candidate, _get_attrib_counter(user, matiere))) tuples.append((candidate, _get_attrib_counter(user, matiere)))
if tuples:
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] = []
candidates = candidates[0:min(3, len(candidates))] candidates = candidates[0:min(3, len(candidates))]
attribdata[matiere.id] = []
proposals[matiere] = [] proposals[matiere] = []
for candidate in candidates: for candidate in candidates:
user = User.objects.get(pk = candidate) user = candidate.user
proposals[matiere].append(user) proposals[matiere].append(user)
attribdata[matiere.id].append(user.id) attribdata[matiere.id].append(user.id)
if user not in proposed_for: if user not in proposed_for:
@ -100,10 +107,15 @@ def traitement(request, demande_id):
proposed_for[user].append(matiere) proposed_for[user].append(matiere)
else: else:
unsatisfied.append(matiere) 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, def _finalize_traitement(request, demande, proposals, proposed_for,
unsatisfied, attribdata, redo = False): unsatisfied, attribdata, redo = False, errors = None):
proposals = proposals.items() proposals = proposals.items()
proposed_for = proposed_for.items() proposed_for = proposed_for.items()
attribdata = attribdata.items() attribdata = attribdata.items()
@ -121,6 +133,7 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
"mainmail": mainmail, "mainmail": mainmail,
"attribdata": base64.b64encode(simplejson.dumps(attribdata)), "attribdata": base64.b64encode(simplejson.dumps(attribdata)),
"redo": redo, "redo": redo,
"errors": errors,
}) })
def _generate_eleve_email(demande, proposed_for): def _generate_eleve_email(demande, proposed_for):
@ -130,15 +143,45 @@ def _generate_eleve_email(demande, proposed_for):
proposed_mails.append((user, msg)) proposed_mails.append((user, msg))
return proposed_mails return proposed_mails
def _traitement_other_post(request, demande): def _traitement_other_preparing(request, demande):
for matiere in demande.matieres.all(): redo = "redo" in request.POST
for choice_id in range(3): unsatisfied = []
choice = request.POST["proposal-%d-%d"] % (matiere.id, choice_id)]: proposals = {}
pass proposed_for = {}
return None attribdata = {}
return _finalize_traitement(request, demande, proposals, proposed_for, unsatisfied, 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 request.method == "POST":
if "preparing" in request.POST: if "preparing" in request.POST:
return _traitement_other_preparing(request, demande) return _traitement_other_preparing(request, demande)
@ -148,13 +191,7 @@ def _traitement_other(request, demande):
proposed_for = {} proposed_for = {}
unsatisfied = [] unsatisfied = []
attribdata = {} attribdata = {}
for matiere in demande.matieres.all(): for matiere, candidates in _get_demande_candidates(demande, redo):
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()
if candidates: if candidates:
tuples = [] tuples = []
for candidate in candidates: for candidate in candidates:
@ -243,49 +280,6 @@ def _traitement_post(request, demande):
"redo": redo, "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): class BaseMatieresFormSet(BaseInlineFormSet):
def clean(self): def clean(self):
super(BaseMatieresFormSet, self).clean() super(BaseMatieresFormSet, self).clean()

View file

@ -2,9 +2,12 @@ from django.contrib.sites.models import Site
from django.conf import settings from django.conf import settings
from django_cas.backends import CASBackend, _verify as CASverify from django_cas.backends import CASBackend, _verify as CASverify
from django_cas.models import User from django_cas.models import User
from django.contrib.auth.models import User as DjangoUser
from django.db import models, connection 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): class COFCASBackend(CASBackend):
def authenticate_cas(self, ticket, service, request): def authenticate_cas(self, ticket, service, request):
@ -76,3 +79,16 @@ def unlock_tables(*models):
return row return row
unlock_table = unlock_tables 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 Survey, SurveyQuestion, SurveyQuestionAnswer, SurveyAnswer
from gestioncof.models import Event, EventOption, EventOptionChoice, EventRegistration from gestioncof.models import Event, EventOption, EventOptionChoice, EventRegistration
from gestioncof.models import EventCommentField, EventCommentValue
from gestioncof.models import CofProfile, Clipper from gestioncof.models import CofProfile, Clipper
from gestioncof.decorators import buro_required, cof_required from gestioncof.decorators import buro_required, cof_required
from gestioncof.widgets import TriStateCheckbox 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 @login_required
def home(request): def home(request):
@ -205,6 +206,17 @@ def get_event_form_choices(event, form):
all_choices.append(choice) all_choices.append(choice)
return all_choices 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 @login_required
def event(request, event_id): def event(request, event_id):
event = get_object_or_404(Event, id = event_id) event = get_object_or_404(Event, id = event_id)
@ -426,6 +438,7 @@ class RegistrationProfileForm(forms.ModelForm):
'mailing_cof', 'mailing_cof',
'mailing_bda', 'mailing_bda',
'mailing_bda_revente', 'mailing_bda_revente',
'comments'
] ]
def save(self, *args, **kw): def save(self, *args, **kw):
@ -445,7 +458,7 @@ class RegistrationProfileForm(forms.ModelForm):
class Meta: class Meta:
model = CofProfile 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): def registration_set_ro_fields(user_form, profile_form):
user_form.fields['username'].widget.attrs['readonly'] = True 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}) return render(request, "registration_form.html", {"user_form": user_form, "profile_form": profile_form, "member": member, "login_clipper": login_clipper})
STATUS_CHOICES = (('no','Non'), STATUS_CHOICES = (('no','Non'),
('wait','Attente paiement'), ('wait','Oui mais attente paiement'),
('paid','Payé'),) ('paid','Oui payé'),)
class AdminEventForm(forms.Form): class AdminEventForm(forms.Form):
status = forms.ChoiceField(label = "Inscription", choices = STATUS_CHOICES, widget = RadioSelect) status = forms.ChoiceField(label = "Inscription", choices = STATUS_CHOICES, widget = RadioSelect)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
event = kwargs.pop("event") event = kwargs.pop("event")
self.event = 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) paid = kwargs.pop("paid", None)
if paid == True: if paid == True:
kwargs["initial"] = {"status":"paid"} kwargs["initial"] = {"status":"paid"}
@ -505,8 +519,8 @@ class AdminEventForm(forms.Form):
kwargs["initial"] = {"status":"no"} kwargs["initial"] = {"status":"no"}
super(AdminEventForm, self).__init__(*args, **kwargs) super(AdminEventForm, self).__init__(*args, **kwargs)
choices = {} choices = {}
if current_choices: comments = {}
for choice in current_choices.all(): for choice in current_choices:
if choice.event_option.id not in choices: if choice.event_option.id not in choices:
choices[choice.event_option.id] = [choice.id] choices[choice.event_option.id] = [choice.id]
else: else:
@ -530,12 +544,31 @@ class AdminEventForm(forms.Form):
initial = initial) initial = initial)
field.option_id = option.id field.option_id = option.id
self.fields["option_%d" % option.id] = field 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): def choices(self):
for name, value in self.cleaned_data.items(): for name, value in self.cleaned_data.items():
if name.startswith('option_'): if name.startswith('option_'):
yield (self.fields[name].option_id, value) 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 @buro_required
def registration_form2(request, login_clipper = None, username = None): def registration_form2(request, login_clipper = None, username = None):
events = Event.objects.filter(old = False).all() events = Event.objects.filter(old = False).all()
@ -571,7 +604,7 @@ def registration_form2(request, login_clipper = None, username = None):
for event in events: for event in events:
try: try:
current_registration = EventRegistration.objects.get(user = member, event = event) 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: except EventRegistration.DoesNotExist:
form = AdminEventForm(event = event) form = AdminEventForm(event = event)
event_forms.append(form) 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]): 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() member = user_form.save()
(profile, _) = CofProfile.objects.get_or_create(user = member) (profile, _) = CofProfile.objects.get_or_create(user = member)
was_cof = profile.is_cof
request_dict["num"] = profile.num request_dict["num"] = profile.num
profile_form = RegistrationProfileForm(request_dict, instance = profile) profile_form = RegistrationProfileForm(request_dict, instance = profile)
profile_form.is_valid() profile_form.is_valid()
profile_form.save() 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: for form in event_forms:
if form.cleaned_data['status'] == 'no': if form.cleaned_data['status'] == 'no':
try: try:
@ -631,10 +668,18 @@ def registration(request):
pass pass
continue continue
all_choices = get_event_form_choices(form.event, form) 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.options = all_choices
current_registration.paid = (form.cleaned_data['status'] == 'paid') current_registration.paid = (form.cleaned_data['status'] == 'paid')
current_registration.save() 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 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}) 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: else:
@ -653,54 +698,76 @@ def export_members(request):
return response 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 @buro_required
def export_mega_orgas(request): def export_mega_orgas(request):
response = HttpResponse(mimetype = 'text/csv') event = Event.objects.get(title = "Mega 2014")
response['Content-Disposition'] = 'attachment; filename=orgas_mega.csv'
writer = unicodecsv.UnicodeWriter(response)
event = Event.objects.get(title = "MEGA")
type_option = event.options.get(name = "Type") type_option = event.options.get(name = "Type")
participant_type = type_option.choices.get(value = "Participant").id participant_type_a = type_option.choices.get(value = "Conscrit étudiant").id
for reg in EventRegistration.objects.filter(event = event).exclude(options__id__exact = participant_type).all(): participant_type_b = type_option.choices.get(value = "Conscrit élève").id
user = reg.user qs = EventRegistration.objects.filter(event = event).exclude(options__id__in = (participant_type_a, participant_type_b))
profile = user.get_profile() return csv_export_mega('orgas_mega_2014.csv', qs)
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
@buro_required
def export_mega_participants(request): def export_mega_participants(request):
response = HttpResponse(mimetype = 'text/csv') event = Event.objects.get(title = "Mega 2014")
response['Content-Disposition'] = 'attachment; filename=participants_mega.csv'
writer = unicodecsv.UnicodeWriter(response)
event = Event.objects.get(title = "MEGA")
type_option = event.options.get(name = "Type") type_option = event.options.get(name = "Type")
participant_type = type_option.choices.get(value = "Participant").id participant_type_a = type_option.choices.get(value = "Conscrit étudiant").id
for reg in EventRegistration.objects.filter(event = event).filter(options__id__exact = participant_type).all(): participant_type_b = type_option.choices.get(value = "Conscrit élève").id
user = reg.user qs = EventRegistration.objects.filter(event = event).filter(options__id__in = (participant_type_a, participant_type_b))
profile = user.get_profile() return csv_export_mega('participants_mega_2014.csv', qs)
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
@buro_required @buro_required
def export_mega(request): def export_mega(request):
response = HttpResponse(mimetype = 'text/csv') event = Event.objects.filter(title = "Mega 2014")
response['Content-Disposition'] = 'attachment; filename=all_mega.csv' qs = EventRegistration.objects.filter(event = event).order_by("user__username")
return csv_export_mega('all_mega_2014.csv', qs)
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
@buro_required @buro_required
def utile_cof(request): def utile_cof(request):

0
manage.py Executable file → Normal file
View file

View file

@ -176,8 +176,8 @@ fieldset legend {
} }
#main-container { #main-container {
max-width: 90%; max-width: 95%;
width: 800px; width: 1000px;
margin: 7em auto; margin: 7em auto;
display: block; display: block;
} }
@ -588,3 +588,47 @@ pre code {
max-height: 340px; max-height: 340px;
overflow-y: scroll; 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>Token : {{ token }}</h2>
<h2>Placés : {{ total_slots }} ; Déçus : {{ total_losers }}</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 %} {% 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 %} {% for show, members, losers in results %}
<div class="attribresult"> <div class="attribresult">

View file

@ -1,11 +1,49 @@
{% extends "base_title.html" %} {% extends "base_title.html" %}
{% block realcontent %} {% block realcontent %}
<h2>Etat des inscriptions BDA</h2> <h2>État des inscriptions BdA</h2>
<ul> <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 %} {% 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> <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 %} {% endfor %}
</ul> </tbody>
</table>
<strong>Total : <u>{{ total }} demandes</u></strong> <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 %} {% endblock %}

View file

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

View file

@ -22,13 +22,32 @@
</ul> </ul>
{% endif %} {% endif %}
{% if user.profile.is_cof %}
<h3>BdA</h3> <h3>BdA</h3>
<ul> <ul>
<li><a href="{% url "bda-tirage-inscription" %}">Inscription au tirage au sort du BdA</a></li> Premier tirage
<li><a href="{% url "bda.views.etat_places" %}">Etat des demandes</a></li> <!-- <li><a href="{% url "bda-tirage-inscription" %}">Inscription au premier tirage au sort du BdA</a></li> -->
<!-- <li><a href="{% url "bda.views.revente" %}">Revente de places BdA (premier tirage)</a></li> --> <li><a href="{% url "bda-etat-places" %}">État des demandes</a></li>
<!-- <li><a href="{% url "bda2.views.revente" %}">Revente de places BdA (second tirage)</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> </ul>
{% endif %}
<h3>Divers</h3> <h3>Divers</h3>
<ul> <ul>

View file

@ -12,8 +12,8 @@
<h3>Mega</h3> <h3>Mega</h3>
<ul> <ul>
<li><a href="{% url 'gestioncof.views.export_mega_participants' %}">Export des participants</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</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> <li><a href="{% url 'gestioncof.views.export_mega' %}">Export de tout le monde</a></li>
</ul> </ul>
{% endblock %} {% endblock %}