Merge branch 'master' into Kerl/registration_mail

This commit is contained in:
Martin Pépin 2016-07-10 14:32:55 +02:00
commit bde632a78f
28 changed files with 1011 additions and 606 deletions

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ settings.py
venv/
.vagrant
/src
media/

View file

@ -6,7 +6,7 @@ https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cof.settings")
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cof.settings")
application = get_wsgi_application()

View file

@ -4,38 +4,49 @@ from django.core.mail import send_mail
from django.contrib import admin
from django.db.models import Sum, Count
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle, Attribution, Tirage
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
Attribution, Tirage
from django import forms
from datetime import timedelta
import autocomplete_light
class ChoixSpectacleInline(admin.TabularInline):
model = ChoixSpectacle
sortable_field_name = "priority"
class AttributionInline(admin.TabularInline):
model = Attribution
class ParticipantAdmin(admin.ModelAdmin):
inlines = [AttributionInline]
def get_queryset(self, request):
return Participant.objects.annotate(nb_places = Count('attributions'),
total = Sum('attributions__price'))
return Participant.objects.annotate(nb_places=Count('attributions'),
total=Sum('attributions__price'))
def nb_places(self, obj):
return obj.nb_places
nb_places.admin_order_field = "nb_places"
nb_places.short_description = "Nombre de places"
def total(self, obj):
tot = obj.total
if tot: return u"%.02f" % tot
else: return u"0 €"
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",
"tirage")
list_filter = ("paid", "tirage")
search_fields = ('user__username', 'user__first_name', 'user__last_name')
actions = ['send_attribs',]
actions = ['send_attribs', ]
actions_on_bottom = True
list_per_page = 400
readonly_fields = ("total",)
@ -49,8 +60,9 @@ class ParticipantAdmin(admin.ModelAdmin):
Tu t'es inscrit-e pour le tirage au sort du BdA. Malheureusement, tu n'as
obtenu aucune place.
Nous proposons cependant de nombreuses offres hors-tirage tout au long de
l'année, et nous t'invitons à nous contacter si l'une d'entre elles t'intéresse !
Nous proposons cependant de nombreuses offres hors-tirage tout au long de
l'année, et nous t'invitons à nous contacter si l'une d'entre elles
t'intéresse !
--
Le Bureau des Arts
@ -69,15 +81,16 @@ pour les spectacles suivants :
L'intégralité de ces places de spectacles est à régler dès maintenant et AVANT
le %s, 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. Pour ceux qui ne pourraient pas venir
payer au bureau, merci de nous contacter par mail.
évidemment possibles : nous pouvons ne pas encaisser le chèque immédiatement,
ou bien découper votre paiement en deux fois. Pour ceux qui ne pourraient pas
venir payer au bureau, merci de nous contacter par mail.
*Mode de retrait des places*
Au moment du paiement, certaines places vous seront remises directement, d'autres
seront à récupérer au cours de l'année, d'autres encore seront nominatives et à retirer
le soir même dans les theâtres correspondants. Pour chaque spectacle, vous recevrez un mail
quelques jours avant la représentation vous indiquant le mode de retrait.
Au moment du paiement, certaines places vous seront remises directement,
d'autres seront à récupérer au cours de l'année, d'autres encore seront
nominatives et à retirer le soir même dans les theâtres correspondants.
Pour chaque spectacle, vous recevrez un mail quelques jours avant la
représentation vous indiquant le mode de retrait.
Nous vous rappelons que l'obtention de places du BdA vous engage à
respecter les règles de fonctionnement :
@ -94,10 +107,11 @@ Le Bureau des Arts
for attrib in attribs:
attribs_text += u"- 1 place pour %s\n" % attrib
deadline = member.tirage.fermeture + timedelta(days=7)
mail = mail % (name, attribs_text, deadline.strftime('%d %b %Y'))
send_mail ("Résultats du tirage au sort", mail,
"bda@ens.fr", [member.user.email],
fail_silently = True)
mail = mail % (name, attribs_text,
deadline.strftime('%d %b %Y'))
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:
message_bit = u"1 membre a"
@ -105,36 +119,48 @@ Le Bureau des Arts
else:
message_bit = u"%d membres ont" % count
plural = "s"
self.message_user(request, u"%s été informé%s avec succès." % (message_bit, plural))
self.message_user(request, u"%s été informé%s avec succès."
% (message_bit, plural))
send_attribs.short_description = u"Envoyer les résultats par mail"
class AttributionAdminForm(forms.ModelForm):
def clean(self):
cleaned_data=super(AttributionAdminForm, self).clean()
cleaned_data = super(AttributionAdminForm, self).clean()
participant = cleaned_data.get("participant")
spectacle = cleaned_data.get("spectacle")
if participant and spectacle:
if participant.tirage != spectacle.tirage:
raise forms.ValidationError(u"Erreur : le participant et le spectacle n'appartiennent pas au même tirage")
raise forms.ValidationError(
u"Erreur : le participant et le spectacle n'appartiennent"
u"pas au même tirage")
return cleaned_data
class AttributionAdmin(admin.ModelAdmin):
def paid(self, obj):
return obj.participant.paid
paid.short_description = 'A payé'
paid.boolean = True
list_display = ("id", "spectacle", "participant", "given", "paid")
search_fields = ('spectacle__title', 'participant__user__username', 'participant__user__first_name', 'participant__user__last_name')
search_fields = ('spectacle__title', 'participant__user__username',
'participant__user__first_name',
'participant__user__last_name')
form = AttributionAdminForm
import autocomplete_light
class ChoixSpectacleAdmin(admin.ModelAdmin):
form = autocomplete_light.modelform_factory(ChoixSpectacle, exclude=[])
def tirage(self, obj):
return obj.participant.tirage
list_display = ("participant", "tirage", "spectacle", "priority", "double_choice")
list_display = ("participant", "tirage", "spectacle", "priority",
"double_choice")
list_filter = ("double_choice", "participant__tirage")
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')
class SpectacleAdmin(admin.ModelAdmin):
model = Spectacle
@ -142,6 +168,7 @@ class SpectacleAdmin(admin.ModelAdmin):
list_filter = ("location", "tirage",)
search_fields = ("title", "location__name")
class TirageAdmin(admin.ModelAdmin):
model = Tirage
list_display = ("title", "ouverture", "fermeture", "active")

View file

@ -6,6 +6,7 @@ from django.db.models import Max
import random
class Algorithm(object):
shows = None
@ -19,7 +20,8 @@ class Algorithm(object):
show.requests
- on crée des tables de demandes pour chaque personne, afin de
pouvoir modifier les rankings"""
self.max_group = 2 * choices.aggregate(Max('priority'))['priority__max']
self.max_group = \
2 * choices.aggregate(Max('priority'))['priority__max']
self.shows = []
showdict = {}
for show in shows:
@ -39,8 +41,10 @@ class Algorithm(object):
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
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]
@ -49,7 +53,7 @@ class Algorithm(object):
for member in members:
self.origranks[member] = dict(self.ranks[member])
def IncrementRanks(self, member, currank, increment = 1):
def IncrementRanks(self, member, currank, increment=1):
for show in self.ranks[member]:
if self.ranks[member][show] > currank:
self.ranks[member][show] -= increment
@ -63,13 +67,14 @@ class Algorithm(object):
def __call__(self, seed):
random.seed(seed)
results = []
shows = sorted(self.shows, key = lambda x: x.nrequests / x.slots, reverse = True)
shows = sorted(self.shows, key=lambda x: x.nrequests / x.slots,
reverse=True)
for show in shows:
# On regroupe tous les gens ayant le même rang
groups = dict([(i, []) for i in range(1, self.max_group + 1)])
for member in show.requests:
if self.ranks[member][show] == 0:
raise RuntimeError, (member, show.title)
raise RuntimeError(member, show.title)
groups[self.ranks[member][show]].append(member)
# On passe à l'attribution
winners = []
@ -78,23 +83,23 @@ class Algorithm(object):
group = list(groups[i])
random.shuffle(group)
for member in group:
if self.choices[member][show].double: # double
if self.choices[member][show].double: # double
if len(winners) + 1 < show.slots:
self.appendResult(winners, member, show)
self.appendResult(winners, member, show)
elif not self.choices[member][show].autoquit and len(winners) < show.slots:
elif not self.choices[member][show].autoquit \
and len(winners) < show.slots:
self.appendResult(winners, member, show)
self.appendResult(losers, member, show)
else:
self.appendResult(losers, member, show)
self.appendResult(losers, member, show)
self.IncrementRanks(member, i, 2)
else: # simple
else: # simple
if len(winners) < show.slots:
self.appendResult(winners, member, show)
else:
self.appendResult(losers, member, show)
self.IncrementRanks(member, i)
results.append((show,winners,losers))
results.append((show, winners, losers))
return results

View file

@ -2,8 +2,11 @@ import autocomplete_light
from bda.models import Participant, Spectacle
autocomplete_light.register(Participant, search_fields=('user__username','user__first_name','user__last_name'),
autocomplete_light.register(
Participant, search_fields=('user__username', 'user__first_name',
'user__last_name'),
autocomplete_js_attributes={'placeholder': 'participant...'})
autocomplete_light.register(Spectacle, search_fields=('title',),
autocomplete_light.register(
Spectacle, search_fields=('title', ),
autocomplete_js_attributes={'placeholder': 'spectacle...'})

View file

@ -4,12 +4,14 @@ from django import forms
from django.forms.models import BaseInlineFormSet
from bda.models import Spectacle
class BaseBdaFormSet(BaseInlineFormSet):
def clean(self):
"""Checks that no two articles have the same title."""
super(BaseBdaFormSet, self).clean()
if any(self.errors):
# Don't bother validating the formset unless each form is valid on its own
# Don't bother validating the formset unless each form is valid on
# its own
return
spectacles = []
for i in range(0, self.total_form_count()):
@ -19,23 +21,27 @@ class BaseBdaFormSet(BaseInlineFormSet):
spectacle = form.cleaned_data['spectacle']
delete = form.cleaned_data['DELETE']
if not delete and spectacle in spectacles:
raise forms.ValidationError("Vous ne pouvez pas vous " + \
"inscrire deux fois pour le même spectacle.")
raise forms.ValidationError(
"Vous ne pouvez pas vous inscrire deux fois pour le "
"même spectacle.")
spectacles.append(spectacle)
class TokenForm(forms.Form):
token = forms.CharField(widget=forms.widgets.Textarea())
class SpectacleModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return u"%s le %s (%s) à %.02f" % (obj.title, obj.date_no_seconds(),
obj.location, obj.price)
class ResellForm(forms.Form):
count = forms.ChoiceField(choices = (("1","1"),("2","2"),))
count = forms.ChoiceField(choices=(("1", "1"), ("2", "2"),))
spectacle = SpectacleModelChoiceField(queryset=Spectacle.objects.none())
def __init__(self, participant, *args, **kwargs):
super(ResellForm, self).__init__(*args, **kwargs)
self.fields['spectacle'].queryset = participant.attributions.all().distinct()
self.fields['spectacle'].queryset = participant.attributions.all() \
.distinct()

View file

@ -6,6 +6,7 @@ from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
class Tirage(models.Model):
title = models.CharField("Titre", max_length=300)
ouverture = models.DateTimeField("Date et heure d'ouverture du tirage")
@ -18,14 +19,16 @@ class Tirage(models.Model):
def __unicode__(self):
return u"%s - %s" % (self.title, self.date_no_seconds())
class Salle(models.Model):
name = models.CharField("Nom", max_length = 300)
name = models.CharField("Nom", max_length=300)
address = models.TextField("Adresse")
def __unicode__ (self):
def __unicode__(self):
return self.name
class Spectacle(models.Model):
title = models.CharField("Titre", max_length=300)
date = models.DateTimeField("Date & heure")
@ -39,9 +42,9 @@ class Spectacle(models.Model):
class Meta:
verbose_name = "Spectacle"
ordering = ("priority", "date","title",)
ordering = ("priority", "date", "title",)
def __repr__ (self):
def __repr__(self):
return u"[%s]" % self.__unicode__()
def timestamp(self):
@ -50,31 +53,33 @@ class Spectacle(models.Model):
def date_no_seconds(self):
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)
self.location, self.price)
PAYMENT_TYPES = (
("cash",u"Cash"),
("cb","CB"),
("cheque",u"Chèque"),
("autre",u"Autre"),
("cash", u"Cash"),
("cb", "CB"),
("cheque", u"Chèque"),
("autre", u"Autre"),
)
class Participant(models.Model):
user = models.ForeignKey(User)
choices = models.ManyToManyField(Spectacle,
through="ChoixSpectacle",
related_name="chosen_by")
through="ChoixSpectacle",
related_name="chosen_by")
attributions = models.ManyToManyField(Spectacle,
through="Attribution",
related_name="attributed_to")
paid = models.BooleanField (u"A payé", default=False)
through="Attribution",
related_name="attributed_to")
paid = models.BooleanField(u"A payé", default=False)
paymenttype = models.CharField(u"Moyen de paiement",
max_length=6, choices=PAYMENT_TYPES, blank=True)
max_length=6, choices=PAYMENT_TYPES,
blank=True)
tirage = models.ForeignKey(Tirage)
def __unicode__ (self):
def __unicode__(self):
return u"%s" % (self.user)
DOUBLE_CHOICES = (
@ -83,12 +88,14 @@ DOUBLE_CHOICES = (
("double", "2 places sinon rien"),
)
class ChoixSpectacle(models.Model):
participant = models.ForeignKey(Participant)
spectacle = models.ForeignKey(Spectacle, related_name="participants")
priority = models.PositiveIntegerField("Priorité")
double_choice = models.CharField("Nombre de places",
default="1", choices=DOUBLE_CHOICES, max_length=10)
default="1", choices=DOUBLE_CHOICES,
max_length=10)
def get_double(self):
return self.double_choice != "1"
@ -104,11 +111,11 @@ class ChoixSpectacle(models.Model):
verbose_name = "voeu"
verbose_name_plural = "voeux"
class Attribution(models.Model):
participant = models.ForeignKey(Participant)
spectacle = models.ForeignKey(Spectacle, related_name="attribues")
given = models.BooleanField(u"Donnée", default=False)
def __unicode__ (self):
def __unicode__(self):
return u"%s -- %s" % (self.participant, self.spectacle)

View file

@ -3,7 +3,8 @@
from django.conf.urls import url, patterns
from bda.views import SpectacleListView
urlpatterns = patterns('',
urlpatterns = patterns(
'',
url(r'inscription/(?P<tirage_id>\d+)$',
'bda.views.inscription',
name='bda-tirage-inscription'),
@ -21,14 +22,14 @@ urlpatterns = patterns('',
name='bda-etat-places'),
url(r'tirage/(?P<tirage_id>\d+)$', 'bda.views.tirage'),
url(r'spectacles/(?P<tirage_id>\d+)$',
SpectacleListView.as_view() ,
name ="bda-liste-spectacles"),
SpectacleListView.as_view(),
name="bda-liste-spectacles"),
url(r'spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$',
"bda.views.spectacle",
name="bda-spectacle"),
url(r'spectacles-ics/(?P<tirage_id>\d+)$',
'bda.views.liste_spectacles_ics',
name ="bda-liste-spectacles-ics"),
name="bda-liste-spectacles-ics"),
url(r'spectacles/unpaid/(?P<tirage_id>\d+)$',
"bda.views.unpaid",
name="bda-unpaid"),

View file

@ -10,7 +10,7 @@ from django.core import serializers
from django.forms.models import inlineformset_factory
import hashlib
from django.core.mail import send_mail
from django.core.mail import send_mail
from django.utils import timezone
from django.views.generic.list import ListView
@ -18,11 +18,13 @@ from datetime import timedelta
import time
from gestioncof.decorators import cof_required, buro_required
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution, Tirage
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\
Tirage
from bda.algorithm import Algorithm
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm
@cof_required
def etat_places(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
@ -30,13 +32,13 @@ def etat_places(request, tirage_id):
.filter(spectacle__tirage=tirage) \
.filter(double_choice="1") \
.all() \
.values('spectacle','spectacle__title') \
.values('spectacle', 'spectacle__title') \
.annotate(total=models.Count('spectacle'))
spectacles2 = ChoixSpectacle.objects \
.filter(spectacle__tirage=tirage) \
.exclude(double_choice="1") \
.all() \
.values('spectacle','spectacle__title') \
.values('spectacle', 'spectacle__title') \
.annotate(total=models.Count('spectacle'))
spectacles = tirage.spectacle_set.all()
spectacles_dict = {}
@ -50,15 +52,16 @@ def etat_places(request, tirage_id):
spectacles_dict[spectacle["spectacle"]].ratio = \
spectacles_dict[spectacle["spectacle"]].total / \
spectacles_dict[spectacle["spectacle"]].slots
total += spectacle["total"]
total += spectacle["total"]
for spectacle in spectacles2:
spectacles_dict[spectacle["spectacle"]].total += 2*spectacle["total"]
spectacles_dict[spectacle["spectacle"]].ratio = \
spectacles_dict[spectacle["spectacle"]].total / \
spectacles_dict[spectacle["spectacle"]].slots
total += spectacle["total"]
total += spectacle["total"]
return render(request, "etat-places.html",
{"spectacles": spectacles, "total": total, 'tirage': tirage})
{"spectacles": spectacles, "total": total, 'tirage': tirage})
def _hash_queryset(queryset):
data = serializers.serialize("json", queryset)
@ -66,13 +69,14 @@ def _hash_queryset(queryset):
hasher.update(data)
return hasher.hexdigest()
@cof_required
def places(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
participant, created = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
user=request.user, tirage=tirage)
places = participant.attribution_set.order_by(
"spectacle__date", "spectacle").all()
"spectacle__date", "spectacle").all()
total = sum([place.spectacle.price for place in places])
filtered_places = []
places_dict = {}
@ -99,13 +103,14 @@ def places(request, tirage_id):
"total": total,
"warning": warning})
@cof_required
@cof_required
def places_ics(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
participant, created = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
user=request.user, tirage=tirage)
places = participant.attribution_set.order_by(
"spectacle__date", "spectacle").all()
"spectacle__date", "spectacle").all()
filtered_places = []
places_dict = {}
spectacles = []
@ -114,7 +119,8 @@ def places_ics(request, tirage_id):
places_dict[place.spectacle].double = True
else:
place.double = False
place.spectacle.dtend = place.spectacle.date + timedelta(seconds=7200)
place.spectacle.dtend = place.spectacle.date \
+ timedelta(seconds=7200)
places_dict[place.spectacle] = place
spectacles.append(place.spectacle)
filtered_places.append(place)
@ -122,35 +128,38 @@ def places_ics(request, tirage_id):
{"participant": participant,
"places": filtered_places}, content_type="text/calendar")
@cof_required
def inscription(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
if timezone.now() < tirage.ouverture:
error_desc = "Ouverture le %s" % (
tirage.ouverture.strftime('%d %b %Y à %H:%M'))
tirage.ouverture.strftime('%d %b %Y à %H:%M'))
return render(request, 'resume_inscription.html',
{ "error_title": "Le tirage n'est pas encore ouvert !",
"error_description": error_desc })
{"error_title": "Le tirage n'est pas encore ouvert !",
"error_description": error_desc})
if timezone.now() > tirage.fermeture:
participant, created = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
user=request.user, tirage=tirage)
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 dans la journée !",
"choices": choices})
{"error_title": "C'est fini !",
"error_description":
u"Tirage au sort dans la journée !",
"choices": choices})
def formfield_callback(f, **kwargs):
if f.name == "spectacle":
kwargs['queryset'] = tirage.spectacle_set
return f.formfield(**kwargs)
BdaFormSet = inlineformset_factory(
Participant,
ChoixSpectacle,
fields=("spectacle","double_choice","priority"),
formset=BaseBdaFormSet,
formfield_callback=formfield_callback)
Participant,
ChoixSpectacle,
fields=("spectacle", "double_choice", "priority"),
formset=BaseBdaFormSet,
formfield_callback=formfield_callback)
participant, created = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
user=request.user, tirage=tirage)
success = False
stateerror = False
if request.method == "POST":
@ -170,14 +179,16 @@ def inscription(request, tirage_id):
total_price = 0
for choice in participant.choixspectacle_set.all():
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,
"dbstate": dbstate,
'tirage': tirage,
"stateerror": stateerror})
{"formset": formset,
"success": success,
"total_price": total_price,
"dbstate": dbstate,
'tirage': tirage,
"stateerror": stateerror})
def do_tirage(request, tirage_id):
tirage_elt = get_object_or_404(Tirage, id=tirage_id)
@ -190,8 +201,8 @@ def do_tirage(request, tirage_id):
data = {}
shows = tirage_elt.spectacle_set.select_related().all()
members = tirage_elt.participant_set.all()
choices = ChoixSpectacle.objects.filter(spectacle__tirage=tirage_elt).order_by(
'participant', 'priority').select_related().all()
choices = ChoixSpectacle.objects.filter(spectacle__tirage=tirage_elt) \
.order_by('participant', 'priority').select_related().all()
algo = Algorithm(shows, members, choices)
results = algo(form.cleaned_data["token"])
total_slots = 0
@ -221,8 +232,9 @@ def do_tirage(request, tirage_id):
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
# Participant objects are not shared accross spectacle results,
# so assign a single object for each Participant id
members_uniq = {}
for (show, members, _) in results:
for (member, _, _, _) in members:
if member.id not in members_uniq:
@ -239,8 +251,8 @@ def do_tirage(request, tirage_id):
# cf. issue #32
if False:
Attribution.objects.filter(
spectacle__tirage=tirage_elt
).delete()
spectacle__tirage=tirage_elt
).delete()
for (show, members, _) in results:
for (member, _, _, _) in members:
attrib = Attribution(spectacle=show, participant=member)
@ -249,6 +261,7 @@ def do_tirage(request, tirage_id):
else:
return render(request, "bda-attrib.html", data)
@buro_required
def tirage(request, tirage_id):
if request.POST:
@ -259,6 +272,7 @@ def tirage(request, tirage_id):
form = TokenForm()
return render(request, "bda-token.html", {"form": form})
def do_resell(request, form):
spectacle = form.cleaned_data["spectacle"]
count = form.cleaned_data["count"]
@ -269,18 +283,20 @@ Je souhaite revendre %s pour %s le %s (%s) à %.02f€.
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)
spectacle.location, spectacle.price,
request.user.get_full_name(), request.user.email)
send_mail("%s" % spectacle, mail,
request.user.email, ["bda-revente@lists.ens.fr"],
fail_silently = False)
return render(request, "bda-success.html", {"show": spectacle, "places": places})
fail_silently=False)
return render(request, "bda-success.html",
{"show": spectacle, "places": places})
@login_required
def revente(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
participant, created = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
user=request.user, tirage=tirage)
if not participant.paid:
return render(request, "bda-notpaid.html", {})
if request.POST:
@ -289,7 +305,9 @@ def revente(request, tirage_id):
return do_resell(request, form)
else:
form = ResellForm(participant)
return render(request, "bda-revente.html", {"form": form, 'tirage': tirage})
return render(request, "bda-revente.html",
{"form": form, 'tirage': tirage})
@buro_required
def spectacle(request, tirage_id, spectacle_id):
@ -299,52 +317,56 @@ def spectacle(request, tirage_id, spectacle_id):
participants = {}
for attrib in attributions:
participant = attrib.participant
participant_info = {'lastname': participant.user.last_name,
'name': participant.user.get_full_name,
'username': participant.user.username,
'email': participant.user.email,
'given': int(attrib.given),
'paid': participant.paid,
participant_info = {'lastname': participant.user.last_name,
'name': participant.user.get_full_name,
'username': participant.user.username,
'email': participant.user.email,
'given': int(attrib.given),
'paid': participant.paid,
'nb_places': 1}
if participant.id in participants:
participants[participant.id]['nb_places'] += 1
participants[participant.id]['nb_places'] += 1
participants[participant.id]['given'] += attrib.given
else:
participants[participant.id] = participant_info
participants_info = sorted(participants.values(),
key=lambda part: part['lastname'])
return render(request, "bda-participants.html",
{"spectacle": spectacle, "participants": participants_info})
participants_info = sorted(participants.values(),
key=lambda part: part['lastname'])
return render(request, "bda-participants.html",
{"spectacle": spectacle, "participants": participants_info})
class SpectacleListView(ListView):
model = Spectacle
template_name = 'spectacle_list.html'
def get_queryset(self):
self.tirage = get_object_or_404(Tirage, id=self.kwargs['tirage_id'])
categories = self.tirage.spectacle_set.all()
return categories
def get_context_data(self, **kwargs):
context = super(SpectacleListView, self).get_context_data(**kwargs)
context['tirage_id'] = self.tirage.id
context['tirage_name'] = self.tirage.title
return context
@buro_required
def unpaid(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
unpaid = tirage.participant_set \
.annotate(nb_attributions=Count('attribution')) \
.filter(paid=False, nb_attributions__gt=0).all()
.annotate(nb_attributions=Count('attribution')) \
.filter(paid=False, nb_attributions__gt=0).all()
return render(request, "bda-unpaid.html", {"unpaid": unpaid})
@buro_required
@buro_required
def liste_spectacles_ics(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
spectacles = tirage.spectacle_set.order_by("date").all()
for spectacle in spectacles:
spectacle.dtend = spectacle.date + timedelta(seconds=7200)
return render(request, "liste_spectacles.ics",
{"spectacles": spectacles, "tirage": tirage},
content_type="text/calendar")
{"spectacles": spectacles, "tirage": tirage},
content_type="text/calendar")

View file

@ -116,6 +116,12 @@ USE_TZ = True
STATIC_URL = '/static/'
# Media upload (through ImageField, SiteField)
# https://docs.djangoproject.com/en/1.9/ref/models/fields/
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
MEDIA_URL = '/media/'
# Various additional settings
SITE_ID = 1
@ -148,11 +154,14 @@ RECAPTCHA_PUBLIC_KEY = "DUMMY"
RECAPTCHA_PRIVATE_KEY = "DUMMY"
RECAPTCHA_USE_SSL = True
# On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar car
# cela interfère avec l'utilisation de Vagrant. En effet, l'adresse de la
# machine physique n'est pas forcément connue, et peut difficilement être mise
# dans les INTERNAL_IPS.
def show_toolbar(request):
"""
On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar
car cela interfère avec l'utilisation de Vagrant. En effet, l'adresse de la
machine physique n'est pas forcément connue, et peut difficilement être
mise dans les INTERNAL_IPS.
"""
if not DEBUG:
return False
if request.is_ajax():

View file

@ -1,22 +1,28 @@
# -*-coding:utf-8 -*
from django.conf import settings
from django.conf.urls import patterns, include, url
from django.conf.urls.static import static
import autocomplete_light
autocomplete_light.autodiscover()
from django.contrib import admin
admin.autodiscover()
from django.views.generic.base import TemplateView
from gestioncof.urls import export_patterns, petitcours_patterns, \
surveys_patterns, events_patterns
urlpatterns = patterns('',
autocomplete_light.autodiscover()
admin.autodiscover()
urlpatterns = patterns(
'',
# Page d'accueil
url(r'^$', 'gestioncof.views.home', name = 'home'),
url(r'^$', 'gestioncof.views.home', name='home'),
# Le BdA
url(r'^bda/', include('bda.urls')),
# Les exports
# Les exports
url(r'^export/', include(export_patterns)),
# Les petits cours
url(r'^petitcours/', include(petitcours_patterns)),
@ -24,40 +30,41 @@ urlpatterns = patterns('',
url(r'^survey/', include(surveys_patterns)),
# Evenements
url(r'^event/', include(events_patterns)),
# Authentification
# Authentification
url(r'^cof/denied$', TemplateView.as_view(template_name='cof-denied.html'),
name="cof-denied"),
name="cof-denied"),
url(r'^cas/login$', 'django_cas_ng.views.login', name="cas_login_view"),
url(r'^cas/logout$', 'django_cas_ng.views.logout'),
url(r'^outsider/login$', 'gestioncof.views.login_ext'),
url(r'^outsider/logout$', 'django.contrib.auth.views.logout',
{'next_page': 'home'}),
{'next_page': 'home'}),
url(r'^login$', 'gestioncof.views.login'),
url(r'^logout$', 'gestioncof.views.logout'),
# Infos persos
# Infos persos
url(r'^profile$', 'gestioncof.views.profile'),
url(r'^outsider/password-change$',
'django.contrib.auth.views.password_change'),
'django.contrib.auth.views.password_change'),
url(r'^outsider/password-change-done$',
'django.contrib.auth.views.password_change_done',
name='password_change_done'),
# Inscription d'un nouveau membre
url(r'^registration$', 'gestioncof.views.registration'),
url(r'^registration/clipper/(?P<login_clipper>[\w-]+)$',
'gestioncof.views.registration_form2', name="clipper-registration"),
'gestioncof.views.registration_form2', name="clipper-registration"),
url(r'^registration/user/(?P<username>.+)$',
'gestioncof.views.registration_form2', name="user-registration"),
'gestioncof.views.registration_form2', name="user-registration"),
url(r'^registration/empty$', 'gestioncof.views.registration_form2',
name="empty-registration"),
name="empty-registration"),
# Autocompletion
url(r'^autocomplete/registration$', 'gestioncof.autocomplete.autocomplete'),
url(r'^autocomplete/registration$',
'gestioncof.autocomplete.autocomplete'),
url(r'^autocomplete/', include('autocomplete_light.urls')),
# Interface admin
url(r'^admin/logout/', 'gestioncof.views.logout'),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/',
'gestioncof.csv_views.admin_list_export',
{'fields': ['username',]}),
'gestioncof.csv_views.admin_list_export',
{'fields': ['username', ]}),
url(r'^admin/', include(admin.site.urls)),
url(r'^grappelli/', include('grappelli.urls')),
# Liens utiles du COF et du BdA
@ -66,5 +73,9 @@ urlpatterns = patterns('',
url(r'^utile_bda/bda_diff$', 'gestioncof.views.liste_bdadiff'),
url(r'^utile_cof/diff_cof$', 'gestioncof.views.liste_diffcof'),
url(r'^utile_bda/bda_revente$', 'gestioncof.views.liste_bdarevente'),
)
) + \
(static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG
else [])
# Si on est en production, MEDIA_ROOT est servi par Apache.
# Il faut dire à Django de servir MEDIA_ROOT lui-même en développement.

View file

@ -9,86 +9,109 @@ from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
def add_link_field(target_model='', field='', link_text=unicode, desc_text=unicode):
import autocomplete_light
def add_link_field(target_model='', field='', link_text=unicode,
desc_text=unicode):
def add_link(cls):
reverse_name = target_model or cls.model.__name__.lower()
def link(self, instance):
app_name = instance._meta.app_label
reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
link_obj = getattr(instance, field, None) or instance
if not link_obj.id:
return ""
url = reverse(reverse_path, args = (link_obj.id,))
return mark_safe("<a href='%s'>%s</a>" % (url, link_text(link_obj)))
url = reverse(reverse_path, args=(link_obj.id,))
return mark_safe("<a href='%s'>%s</a>"
% (url, link_text(link_obj)))
link.allow_tags = True
link.short_description = desc_text(reverse_name + ' link')
cls.link = link
cls.readonly_fields = list(getattr(cls, 'readonly_fields', [])) + ['link']
cls.readonly_fields =\
list(getattr(cls, 'readonly_fields', [])) + ['link']
return cls
return add_link
class SurveyQuestionAnswerInline(admin.TabularInline):
model = SurveyQuestionAnswer
@add_link_field(desc_text=lambda x: "Réponses",
link_text=lambda x: "Éditer les réponses")
link_text=lambda x: "Éditer les réponses")
class SurveyQuestionInline(admin.TabularInline):
model = SurveyQuestion
class SurveyQuestionAdmin(admin.ModelAdmin):
inlines = [
SurveyQuestionAnswerInline,
]
SurveyQuestionAnswerInline,
]
class SurveyAdmin(admin.ModelAdmin):
inlines = [
SurveyQuestionInline,
]
SurveyQuestionInline,
]
class EventOptionChoiceInline(admin.TabularInline):
model = EventOptionChoice
@add_link_field(desc_text=lambda x: "Choix",
link_text=lambda x: "Éditer les choix")
link_text=lambda x: "Éditer les choix")
class EventOptionInline(admin.TabularInline):
model = EventOption
class EventCommentFieldInline(admin.TabularInline):
model = EventCommentField
class EventOptionAdmin(admin.ModelAdmin):
inlines = [
EventOptionChoiceInline,
]
EventOptionChoiceInline,
]
class EventAdmin(admin.ModelAdmin):
inlines = [
EventOptionInline,
EventCommentFieldInline,
]
EventOptionInline,
EventCommentFieldInline,
]
class CofProfileInline(admin.StackedInline):
model = CofProfile
inline_classes = ("collapse open",)
class FkeyLookup(object):
def __init__(self, fkeydecl, short_description=None, admin_order_field=None):
def __init__(self, fkeydecl, short_description=None,
admin_order_field=None):
self.fk, fkattrs = fkeydecl.split('__', 1)
self.fkattrs = fkattrs.split('__')
self.short_description = short_description or self.fkattrs[-1]
self.admin_order_field = admin_order_field or fkeydecl
def __get__(self, obj, klass):
if obj is None:
return self # hack required to make Django validate (if obj is
# None, then we're a class, and classes are callable
# <wink>)
"""
hack required to make Django validate (if obj is
None, then we're a class, and classes are callable
<wink>)
"""
return self
item = getattr(obj, self.fk)
for attr in self.fkattrs:
item = getattr(item, attr)
return item
def ProfileInfo(field, short_description, boolean=False):
def getter(self):
try:
@ -99,14 +122,17 @@ def ProfileInfo(field, short_description, boolean=False):
getter.boolean = boolean
return getter
User.profile_login_clipper = FkeyLookup("profile__login_clipper", "Login clipper")
User.profile_login_clipper = FkeyLookup("profile__login_clipper",
"Login clipper")
User.profile_num = FkeyLookup("profile__num", "Numéro")
User.profile_phone = ProfileInfo("phone", "Téléphone")
User.profile_occupation = ProfileInfo("occupation", "Occupation")
User.profile_departement = ProfileInfo("departement", "Departement")
User.profile_mailing_cof = ProfileInfo("mailing_cof", "ML COF", True)
User.profile_mailing_bda = ProfileInfo("mailing_bda", "ML BDA", True)
User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente", "ML BDA-R", True)
User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente",
"ML BDA-R", True)
class UserProfileAdmin(UserAdmin):
def is_buro(self, obj):
@ -116,6 +142,7 @@ class UserProfileAdmin(UserAdmin):
return False
is_buro.short_description = 'Membre du Buro'
is_buro.boolean = True
def is_cof(self, obj):
try:
return obj.profile.is_cof
@ -124,57 +151,63 @@ class UserProfileAdmin(UserAdmin):
is_cof.short_description = 'Membre du COF'
is_cof.boolean = True
list_display = ('profile_num',) + UserAdmin.list_display \
+ ( 'profile_login_clipper','profile_phone','profile_occupation',
'profile_mailing_cof','profile_mailing_bda',
'profile_mailing_bda_revente','is_cof','is_buro',)
list_display_links = ('username','email','first_name','last_name')
+ ('profile_login_clipper', 'profile_phone', 'profile_occupation',
'profile_mailing_cof', 'profile_mailing_bda',
'profile_mailing_bda_revente', 'is_cof', 'is_buro', )
list_display_links = ('username', 'email', 'first_name', 'last_name')
list_filter = UserAdmin.list_filter \
+ ( 'profile__is_cof', 'profile__is_buro', 'profile__mailing_cof',
'profile__mailing_bda')
+ ('profile__is_cof', 'profile__is_buro', 'profile__mailing_cof',
'profile__mailing_bda')
search_fields = UserAdmin.search_fields + ('profile__phone',)
inlines = [
CofProfileInline,
]
CofProfileInline,
]
import autocomplete_light
def user_unicode(self):
if self.first_name and self.last_name:
return u"%s %s (%s)" % (self.first_name, self.last_name, self.username)
else:
return self.username
User.__unicode__ = user_unicode
class EventRegistrationAdmin(admin.ModelAdmin):
form = autocomplete_light.modelform_factory(EventRegistration, exclude=[])
list_display = ('__unicode__','event','user','paid')
list_display = ('__unicode__', 'event', 'user', 'paid')
list_filter = ('paid',)
search_fields = ('user__username', 'user__first_name', 'user__last_name',
'user__email', 'event__title')
class PetitCoursAbilityAdmin(admin.ModelAdmin):
list_display = ('user','matiere','niveau','agrege')
list_display = ('user', 'matiere', 'niveau', 'agrege')
search_fields = ('user__username', 'user__first_name', 'user__last_name',
'user__email', 'matiere__name', 'niveau')
list_filter = ('matiere','niveau','agrege')
list_filter = ('matiere', 'niveau', 'agrege')
class PetitCoursAttributionAdmin(admin.ModelAdmin):
list_display = ('user','demande','matiere','rank',)
list_display = ('user', 'demande', 'matiere', 'rank', )
class PetitCoursAttributionCounterAdmin(admin.ModelAdmin):
list_display = ('user','matiere','count',)
list_display = ('user', 'matiere', 'count', )
list_filter = ('matiere',)
search_fields = ('user__username', 'user__first_name', 'user__last_name',
'user__email', 'matiere__name')
actions = ['reset',]
actions = ['reset', ]
actions_on_bottom = True
def reset(self, request, queryset):
queryset.update(count=0)
reset.short_description = u"Remise à zéro du compteur"
class PetitCoursDemandeAdmin(admin.ModelAdmin):
list_display = ('name','email','agrege_requis','niveau','created',
'traitee','processed')
list_filter = ('traitee','niveau')
list_display = ('name', 'email', 'agrege_requis', 'niveau', 'created',
'traitee', 'processed')
list_filter = ('traitee', 'niveau')
admin.site.register(Survey, SurveyAdmin)
admin.site.register(SurveyQuestion, SurveyQuestionAdmin)
@ -188,6 +221,7 @@ admin.site.register(CustomMail)
admin.site.register(PetitCoursSubject)
admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin)
admin.site.register(PetitCoursAttribution, PetitCoursAttributionAdmin)
admin.site.register(PetitCoursAttributionCounter, PetitCoursAttributionCounterAdmin)
admin.site.register(PetitCoursAttributionCounter,
PetitCoursAttributionCounterAdmin)
admin.site.register(PetitCoursDemande, PetitCoursDemandeAdmin)
admin.site.register(EventRegistration, EventRegistrationAdmin)

View file

@ -5,6 +5,7 @@ from django.db.models import Q
from django.contrib.auth.models import User
from gestioncof.models import CofProfile, Clipper
def autocomplete(request):
if "q" not in request.GET:
raise Http404
@ -22,25 +23,28 @@ def autocomplete(request):
for bit in bits:
queries['members'] = queries['members'].filter(
Q(user__first_name__icontains=bit)
|Q(user__last_name__icontains=bit)
|Q(user__username__icontains=bit)
|Q(login_clipper__icontains=bit))
| Q(user__last_name__icontains=bit)
| Q(user__username__icontains=bit)
| Q(login_clipper__icontains=bit))
queries['users'] = queries['users'].filter(
Q(first_name__icontains=bit)
|Q(last_name__icontains=bit)
|Q(username__icontains=bit))
| Q(last_name__icontains=bit)
| Q(username__icontains=bit))
queries['clippers'] = queries['clippers'].filter(
Q(fullname__icontains=bit)
|Q(username__icontains=bit))
| Q(username__icontains=bit))
queries['members'] = queries['members'].distinct()
queries['users'] = queries['users'].distinct()
usernames = list(queries['members'].values_list('login_clipper', flat = 'True')) \
+ list(queries['users'].values_list('profile__login_clipper', flat = 'True'))
queries['clippers'] = queries['clippers'].exclude(username__in = usernames).distinct()
usernames = list(queries['members'].values_list('login_clipper',
flat='True')) \
+ list(queries['users'].values_list('profile__login_clipper',
flat='True'))
queries['clippers'] = queries['clippers'] \
.exclude(username__in=usernames).distinct()
# add clippers
data.update(queries)
options = 0
for query in queries.values():
options += len(query)

View file

@ -2,5 +2,6 @@ import autocomplete_light
from django.contrib.auth.models import User
autocomplete_light.register(User, search_fields=('username','first_name','last_name'),
autocomplete_light.register(
User, search_fields=('username', 'first_name', 'last_name'),
autocomplete_js_attributes={'placeholder': 'membre...'})

View file

@ -3,10 +3,12 @@ from django.http import HttpResponse, HttpResponseForbidden
from django.template.defaultfilters import slugify
from django.apps import apps
def export(qs, fields=None):
model = qs.model
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=%s.csv' % slugify(model.__name__)
response['Content-Disposition'] = 'attachment; filename=%s.csv' \
% slugify(model.__name__)
writer = csv.writer(response)
# Write headers to CSV file
if fields:
@ -29,17 +31,20 @@ def export(qs, fields=None):
# Return CSV file to browser as download
return response
def admin_list_export(request, model_name, app_label, queryset=None, fields=None, list_display=True):
def admin_list_export(request, model_name, app_label, queryset=None,
fields=None, list_display=True):
"""
Put the following line in your urls.py BEFORE your admin include
(r'^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/', 'util.csv_view.admin_list_export'),
(r'^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/',
'util.csv_view.admin_list_export'),
"""
if not request.user.is_staff:
return HttpResponseForbidden()
if not queryset:
model = apps.get_model(app_label, model_name)
queryset = model.objects.all()
queryset = queryset.filter(profile__is_cof = True)
queryset = queryset.filter(profile__is_cof=True)
if not fields:
if list_display and len(queryset.model._meta.admin.list_display) > 1:
fields = queryset.model._meta.admin.list_display
@ -47,12 +52,17 @@ def admin_list_export(request, model_name, app_label, queryset=None, fields=None
fields = None
return export(queryset, fields)
"""
Create your own change_list.html for your admin view and put something like this in it:
Create your own change_list.html for your admin view and put something
like this in it:
{% block object-tools %}
<ul class="object-tools">
<li><a href="csv/{%if request.GET%}?{{request.GET.urlencode}}{%endif%}" class="addlink">Export to CSV</a></li>
<li><a href="csv/{%if request.GET%}?{{request.GET.urlencode}}
{%endif%}" class="addlink">Export to CSV</a></li>
{% if has_add_permission %}
<li><a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">{% blocktrans with cl.opts.verbose_name|escape as name %}Add {{ name }}{% endblocktrans %}</a></li>
<li><a href="add/{% if is_popup %}?_popup=1{% endif %}"
class="addlink">
{% blocktrans with cl.opts.verbose_name|escape as name %}
Add {{ name }}{% endblocktrans %}</a></li>
{% endif %}
</ul>
{% endblock %}

View file

@ -1,5 +1,6 @@
from django_cas_ng.decorators import user_passes_test
def is_cof(user):
try:
profile = user.profile
@ -8,7 +9,9 @@ def is_cof(user):
return False
cof_required = user_passes_test(lambda u: is_cof(u))
cof_required_customdenied = user_passes_test(lambda u: is_cof(u), login_url = "cof-denied")
cof_required_customdenied = user_passes_test(lambda u: is_cof(u),
login_url="cof-denied")
def is_buro(user):
try:

View file

@ -10,6 +10,7 @@ from gestioncof.models import CofProfile, EventCommentValue
from gestioncof.widgets import TriStateCheckbox
from gestioncof.shared import lock_table, unlock_table
class EventForm(forms.Form):
def __init__(self, *args, **kwargs):
event = kwargs.pop("event")
@ -25,21 +26,25 @@ class EventForm(forms.Form):
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()]
choices = [(choice.id, choice.value)
for choice in option.choices.all()]
if option.multi_choices:
initial = [] if option.id not in all_choices else all_choices[option.id]
field = forms.MultipleChoiceField(label = option.name,
choices = choices,
widget = CheckboxSelectMultiple,
required = False,
initial = initial)
initial = [] if option.id not in all_choices \
else all_choices[option.id]
field = forms.MultipleChoiceField(
label=option.name,
choices=choices,
widget=CheckboxSelectMultiple,
required=False,
initial=initial)
else:
initial = None if option.id not in all_choices else all_choices[option.id][0]
field = forms.ChoiceField(label = option.name,
choices = choices,
widget = RadioSelect,
required = False,
initial = initial)
initial = None if option.id not in all_choices \
else all_choices[option.id][0]
field = forms.ChoiceField(label=option.name,
choices=choices,
widget=RadioSelect,
required=False,
initial=initial)
field.option_id = option.id
self.fields["option_%d" % option.id] = field
@ -48,6 +53,7 @@ class EventForm(forms.Form):
if name.startswith('option_'):
yield (self.fields[name].option_id, value)
class SurveyForm(forms.Form):
def __init__(self, *args, **kwargs):
survey = kwargs.pop("survey")
@ -61,21 +67,25 @@ class SurveyForm(forms.Form):
else:
answers[answer.survey_question.id].append(answer.id)
for question in survey.questions.all():
choices = [(answer.id, answer.answer) for answer in question.answers.all()]
choices = [(answer.id, answer.answer)
for answer in question.answers.all()]
if question.multi_answers:
initial = [] if question.id not in answers else answers[question.id]
field = forms.MultipleChoiceField(label = question.question,
choices = choices,
widget = CheckboxSelectMultiple,
required = False,
initial = initial)
initial = [] if question.id not in answers\
else answers[question.id]
field = forms.MultipleChoiceField(
label=question.question,
choices=choices,
widget=CheckboxSelectMultiple,
required=False,
initial=initial)
else:
initial = None if question.id not in answers else answers[question.id][0]
field = forms.ChoiceField(label = question.question,
choices = choices,
widget = RadioSelect,
required = False,
initial = initial)
initial = None if question.id not in answers\
else answers[question.id][0]
field = forms.ChoiceField(label=question.question,
choices=choices,
widget=RadioSelect,
required=False,
initial=initial)
field.question_id = question.id
self.fields["question_%d" % question.id] = field
@ -83,7 +93,8 @@ class SurveyForm(forms.Form):
for name, value in self.cleaned_data.items():
if name.startswith('question_'):
yield (self.fields[name].question_id, value)
class SurveyStatusFilterForm(forms.Form):
def __init__(self, *args, **kwargs):
survey = kwargs.pop("survey")
@ -91,15 +102,17 @@ class SurveyStatusFilterForm(forms.Form):
for question in survey.questions.all():
for answer in question.answers.all():
name = "question_%d_answer_%d" % (question.id, answer.id)
if self.is_bound and self.data.get(self.add_prefix(name), None):
if self.is_bound \
and self.data.get(self.add_prefix(name), None):
initial = self.data.get(self.add_prefix(name), None)
else:
initial = "none"
field = forms.ChoiceField(label = "%s : %s" % (question.question, answer.answer),
choices = [("yes", "yes"),("no","no"),("none","none")],
widget = TriStateCheckbox,
required = False,
initial = initial)
field = forms.ChoiceField(
label="%s : %s" % (question.question, answer.answer),
choices=[("yes", "yes"), ("no", "no"), ("none", "none")],
widget=TriStateCheckbox,
required=False,
initial=initial)
field.question_id = question.id
field.answer_id = answer.id
self.fields[name] = field
@ -107,7 +120,9 @@ class SurveyStatusFilterForm(forms.Form):
def filters(self):
for name, value in self.cleaned_data.items():
if name.startswith('question_'):
yield (self.fields[name].question_id, self.fields[name].answer_id, value)
yield (self.fields[name].question_id,
self.fields[name].answer_id, value)
class EventStatusFilterForm(forms.Form):
def __init__(self, *args, **kwargs):
@ -116,15 +131,17 @@ class EventStatusFilterForm(forms.Form):
for option in event.options.all():
for choice in option.choices.all():
name = "option_%d_choice_%d" % (option.id, choice.id)
if self.is_bound and self.data.get(self.add_prefix(name), None):
if self.is_bound \
and self.data.get(self.add_prefix(name), None):
initial = self.data.get(self.add_prefix(name), None)
else:
initial = "none"
field = forms.ChoiceField(label = "%s : %s" % (option.name, choice.value),
choices = [("yes", "yes"),("no","no"),("none","none")],
widget = TriStateCheckbox,
required = False,
initial = initial)
field = forms.ChoiceField(
label="%s : %s" % (option.name, choice.value),
choices=[("yes", "yes"), ("no", "no"), ("none", "none")],
widget=TriStateCheckbox,
required=False,
initial=initial)
field.option_id = option.id
field.choice_id = choice.id
self.fields[name] = field
@ -134,20 +151,23 @@ class EventStatusFilterForm(forms.Form):
initial = self.data.get(self.add_prefix(name), None)
else:
initial = "none"
field = forms.ChoiceField(label = "Événement payé",
choices = [("yes", "yes"),("no","no"),("none","none")],
widget = TriStateCheckbox,
required = False,
initial = initial)
field = forms.ChoiceField(label="Événement payé",
choices=[("yes", "yes"), ("no", "no"),
("none", "none")],
widget=TriStateCheckbox,
required=False,
initial=initial)
self.fields[name] = field
def filters(self):
for name, value in self.cleaned_data.items():
if name.startswith('option_'):
yield (self.fields[name].option_id, self.fields[name].choice_id, value)
yield (self.fields[name].option_id,
self.fields[name].choice_id, value)
elif name == "event_has_paid":
yield ("has_paid", None, value)
class UserProfileForm(forms.ModelForm):
first_name = forms.CharField(label=_(u'Prénom'), max_length=30)
last_name = forms.CharField(label=_(u'Nom'), max_length=30)
@ -174,7 +194,9 @@ class UserProfileForm(forms.ModelForm):
class Meta:
model = CofProfile
fields = ("phone", "mailing_cof", "mailing_bda", "mailing_bda_revente",)
fields = ("phone", "mailing_cof", "mailing_bda",
"mailing_bda_revente", )
class RegistrationUserForm(forms.ModelForm):
def __init__(self, *args, **kw):
@ -185,6 +207,7 @@ class RegistrationUserForm(forms.ModelForm):
model = User
fields = ("username", "first_name", "last_name", "email")
class RegistrationProfileForm(forms.ModelForm):
def __init__(self, *args, **kw):
super(RegistrationProfileForm, self).__init__(*args, **kw)
@ -224,27 +247,33 @@ 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", "comments")
fields = ("login_clipper", "num", "phone", "occupation",
"departement", "is_cof", "type_cotiz", "mailing_cof",
"mailing_bda", "mailing_bda_revente", "comments")
STATUS_CHOICES = (('no', 'Non'),
('wait', 'Oui mais attente paiement'),
('paid', 'Oui payé'),)
STATUS_CHOICES = (('no','Non'),
('wait','Oui mais attente paiement'),
('paid','Oui payé'),)
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):
event = kwargs.pop("event")
self.event = event
registration = kwargs.pop("current_registration", None)
current_choices = registration.options.all() if registration is not None else []
current_choices = \
registration.options.all() if registration is not None\
else []
paid = kwargs.pop("paid", None)
if paid == True:
kwargs["initial"] = {"status":"paid"}
elif paid == False:
kwargs["initial"] = {"status":"wait"}
if paid is True:
kwargs["initial"] = {"status": "paid"}
elif paid is False:
kwargs["initial"] = {"status": "wait"}
else:
kwargs["initial"] = {"status":"no"}
kwargs["initial"] = {"status": "no"}
super(AdminEventForm, self).__init__(*args, **kwargs)
choices = {}
for choice in current_choices:
@ -254,35 +283,41 @@ class AdminEventForm(forms.Form):
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()]
choices = [(choice.id, choice.value)
for choice in option.choices.all()]
if option.multi_choices:
initial = [] if option.id not in all_choices else all_choices[option.id]
field = forms.MultipleChoiceField(label = option.name,
choices = choices,
widget = CheckboxSelectMultiple,
required = False,
initial = initial)
initial = [] if option.id not in all_choices\
else all_choices[option.id]
field = forms.MultipleChoiceField(
label=option.name,
choices=choices,
widget=CheckboxSelectMultiple,
required=False,
initial=initial)
else:
initial = None if option.id not in all_choices else all_choices[option.id][0]
field = forms.ChoiceField(label = option.name,
choices = choices,
widget = RadioSelect,
required = False,
initial = initial)
initial = None if option.id not in all_choices\
else all_choices[option.id][0]
field = forms.ChoiceField(label=option.name,
choices=choices,
widget=RadioSelect,
required=False,
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
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)
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
@ -295,4 +330,3 @@ class AdminEventForm(forms.Form):
for name, value in self.cleaned_data.items():
if name.startswith('comment_'):
yield (self.fields[name].comment_id, value)

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gestioncof', '0002_enable_unprocessed_demandes'),
]
operations = [
migrations.AddField(
model_name='event',
name='image',
field=models.ImageField(upload_to=b'imgs/events/', null=True, verbose_name=b'Image', blank=True),
),
]

View file

@ -29,32 +29,41 @@ TYPE_COMMENT_FIELD = (
('char', _(u"Texte court")),
)
def choices_length (choices):
return reduce (lambda m, choice: max (m, len (choice[0])), choices, 0)
def choices_length(choices):
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
class CofProfile(models.Model):
user = models.OneToOneField(User, related_name = "profile")
login_clipper = models.CharField("Login clipper", max_length = 8, blank = True)
is_cof = models.BooleanField("Membre du COF", default = False)
num = models.IntegerField ("Numéro d'adhérent", blank = True, default = 0)
phone = models.CharField("Téléphone", max_length = 20, blank = True)
occupation = models.CharField (_(u"Occupation"),
default = "1A",
choices = OCCUPATION_CHOICES,
max_length = choices_length (OCCUPATION_CHOICES))
departement = models.CharField (_(u"Département"), max_length = 50, blank = True)
type_cotiz = models.CharField (_(u"Type de cotisation"),
default = "normalien",
choices = TYPE_COTIZ_CHOICES,
max_length = choices_length (TYPE_COTIZ_CHOICES))
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"),
blank = True, default = "")
user = models.OneToOneField(User, related_name="profile")
login_clipper = models.CharField("Login clipper", max_length=8, blank=True)
is_cof = models.BooleanField("Membre du COF", default=False)
num = models.IntegerField("Numéro d'adhérent", blank=True, default=0)
phone = models.CharField("Téléphone", max_length=20, blank=True)
occupation = models.CharField(_(u"Occupation"),
default="1A",
choices=OCCUPATION_CHOICES,
max_length=choices_length(
OCCUPATION_CHOICES))
departement = models.CharField(_(u"Département"), max_length=50,
blank=True)
type_cotiz = models.CharField(_(u"Type de cotisation"),
default="normalien",
choices=TYPE_COTIZ_CHOICES,
max_length=choices_length(
TYPE_COTIZ_CHOICES))
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"),
blank=True, default="")
class Meta:
verbose_name = "Profil COF"
@ -63,22 +72,26 @@ class CofProfile(models.Model):
def __unicode__(self):
return unicode(self.user.username)
def create_user_profile(sender, instance, created, **kwargs):
if created:
CofProfile.objects.get_or_create(user = instance)
post_save.connect(create_user_profile, sender = User)
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)
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")
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)
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"
@ -86,14 +99,18 @@ class CustomMail(models.Model):
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)
start_date = models.DateField("Date de début", blank = True, null = True)
end_date = models.DateField("Date de fin", blank = True, null = True)
description = models.TextField("Description", blank = True)
registration_open = models.BooleanField("Inscriptions ouvertes", default = True)
old = models.BooleanField("Archiver (événement fini)", default = False)
title = models.CharField("Titre", max_length=200)
location = models.CharField("Lieu", max_length=200)
start_date = models.DateField("Date de début", blank=True, null=True)
end_date = models.DateField("Date de fin", blank=True, null=True)
description = models.TextField("Description", blank=True)
image = models.ImageField("Image", blank=True, null=True,
upload_to="imgs/events/")
registration_open = models.BooleanField("Inscriptions ouvertes",
default=True)
old = models.BooleanField("Archiver (événement fini)", default=False)
class Meta:
verbose_name = "Événement"
@ -101,11 +118,13 @@ 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)
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"
@ -113,15 +132,18 @@ class EventCommentField(models.Model):
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)
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)
multi_choices = models.BooleanField("Choix multiples", default = False)
event = models.ForeignKey(Event, related_name="options")
name = models.CharField("Option", max_length=200)
multi_choices = models.BooleanField("Choix multiples", default=False)
class Meta:
verbose_name = "Option"
@ -129,9 +151,10 @@ class EventOption(models.Model):
def __unicode__(self):
return unicode(self.name)
class EventOptionChoice(models.Model):
event_option = models.ForeignKey(EventOption, related_name = "choices")
value = models.CharField("Valeur", max_length = 200)
event_option = models.ForeignKey(EventOption, related_name="choices")
value = models.CharField("Valeur", max_length=200)
class Meta:
verbose_name = "Choix"
@ -139,25 +162,29 @@ class EventOptionChoice(models.Model):
def __unicode__(self):
return unicode(self.value)
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)
filledcomments = models.ManyToManyField(EventCommentField,
through=EventCommentValue)
paid = models.BooleanField("A payé", default=False)
class Meta:
verbose_name = "Inscription"
unique_together = ("user", "event")
def __unicode__(self):
return u"Inscription de %s à %s" % (unicode(self.user), unicode(self.event.title))
return u"Inscription de %s à %s" % (unicode(self.user),
unicode(self.event.title))
class Survey(models.Model):
title = models.CharField("Titre", max_length = 200)
details = models.TextField("Détails", blank = True)
survey_open = models.BooleanField("Sondage ouvert", default = True)
old = models.BooleanField("Archiver (sondage fini)", default = False)
title = models.CharField("Titre", max_length=200)
details = models.TextField("Détails", blank=True)
survey_open = models.BooleanField("Sondage ouvert", default=True)
old = models.BooleanField("Archiver (sondage fini)", default=False)
class Meta:
verbose_name = "Sondage"
@ -165,10 +192,11 @@ class Survey(models.Model):
def __unicode__(self):
return unicode(self.title)
class SurveyQuestion(models.Model):
survey = models.ForeignKey(Survey, related_name = "questions")
question = models.CharField("Question", max_length = 200)
multi_answers = models.BooleanField("Choix multiples", default = False)
survey = models.ForeignKey(Survey, related_name="questions")
question = models.CharField("Question", max_length=200)
multi_answers = models.BooleanField("Choix multiples", default=False)
class Meta:
verbose_name = "Question"
@ -176,9 +204,10 @@ class SurveyQuestion(models.Model):
def __unicode__(self):
return unicode(self.question)
class SurveyQuestionAnswer(models.Model):
survey_question = models.ForeignKey(SurveyQuestion, related_name = "answers")
answer = models.CharField("Réponse", max_length = 200)
survey_question = models.ForeignKey(SurveyQuestion, related_name="answers")
answer = models.CharField("Réponse", max_length=200)
class Meta:
verbose_name = "Réponse"
@ -186,15 +215,18 @@ class SurveyQuestionAnswer(models.Model):
def __unicode__(self):
return unicode(self.answer)
class SurveyAnswer(models.Model):
user = models.ForeignKey(User)
survey = models.ForeignKey(Survey)
answers = models.ManyToManyField(SurveyQuestionAnswer, related_name = "selected_by")
answers = models.ManyToManyField(SurveyQuestionAnswer,
related_name="selected_by")
class Meta:
verbose_name = "Réponses"
unique_together = ("user", "survey")
class Clipper(models.Model):
username = models.CharField("Identifiant", max_length = 20)
fullname = models.CharField("Nom complet", max_length = 200)
username = models.CharField("Identifiant", max_length=20)
fullname = models.CharField("Nom complet", max_length=200)

View file

@ -4,8 +4,9 @@ from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
def choices_length (choices):
return reduce (lambda m, choice: max (m, len (choice[0])), choices, 0)
def choices_length(choices):
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
LEVELS_CHOICES = (
('college', _(u"Collège")),
@ -16,6 +17,7 @@ LEVELS_CHOICES = (
('other', _(u"Autre (préciser dans les commentaires)")),
)
class PetitCoursSubject(models.Model):
name = models.CharField(_(u"Matière"), max_length=30)
users = models.ManyToManyField(User, related_name="petits_cours_matieres",
@ -28,12 +30,13 @@ class PetitCoursSubject(models.Model):
def __unicode__(self):
return self.name
class PetitCoursAbility(models.Model):
user = models.ForeignKey(User)
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_(u"Matière"))
niveau = models.CharField (_(u"Niveau"),
choices=LEVELS_CHOICES,
max_length=choices_length (LEVELS_CHOICES))
niveau = models.CharField(_(u"Niveau"),
choices=LEVELS_CHOICES,
max_length=choices_length(LEVELS_CHOICES))
agrege = models.BooleanField(_(u"Agrégé"), default=False)
class Meta:
@ -41,7 +44,9 @@ class PetitCoursAbility(models.Model):
verbose_name_plural = "Compétences des petits cours"
def __unicode__(self):
return u"%s - %s - %s" % (self.user.username, self.matiere, self.niveau)
return u"%s - %s - %s" % (self.user.username,
self.matiere, self.niveau)
class PetitCoursDemande(models.Model):
name = models.CharField(_(u"Nom/prénom"), max_length=200)
@ -49,28 +54,28 @@ class PetitCoursDemande(models.Model):
phone = models.CharField(_(u"Téléphone (facultatif)"),
max_length=20, blank=True)
quand = models.CharField(
_(u"Quand ?"),
help_text=_(u"Indiquez ici la période désirée pour les petits" \
+ " cours (vacances scolaires, semaine, week-end)."),
max_length=300, blank=True)
_(u"Quand ?"),
help_text=_(u"Indiquez ici la période désirée pour les petits"
" cours (vacances scolaires, semaine, week-end)."),
max_length=300, blank=True)
freq = models.CharField(
_(u"Fréquence"),
help_text=_(u"Indiquez ici la fréquence envisagée " \
_(u"Fréquence"),
help_text=_(u"Indiquez ici la fréquence envisagée "
+ "(hebdomadaire, 2 fois par semaine, ...)"),
max_length=300, blank=True)
max_length=300, blank=True)
lieu = models.CharField(
_(u"Lieu (si préférence)"),
help_text=_(u"Si vous avez avez une préférence sur le lieu."),
max_length=300, blank=True)
_(u"Lieu (si préférence)"),
help_text=_(u"Si vous avez avez une préférence sur le lieu."),
max_length=300, blank=True)
matieres = models.ManyToManyField(
PetitCoursSubject, verbose_name=_(u"Matières"),
related_name="demandes")
PetitCoursSubject, verbose_name=_(u"Matières"),
related_name="demandes")
agrege_requis = models.BooleanField(_(u"Agrégé requis"), default=False)
niveau = models.CharField (_(u"Niveau"),
default="",
choices=LEVELS_CHOICES,
max_length=choices_length (LEVELS_CHOICES))
niveau = models.CharField(_(u"Niveau"),
default="",
choices=LEVELS_CHOICES,
max_length=choices_length(LEVELS_CHOICES))
remarques = models.TextField(_(u"Remarques et précisions"), blank=True)
@ -85,7 +90,9 @@ class PetitCoursDemande(models.Model):
verbose_name_plural = "Demandes de petits cours"
def __unicode__(self):
return u"Demande %d du %s" % (self.id, self.created.strftime("%d %b %Y"))
return u"Demande %d du %s" % (self.id,
self.created.strftime("%d %b %Y"))
class PetitCoursAttribution(models.Model):
user = models.ForeignKey(User)
@ -94,7 +101,7 @@ class PetitCoursAttribution(models.Model):
date = models.DateTimeField(_(u"Date d'attribution"), auto_now_add=True)
rank = models.IntegerField("Rang dans l'email")
selected = models.BooleanField(_(u"Sélectionné par le demandeur"),
default=False)
default=False)
class Meta:
verbose_name = "Attribution de petits cours"
@ -102,7 +109,8 @@ class PetitCoursAttribution(models.Model):
def __unicode__(self):
return u"Attribution de la demande %d à %s pour %s" \
% (self.demande.id, self.user.username, self.matiere)
% (self.demande.id, self.user.username, self.matiere)
class PetitCoursAttributionCounter(models.Model):
user = models.ForeignKey(User)
@ -115,5 +123,4 @@ class PetitCoursAttributionCounter(models.Model):
def __unicode__(self):
return u"%d demandes envoyées à %s pour %s" \
% (self.count, self.user.username, self.matiere)
% (self.count, self.user.username, self.matiere)

View file

@ -26,58 +26,67 @@ from datetime import datetime
import base64
import simplejson
def render_template(template_path, data):
tmpl = loader.get_template(template_path)
context = Context(data)
return tmpl.render(context)
class DemandeListView(ListView):
model = PetitCoursDemande
template_name = "petits_cours_demandes_list.html"
paginate_by = 20
def get_queryset(self):
return PetitCoursDemande.objects.order_by('traitee','-id').all()
return PetitCoursDemande.objects.order_by('traitee', '-id').all()
@method_decorator(buro_required)
def dispatch(self, *args, **kwargs):
return super(DemandeListView, self).dispatch(*args, **kwargs)
@buro_required
def details(request, demande_id):
demande = get_object_or_404(PetitCoursDemande, id = demande_id)
attributions = PetitCoursAttribution.objects.filter(demande = demande).all()
demande = get_object_or_404(PetitCoursDemande, id=demande_id)
attributions = PetitCoursAttribution.objects.filter(demande=demande).all()
return render(request, "details_demande_petit_cours.html",
{"demande": demande,
"attributions": attributions})
def _get_attrib_counter(user, matiere):
counter, created = PetitCoursAttributionCounter.objects.get_or_create(user = user,
matiere = matiere)
counter, created = PetitCoursAttributionCounter \
.objects.get_or_create(user=user, matiere=matiere)
if created:
mincount = PetitCoursAttributionCounter.objects.filter(matiere = matiere).exclude(user = user).all().aggregate(Min('count'))
mincount = PetitCoursAttributionCounter.objects \
.filter(matiere=matiere).exclude(user=user).all() \
.aggregate(Min('count'))
counter.count = mincount['count__min']
counter.save()
return counter
def _get_demande_candidates(demande, redo = False):
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)
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.filter(agrege=True)
if redo:
attributions = PetitCoursAttribution.objects.filter(demande = demande,
matiere = matiere).all()
attributions = PetitCoursAttribution.objects \
.filter(demande=demande, matiere=matiere).all()
for attrib in attributions:
candidates = candidates.exclude(user = attrib.user)
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)
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":
@ -92,7 +101,7 @@ def traitement(request, demande_id, redo = False):
for candidate in candidates:
user = candidate.user
tuples.append((candidate, _get_attrib_counter(user, matiere)))
tuples = sorted(tuples, key = lambda c: c[1].count)
tuples = sorted(tuples, key=lambda c: c[1].count)
candidates, _ = zip(*tuples)
candidates = candidates[0:min(3, len(candidates))]
attribdata[matiere.id] = []
@ -110,12 +119,14 @@ def traitement(request, demande_id, redo = False):
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)
return traitement(request, demande_id, redo=True)
def _finalize_traitement(request, demande, proposals, proposed_for,
unsatisfied, attribdata, redo = False, errors = None):
unsatisfied, attribdata, redo=False, errors=None):
proposals = proposals.items()
proposed_for = proposed_for.items()
attribdata = attribdata.items()
@ -123,7 +134,11 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
mainmail = render_template("petits-cours-mail-demandeur.txt",
{"proposals": proposals,
"unsatisfied": unsatisfied,
"extra": "<textarea name=\"extra\" style=\"width:99%; height: 90px;\"></textarea>"})
"extra":
'<textarea name="extra" '
'style="width:99%; height: 90px;">'
'</textarea>'
})
return render(request, "traitement_demande_petit_cours.html",
{"demande": demande,
"unsatisfied": unsatisfied,
@ -131,18 +146,22 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
"proposed_for": proposed_for,
"proposed_mails": proposed_mails,
"mainmail": mainmail,
"attribdata": base64.b64encode(simplejson.dumps(attribdata)),
"attribdata":
base64.b64encode(simplejson.dumps(attribdata)),
"redo": redo,
"errors": errors,
})
})
def _generate_eleve_email(demande, proposed_for):
proposed_mails = []
for user, matieres in proposed_for:
msg = render_template("petits-cours-mail-eleve.txt", {"demande": demande, "matieres": matieres})
msg = render_template("petits-cours-mail-eleve.txt",
{"demande": demande, "matieres": matieres})
proposed_mails.append((user, msg))
return proposed_mails
def _traitement_other_preparing(request, demande):
redo = "redo" in request.POST
unsatisfied = []
@ -157,15 +176,18 @@ def _traitement_other_preparing(request, demande):
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)])
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))
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))
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)
@ -176,10 +198,15 @@ def _traitement_other_preparing(request, demande):
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))
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)
return _finalize_traitement(request, demande, proposals, proposed_for,
unsatisfied, attribdata, errors=errors)
def _traitement_other(request, demande, redo):
if request.method == "POST":
@ -197,7 +224,7 @@ def _traitement_other(request, demande, redo):
for candidate in candidates:
user = candidate.user
tuples.append((candidate, _get_attrib_counter(user, matiere)))
tuples = sorted(tuples, key = lambda c: c[1].count)
tuples = sorted(tuples, key=lambda c: c[1].count)
candidates, _ = zip(*tuples)
attribdata[matiere.id] = []
proposals[matiere] = []
@ -218,7 +245,8 @@ def _traitement_other(request, demande, redo):
"unsatisfied": unsatisfied,
"proposals": proposals,
"proposed_for": proposed_for,
})
})
def _traitement_post(request, demande):
proposals = {}
@ -234,7 +262,7 @@ def _traitement_post(request, demande):
else:
proposals[matiere] = []
for user_id in attribdata[matiere.id]:
user = User.objects.get(pk = user_id)
user = User.objects.get(pk=user_id)
proposals[matiere].append(user)
if user not in proposed_for:
proposed_for[user] = [matiere]
@ -254,21 +282,23 @@ def _traitement_post(request, demande):
for (user, msg) in proposed_mails:
msg = EmailMessage("Petits cours ENS par le COF", msg,
frommail, [user.email],
[bccaddress], headers = {'Reply-To': replyto})
[bccaddress], headers={'Reply-To': replyto})
mails_to_send.append(msg)
mails_to_send.append(EmailMessage("Cours particuliers ENS", mainmail,
frommail, [demande.email],
[bccaddress], headers = {'Reply-To': replyto}))
connection = mail.get_connection(fail_silently = True)
[bccaddress],
headers={'Reply-To': replyto}))
connection = mail.get_connection(fail_silently=True)
connection.send_messages(mails_to_send)
lock_table(PetitCoursAttributionCounter, PetitCoursAttribution, User)
for matiere in proposals:
for rank, user in enumerate(proposals[matiere]):
counter = PetitCoursAttributionCounter.objects.get(user = user, matiere = matiere)
counter = PetitCoursAttributionCounter.objects.get(user=user,
matiere=matiere)
counter.count += 1
counter.save()
attrib = PetitCoursAttribution(user = user, matiere = matiere,
demande = demande, rank = rank + 1)
attrib = PetitCoursAttribution(user=user, matiere=matiere,
demande=demande, rank=rank + 1)
attrib.save()
unlock_tables()
demande.traitee = True
@ -278,13 +308,15 @@ def _traitement_post(request, demande):
return render(request, "traitement_demande_petit_cours_success.html",
{"demande": demande,
"redo": redo,
})
})
class BaseMatieresFormSet(BaseInlineFormSet):
def clean(self):
super(BaseMatieresFormSet, self).clean()
if any(self.errors):
# Don't bother validating the formset unless each form is valid on its own
# Don't bother validating the formset unless each form is
# valid on its own
return
matieres = []
for i in range(0, self.total_form_count()):
@ -295,38 +327,48 @@ class BaseMatieresFormSet(BaseInlineFormSet):
niveau = form.cleaned_data['niveau']
delete = form.cleaned_data['DELETE']
if not delete and (matiere, niveau) in matieres:
raise forms.ValidationError("Vous ne pouvez pas vous inscrire deux fois pour la même matiere avec le même niveau.")
raise forms.ValidationError(
"Vous ne pouvez pas vous inscrire deux fois pour la "
"même matiere avec le même niveau.")
matieres.append((matiere, niveau))
@login_required
def inscription(request):
profile, created = CofProfile.objects.get_or_create(user = request.user)
profile, created = CofProfile.objects.get_or_create(user=request.user)
if not profile.is_cof:
return redirect("cof-denied")
MatieresFormSet = inlineformset_factory(User, PetitCoursAbility,
fields = ("matiere", "niveau", "agrege",),
formset = BaseMatieresFormSet)
fields=("matiere", "niveau",
"agrege",),
formset=BaseMatieresFormSet)
success = False
if request.method == "POST":
formset = MatieresFormSet(request.POST, instance = request.user)
formset = MatieresFormSet(request.POST, instance=request.user)
if formset.is_valid():
formset.save()
profile.petits_cours_accept = "receive_proposals" in request.POST
profile.petits_cours_remarques = request.POST["remarques"]
profile.save()
lock_table(PetitCoursAttributionCounter, PetitCoursAbility, User, PetitCoursSubject)
abilities = PetitCoursAbility.objects.filter(user = request.user).all()
lock_table(PetitCoursAttributionCounter, PetitCoursAbility, User,
PetitCoursSubject)
abilities = PetitCoursAbility.objects \
.filter(user=request.user).all()
for ability in abilities:
counter = _get_attrib_counter(ability.user, ability.matiere)
unlock_tables()
success = True
formset = MatieresFormSet(instance = request.user)
formset = MatieresFormSet(instance=request.user)
else:
formset = MatieresFormSet(instance = request.user)
return render(request, "inscription-petit-cours.html", {"formset": formset, "success": success, "receive_proposals": profile.petits_cours_accept, "remarques": profile.petits_cours_remarques})
formset = MatieresFormSet(instance=request.user)
return render(request, "inscription-petit-cours.html",
{"formset": formset, "success": success,
"receive_proposals": profile.petits_cours_accept,
"remarques": profile.petits_cours_remarques})
class DemandeForm(ModelForm):
captcha = ReCaptchaField(attrs = {'theme': 'clean', 'lang': 'fr'})
captcha = ReCaptchaField(attrs={'theme': 'clean', 'lang': 'fr'})
def __init__(self, *args, **kwargs):
super(DemandeForm, self).__init__(*args, **kwargs)
@ -334,9 +376,11 @@ class DemandeForm(ModelForm):
class Meta:
model = PetitCoursDemande
fields = ('name', 'email', 'phone', 'quand', 'freq', 'lieu', 'matieres', 'agrege_requis', 'niveau', 'remarques')
fields = ('name', 'email', 'phone', 'quand', 'freq', 'lieu',
'matieres', 'agrege_requis', 'niveau', 'remarques')
widgets = {'matieres': forms.CheckboxSelectMultiple}
@csrf_exempt
def demande(request):
success = False
@ -347,7 +391,9 @@ def demande(request):
success = True
else:
form = DemandeForm()
return render(request, "demande-petit-cours.html", {"form": form, "success": success})
return render(request, "demande-petit-cours.html", {"form": form,
"success": success})
@csrf_exempt
def demande_raw(request):
@ -359,4 +405,5 @@ def demande_raw(request):
success = True
else:
form = DemandeForm()
return render(request, "demande-petit-cours-raw.html", {"form": form, "success": success})
return render(request, "demande-petit-cours-raw.html",
{"form": form, "success": success})

View file

@ -12,17 +12,18 @@ from gestioncof.models import CofProfile, CustomMail
User = get_user_model()
class COFCASBackend(CASBackend):
def authenticate_cas(self, ticket, service, request):
"""Verifies CAS ticket and gets or creates User object"""
client = get_cas_client(service_url=service)
username, attributes, _= client.verify_ticket(ticket)
username, attributes, _ = client.verify_ticket(ticket)
if attributes:
request.session['attributes'] = attributes
if not username:
return None
profiles = CofProfile.objects.filter(login_clipper = username)
profiles = CofProfile.objects.filter(login_clipper=username)
if len(profiles) > 0:
profile = profiles.order_by('-is_cof')[0]
user = profile.user
@ -43,7 +44,7 @@ class COFCASBackend(CASBackend):
try:
profile = user.profile
except CofProfile.DoesNotExist:
profile, created = CofProfile.objects.get_or_create(user = user)
profile, created = CofProfile.objects.get_or_create(user=user)
profile.save()
if not profile.login_clipper:
profile.login_clipper = user.username
@ -57,7 +58,8 @@ class COFCASBackend(CASBackend):
user.save()
return user
def context_processor (request):
def context_processor(request):
'''Append extra data to the context of the given request'''
data = {
"user": request.user,
@ -65,17 +67,20 @@ def context_processor (request):
}
return data
def lock_table(*models):
query = "LOCK TABLES "
for i, model in enumerate(models):
table = model._meta.db_table
if i > 0: query += ", "
if i > 0:
query += ", "
query += "%s WRITE" % table
cursor = connection.cursor()
cursor.execute(query)
row = cursor.fetchone()
return row
def unlock_tables(*models):
cursor = connection.cursor()
cursor.execute("UNLOCK TABLES")
@ -84,15 +89,17 @@ def unlock_tables(*models):
unlock_table = unlock_tables
def send_custom_mail(to, shortname, context = None, from_email = "cof@ens.fr"):
if context is None: context = {}
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)
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)
send_mail(mail.title, message,
from_email, [to],
fail_silently=True)

View file

@ -10,6 +10,7 @@
<h2>Inscription d'un nouveau membre</h2>
<input type="text" name="q" id="search_autocomplete" spellcheck="false" />
<div id="form-placeholder"></div>
<span class="yourlabs-autocomplete"></span>
<script type="text/javascript">
$(document).ready(function() {
$('input#search_autocomplete').yourlabsAutocomplete({
@ -18,6 +19,9 @@
id: 'search_autocomplete',
choiceSelector: 'li:has(a)',
placeholder: "Chercher un utilisateur par nom, prénom ou identifiant clipper",
container: $("#main-content"),
box: $(".yourlabs-autocomplete"),
fixPosition: function() {},
});
$('input#search_autocomplete').bind(
'selectChoice',

View file

@ -19,7 +19,8 @@ petitcours_patterns = [
name='petits-cours-demande-raw'),
url(r'^demandes$', DemandeListView.as_view(),
name='petits-cours-demandes-list'),
url(r'^demandes/(?P<demande_id>\d+)$', 'gestioncof.petits_cours_views.details',
url(r'^demandes/(?P<demande_id>\d+)$',
'gestioncof.petits_cours_views.details',
name='petits-cours-demande-details'),
url(r'^demandes/(?P<demande_id>\d+)/traitement$',
'gestioncof.petits_cours_views.traitement',
@ -38,5 +39,3 @@ events_patterns = [
url(r'^(?P<event_id>\d+)$', 'gestioncof.views.event'),
url(r'^(?P<event_id>\d+)/status$', 'gestioncof.views.event_status'),
]

View file

@ -8,8 +8,10 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import login as django_login_view
from django.contrib.auth.models import User
from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, SurveyQuestionAnswer
from gestioncof.models import Event, EventRegistration, EventOption, EventOptionChoice
from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \
SurveyQuestionAnswer
from gestioncof.models import Event, EventRegistration, EventOption, \
EventOptionChoice
from gestioncof.models import EventCommentField, EventCommentValue
from gestioncof.shared import send_custom_mail
from gestioncof.models import CofProfile, Clipper
@ -20,70 +22,81 @@ from gestioncof.forms import UserProfileForm, EventStatusFilterForm, \
from bda.models import Tirage
@login_required
def home(request):
data = {"surveys": Survey.objects.filter(old=False).all(),
"events": Event.objects.filter(old=False).all(),
"open_surveys": Survey.objects.filter(survey_open=True, old=False).all(),
"open_events": Event.objects.filter(registration_open=True, old=False).all(),
"open_surveys":
Survey.objects.filter(survey_open=True, old=False).all(),
"open_events":
Event.objects.filter(registration_open=True, old=False).all(),
"open_tirages": Tirage.objects.filter(active=True).all()}
return render(request, "home.html", data)
def login(request):
if request.user.is_authenticated():
return redirect("gestioncof.views.home")
return render(request, "login_switch.html", {})
def login_ext(request):
if request.method == "POST" and "username" in request.POST:
try:
user = User.objects.get(username = request.POST["username"])
if not user.has_usable_password() or user.password in ("","!"):
profile, created = CofProfile.objects.get_or_create(user = user)
user = User.objects.get(username=request.POST["username"])
if not user.has_usable_password() or user.password in ("", "!"):
profile, created = CofProfile.objects.get_or_create(user=user)
if profile.login_clipper:
return render(request, "error.html", {"error_type": "use_clipper_login"})
return render(request, "error.html",
{"error_type": "use_clipper_login"})
else:
return render(request, "error.html", {"error_type": "no_password"})
return render(request, "error.html",
{"error_type": "no_password"})
except User.DoesNotExist:
pass
return django_login_view(request, template_name = 'login.html')
return django_login_view(request, template_name='login.html')
@login_required
def logout(request):
try:
profile = request.user.profile
except CofProfile.DoesNotExist:
profile, created = CofProfile.objects.get_or_create(user = request.user)
profile, created = CofProfile.objects.get_or_create(user=request.user)
if profile.login_clipper:
return redirect("django_cas_ng.views.logout")
else:
return redirect("django.contrib.auth.views.logout")
@login_required
def survey(request, survey_id):
survey = get_object_or_404(Survey, id = survey_id)
survey = get_object_or_404(Survey, id=survey_id)
if not survey.survey_open:
raise Http404
success = False
deleted = False
if request.method == "POST":
form = SurveyForm(request.POST, survey = survey)
form = SurveyForm(request.POST, survey=survey)
if request.POST.get('delete'):
try:
current_answer = SurveyAnswer.objects.get(user = request.user, survey = survey)
current_answer = SurveyAnswer.objects.get(user=request.user,
survey=survey)
current_answer.delete()
current_answer = None
except SurveyAnswer.DoesNotExist:
current_answer = None
form = SurveyForm(survey = survey)
form = SurveyForm(survey=survey)
success = True
deleted = True
else:
if form.is_valid():
all_answers = []
for question_id, answers_ids in form.answers():
question = get_object_or_404(SurveyQuestion, id = question_id,
survey = survey)
question = get_object_or_404(SurveyQuestion,
id=question_id,
survey=survey)
if type(answers_ids) != list:
answers_ids = [answers_ids]
if not question.multi_answers and len(answers_ids) > 1:
@ -93,31 +106,39 @@ def survey(request, survey_id):
continue
answer_id = int(answer_id)
answer = SurveyQuestionAnswer.objects.get(
id = answer_id,
survey_question = question)
id=answer_id,
survey_question=question)
all_answers.append(answer)
try:
current_answer = SurveyAnswer.objects.get(user = request.user, survey = survey)
current_answer = SurveyAnswer.objects.get(
user=request.user, survey=survey)
except SurveyAnswer.DoesNotExist:
current_answer = SurveyAnswer(user = request.user, survey = survey)
current_answer = SurveyAnswer(user=request.user,
survey=survey)
current_answer.save()
current_answer.answers = all_answers
current_answer.save()
success = True
else:
try:
current_answer = SurveyAnswer.objects.get(user = request.user, survey = survey)
form = SurveyForm(survey = survey, current_answers = current_answer.answers)
current_answer = SurveyAnswer.objects.get(user=request.user,
survey=survey)
form = SurveyForm(survey=survey,
current_answers=current_answer.answers)
except SurveyAnswer.DoesNotExist:
current_answer = None
form = SurveyForm(survey = survey)
return render(request, "survey.html", {"survey": survey, "form": form, "success": success, "deleted": deleted, "current_answer": current_answer})
form = SurveyForm(survey=survey)
return render(request, "survey.html", {"survey": survey, "form": form,
"success": success,
"deleted": deleted,
"current_answer": current_answer})
def get_event_form_choices(event, form):
all_choices = []
for option_id, choices_ids in form.choices():
option = get_object_or_404(EventOption, id = option_id,
event = event)
option = get_object_or_404(EventOption, id=option_id,
event=event)
if type(choices_ids) != list:
choices_ids = [choices_ids]
if not option.multi_choices and len(choices_ids) > 1:
@ -127,43 +148,52 @@ def get_event_form_choices(event, form):
continue
choice_id = int(choice_id)
choice = EventOptionChoice.objects.get(
id = choice_id,
event_option = option)
id=choice_id,
event_option=option)
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)
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, _) = 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)
event = get_object_or_404(Event, id=event_id)
if not event.registration_open:
raise Http404
success = False
if request.method == "POST":
form = EventForm(request.POST, event = event)
form = EventForm(request.POST, event=event)
if form.is_valid():
all_choices = get_event_form_choices(event, form)
(current_registration, _) = EventRegistration.objects.get_or_create(user = request.user, event = event)
(current_registration, _) = \
EventRegistration.objects.get_or_create(user=request.user,
event=event)
current_registration.options = all_choices
current_registration.save()
success = True
else:
try:
current_registration = EventRegistration.objects.get(user = request.user, event = event)
form = EventForm(event = event, current_choices = current_registration.options)
current_registration = \
EventRegistration.objects.get(user=request.user, event=event)
form = EventForm(event=event,
current_choices=current_registration.options)
except EventRegistration.DoesNotExist:
form = EventForm(event = event)
return render(request, "event.html", {"event": event, "form": form, "success": success})
form = EventForm(event=event)
return render(request, "event.html",
{"event": event, "form": form, "success": success})
def clean_post_for_status(initial):
d = initial.copy()
@ -173,29 +203,34 @@ def clean_post_for_status(initial):
d[k[3:]] = v
return d
@buro_required
def event_status(request, event_id):
event = get_object_or_404(Event, id = event_id)
registrations_query = EventRegistration.objects.filter(event = event)
event = get_object_or_404(Event, id=event_id)
registrations_query = EventRegistration.objects.filter(event=event)
post_data = clean_post_for_status(request.POST)
form = EventStatusFilterForm(post_data or None, event = event)
form = EventStatusFilterForm(post_data or None, event=event)
if form.is_valid():
for option_id, choice_id, value in form.filters():
if option_id == "has_paid":
if value == "yes":
registrations_query = registrations_query.filter(paid = True)
registrations_query = registrations_query.filter(paid=True)
elif value == "no":
registrations_query = registrations_query.filter(paid = False)
registrations_query = registrations_query.filter(
paid=False)
continue
choice = get_object_or_404(EventOptionChoice, id = choice_id, event_option__id = option_id)
choice = get_object_or_404(EventOptionChoice, id=choice_id,
event_option__id=option_id)
if value == "none":
continue
if value == "yes":
registrations_query = registrations_query.filter(options__id__exact = choice.id)
registrations_query = registrations_query.filter(
options__id__exact=choice.id)
elif value == "no":
registrations_query = registrations_query.exclude(options__id__exact = choice.id)
registrations_query = registrations_query.exclude(
options__id__exact=choice.id)
user_choices = registrations_query.prefetch_related("user").all()
options = EventOption.objects.filter(event = event).all()
options = EventOption.objects.filter(event=event).all()
choices_count = {}
for option in options:
for choice in option.choices.all():
@ -203,25 +238,32 @@ def event_status(request, event_id):
for user_choice in user_choices:
for choice in user_choice.options.all():
choices_count[choice.id] += 1
return render(request, "event_status.html", {"event": event, "user_choices": user_choices, "options": options, "choices_count": choices_count, "form": form})
return render(request, "event_status.html",
{"event": event, "user_choices": user_choices,
"options": options, "choices_count": choices_count,
"form": form})
@buro_required
def survey_status(request, survey_id):
survey = get_object_or_404(Survey, id = survey_id)
answers_query = SurveyAnswer.objects.filter(survey = survey)
survey = get_object_or_404(Survey, id=survey_id)
answers_query = SurveyAnswer.objects.filter(survey=survey)
post_data = clean_post_for_status(request.POST)
form = SurveyStatusFilterForm(post_data or None, survey = survey)
form = SurveyStatusFilterForm(post_data or None, survey=survey)
if form.is_valid():
for question_id, answer_id, value in form.filters():
answer = get_object_or_404(SurveyQuestionAnswer, id = answer_id, survey_question__id = question_id)
answer = get_object_or_404(SurveyQuestionAnswer, id=answer_id,
survey_question__id=question_id)
if value == "none":
continue
if value == "yes":
answers_query = answers_query.filter(answers__id__exact = answer.id)
answers_query = answers_query.filter(
answers__id__exact=answer.id)
elif value == "no":
answers_query = answers_query.exclude(answers__id__exact = answer.id)
answers_query = answers_query.exclude(
answers__id__exact=answer.id)
user_answers = answers_query.prefetch_related("user").all()
questions = SurveyQuestion.objects.filter(survey = survey).all()
questions = SurveyQuestion.objects.filter(survey=survey).all()
answers_count = {}
for question in questions:
for answer in question.answers.all():
@ -229,31 +271,37 @@ def survey_status(request, survey_id):
for user_answer in user_answers:
for answer in user_answer.answers.all():
answers_count[answer.id] += 1
return render(request, "survey_status.html", {"survey": survey, "user_answers": user_answers, "questions": questions, "answers_count": answers_count, "form": form})
return render(request, "survey_status.html",
{"survey": survey, "user_answers": user_answers,
"questions": questions, "answers_count": answers_count,
"form": form})
@login_required
def profile(request):
success = False
if request.method == "POST":
form = UserProfileForm(request.POST, instance = request.user.profile)
form = UserProfileForm(request.POST, instance=request.user.profile)
if form.is_valid():
form.save()
success = True
else:
form = UserProfileForm(instance = request.user.profile)
form = UserProfileForm(instance=request.user.profile)
return render(request, "profile.html", {"form": form, "success": success})
def registration_set_ro_fields(user_form, profile_form):
user_form.fields['username'].widget.attrs['readonly'] = True
profile_form.fields['login_clipper'].widget.attrs['readonly'] = True
@buro_required
def registration_form(request, login_clipper = None, username = None):
def registration_form(request, login_clipper=None, username=None):
member = None
if login_clipper:
clipper = get_object_or_404(Clipper, username = login_clipper)
try: # check if the given user is already registered
member = User.objects.filter(username = login_clipper).get()
clipper = get_object_or_404(Clipper, username=login_clipper)
try: # check if the given user is already registered
member = User.objects.filter(username=login_clipper).get()
username = member.username
login_clipper = None
except User.DoesNotExist:
@ -261,7 +309,8 @@ def registration_form(request, login_clipper = None, username = None):
user_form = RegistrationUserForm()
profile_form = RegistrationProfileForm()
user_form.fields['username'].initial = login_clipper
user_form.fields['email'].initial = login_clipper + "@clipper.ens.fr"
user_form.fields['email'].initial = \
login_clipper + "@clipper.ens.fr"
profile_form.fields['login_clipper'].initial = login_clipper
if clipper.fullname:
bits = clipper.fullname.split(" ")
@ -270,35 +319,39 @@ def registration_form(request, login_clipper = None, username = None):
user_form.fields['last_name'].initial = " ".join(bits[1:])
registration_set_ro_fields(user_form, profile_form)
if username:
member = get_object_or_404(User, username = username)
(profile, _) = CofProfile.objects.get_or_create(user = member)
member = get_object_or_404(User, username=username)
(profile, _) = CofProfile.objects.get_or_create(user=member)
# already existing, prefill
user_form = RegistrationUserForm(instance = member)
profile_form = RegistrationProfileForm(instance = profile)
user_form = RegistrationUserForm(instance=member)
profile_form = RegistrationProfileForm(instance=profile)
registration_set_ro_fields(user_form, profile_form)
elif not login_clipper:
# new user
user_form = RegistrationUserForm()
profile_form = RegistrationProfileForm()
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})
@buro_required
def registration_form2(request, login_clipper = None, username = None):
events = Event.objects.filter(old = False).all()
def registration_form2(request, login_clipper=None, username=None):
events = Event.objects.filter(old=False).all()
member = None
if login_clipper:
clipper = get_object_or_404(Clipper, username = login_clipper)
try: # check if the given user is already registered
member = User.objects.filter(username = login_clipper).get()
clipper = get_object_or_404(Clipper, username=login_clipper)
try: # check if the given user is already registered
member = User.objects.filter(username=login_clipper).get()
username = member.username
login_clipper = None
except User.DoesNotExist:
# new user, but prefill
user_form = RegistrationUserForm()
profile_form = RegistrationProfileForm()
event_forms = [AdminEventForm(event = event) for event in events]
event_forms = [AdminEventForm(event=event) for event in events]
user_form.fields['username'].initial = login_clipper
user_form.fields['email'].initial = login_clipper + "@clipper.ens.fr"
user_form.fields['email'].initial = \
login_clipper + "@clipper.ens.fr"
profile_form.fields['login_clipper'].initial = login_clipper
if clipper.fullname:
bits = clipper.fullname.split(" ")
@ -307,26 +360,34 @@ def registration_form2(request, login_clipper = None, username = None):
user_form.fields['last_name'].initial = " ".join(bits[1:])
registration_set_ro_fields(user_form, profile_form)
if username:
member = get_object_or_404(User, username = username)
(profile, _) = CofProfile.objects.get_or_create(user = member)
member = get_object_or_404(User, username=username)
(profile, _) = CofProfile.objects.get_or_create(user=member)
# already existing, prefill
user_form = RegistrationUserForm(instance = member)
profile_form = RegistrationProfileForm(instance = profile)
user_form = RegistrationUserForm(instance=member)
profile_form = RegistrationProfileForm(instance=profile)
registration_set_ro_fields(user_form, profile_form)
event_forms = []
for event in events:
try:
current_registration = EventRegistration.objects.get(user = member, event = event)
form = AdminEventForm(event = event, current_registration = current_registration, paid = current_registration.paid)
current_registration = EventRegistration.objects.get(
user=member, event=event)
form = AdminEventForm(
event=event,
current_registration=current_registration,
paid=current_registration.paid)
except EventRegistration.DoesNotExist:
form = AdminEventForm(event = event)
form = AdminEventForm(event=event)
event_forms.append(form)
elif not login_clipper:
# new user
user_form = RegistrationUserForm()
profile_form = RegistrationProfileForm()
event_forms = [AdminEventForm(event = event) for event in events]
return render(request, "registration_form.html", {"user_form": user_form, "profile_form": profile_form, "member": member, "login_clipper": login_clipper, "event_forms": event_forms})
event_forms = [AdminEventForm(event=event) for event in events]
return render(request, "registration_form.html",
{"user_form": user_form, "profile_form": profile_form,
"member": member, "login_clipper": login_clipper,
"event_forms": event_forms})
@buro_required
def registration(request):
@ -337,113 +398,144 @@ def registration(request):
success = False
user_form = RegistrationUserForm(request_dict)
profile_form = RegistrationProfileForm(request_dict)
events = Event.objects.filter(old = False).all()
event_forms = [AdminEventForm(request_dict, event = event) for event in events]
events = Event.objects.filter(old=False).all()
event_forms = \
[AdminEventForm(request_dict, event=event) for event in events]
user_form.is_valid()
profile_form.is_valid()
for event_form in event_forms: event_form.is_valid()
for event_form in event_forms:
event_form.is_valid()
member = None
login_clipper = None
if "user_exists" in request_dict and request_dict["user_exists"]:
username = request_dict["username"]
try:
member = User.objects.filter(username = username).get()
(profile, _) = CofProfile.objects.get_or_create(user = member)
user_form = RegistrationUserForm(request_dict, instance = member)
profile_form = RegistrationProfileForm(request_dict, instance = profile)
member = User.objects.filter(username=username).get()
(profile, _) = CofProfile.objects.get_or_create(user=member)
user_form = RegistrationUserForm(request_dict, instance=member)
profile_form = RegistrationProfileForm(request_dict,
instance=profile)
except User.DoesNotExist:
try:
clipper = Clipper.objects.filter(username = username).get()
clipper = Clipper.objects.filter(username=username).get()
login_clipper = clipper.username
except Clipper.DoesNotExist:
pass
for form in event_forms:
if not form.is_valid(): break
if form.cleaned_data['status'] == 'no': continue
if not form.is_valid():
break
if form.cleaned_data['status'] == 'no':
continue
all_choices = get_event_form_choices(form.event, form)
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()
(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
profile_form = RegistrationProfileForm(request_dict, instance = profile)
profile_form = RegistrationProfileForm(request_dict,
instance=profile)
profile_form.is_valid()
profile_form.save()
(profile, _) = CofProfile.objects.get_or_create(user = member)
(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:
current_registration = EventRegistration.objects.get(user = member, event = form.event)
current_registration = EventRegistration.objects.get(
user=member, event=form.event)
current_registration.delete()
except EventRegistration.DoesNotExist:
pass
continue
all_choices = get_event_form_choices(form.event, form)
(current_registration, created_reg) = 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.paid = \
(form.cleaned_data['status'] == 'paid')
current_registration.save()
if event.title == "Mega 15" and created_reg:
field = EventCommentField.objects.get(event = event, name = "Commentaires")
field = EventCommentField.objects.get(event=event,
name="Commentaires")
try:
comments = EventCommentValue.objects.get(commentfield = field, registration = current_registration).content
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})
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:
return render(request, "registration.html")
@buro_required
def export_members(request):
response = HttpResponse(content_type = 'text/csv')
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=membres_cof.csv'
writer = unicodecsv.writer(response)
for profile in CofProfile.objects.filter(is_cof = True).all():
for profile in CofProfile.objects.filter(is_cof=True).all():
user = profile.user
bits = [profile.num, user.username, user.first_name, user.last_name, user.email, profile.phone, profile.occupation, profile.departement, profile.type_cotiz]
bits = [profile.num, user.username, user.first_name, user.last_name,
user.email, profile.phone, profile.occupation,
profile.departement, profile.type_cotiz]
writer.writerow([unicode(bit) for bit in bits])
return response
@buro_required
def csv_export_mega(filename, qs):
response = HttpResponse(content_type = 'text/csv')
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=' + filename
writer = unicodecsv.writer(response)
for reg in qs.all():
user = reg.user
profile = user.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]
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_2015.csv'
response = HttpResponse(content_type = 'text/csv')
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=' + filename
writer = unicodecsv.writer(response)
event = Event.objects.get(title = "Mega 15")
commentfield = event.commentfields.get(name = "Commentaires")
event = Event.objects.get(title="Mega 15")
commentfield = event.commentfields.get(name="Commentaires")
for val in commentfield.values.all():
reg = val.registration
user = reg.user
profile = user.profile
bits = [user.username, user.first_name, user.last_name, user.email, profile.phone, profile.num, profile.comments, val.content]
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 élève",
@ -454,60 +546,75 @@ def export_mega_bytype(request, type):
if type not in types:
raise Http404
event = Event.objects.get(title = "Mega 15")
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)
event = Event.objects.get(title="Mega 15")
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_2015.csv', qs)
@buro_required
def export_mega_orgas(request):
event = Event.objects.get(title = "Mega 15")
type_option = event.options.get(name = "Type")
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))
event = Event.objects.get(title="Mega 15")
type_option = event.options.get(name="Type")
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_15.csv', qs)
@buro_required
def export_mega_participants(request):
event = Event.objects.get(title = "Mega 15")
type_option = event.options.get(name = "Type")
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))
event = Event.objects.get(title="Mega 15")
type_option = event.options.get(name="Type")
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_15.csv', qs)
@buro_required
def export_mega(request):
event = Event.objects.filter(title = "Mega 15")
qs = EventRegistration.objects.filter(event = event).order_by("user__username")
event = Event.objects.filter(title="Mega 15")
qs = EventRegistration.objects.filter(event=event) \
.order_by("user__username")
return csv_export_mega('all_mega_2015.csv', qs)
@buro_required
def utile_cof(request):
return render(request, "utile_cof.html", {})
return render(request, "utile_cof.html", {})
@buro_required
def utile_bda(request):
tirages = Tirage.objects.all()
return render(request, "utile_bda.html", {'tirages': tirages})
@buro_required
def liste_bdadiff(request):
titre = "BdA diffusion"
personnes = CofProfile.objects.filter(mailing_bda = True, is_cof = True).all()
return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes})
personnes = CofProfile.objects.filter(mailing_bda=True, is_cof=True).all()
return render(request, "liste_mails.html",
{"titre": titre, "personnes": personnes})
@buro_required
def liste_bdarevente(request):
titre = "BdA revente"
personnes = CofProfile.objects.filter(mailing_bda_revente = True, is_cof = True).all()
return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes})
personnes = CofProfile.objects.filter(mailing_bda_revente=True,
is_cof=True).all()
return render(request, "liste_mails.html", {"titre": titre,
"personnes": personnes})
@buro_required
def liste_diffcof(request):
titre = "Diffusion COF"
personnes = CofProfile.objects.filter(mailing_cof = True, is_cof = True).all()
return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes})
personnes = CofProfile.objects.filter(mailing_cof=True, is_cof=True).all()
return render(request, "liste_mails.html", {"titre": titre,
"personnes": personnes})

View file

@ -2,8 +2,8 @@ from django.forms.widgets import Widget
from django.forms.utils import flatatt
from django.utils.safestring import mark_safe
class TriStateCheckbox(Widget):
class TriStateCheckbox(Widget):
def __init__(self, attrs=None, choices=()):
super(TriStateCheckbox, self).__init__(attrs)
# choices can be any iterable, but we may need to render this widget
@ -12,7 +12,8 @@ class TriStateCheckbox(Widget):
self.choices = list(choices)
def render(self, name, value, attrs=None, choices=()):
if value is None: value = 'none'
if value is None:
value = 'none'
final_attrs = self.build_attrs(attrs, value=value)
output = [u"<span class=\"tristate\"%s></span>" % flatatt(final_attrs)]
return mark_safe('\n'.join(output))

View file

@ -22,7 +22,7 @@ if __name__ == "__main__":
clipper.save()
print "Updated", username
else:
clipper = Clipper(username = username, fullname = fullname)
clipper = Clipper(username=username, fullname=fullname)
clipper.save()
print "Created", username
print "[ DONE ]"

View file

@ -15,16 +15,19 @@ if __name__ == "__main__":
start = time.time()
shows = Spectacle.objects.all()
members = Participant.objects.all()
choices = ChoixSpectacle.objects.order_by('participant', 'priority').select_related().all()
choices = ChoixSpectacle.objects.order_by('participant', 'priority') \
.select_related().all()
available_slots = Spectacle.objects.aggregate(Sum('slots'))['slots__sum']
cursor = connection.cursor()
cursor.execute("SELECT SUM(`slots` * `price`) AS `total` FROM `bda_spectacle`;")
cursor.execute(
"SELECT SUM(`slots` * `price`) AS `total` FROM `bda_spectacle`;")
total_price = cursor.fetchone()[0]
print "%d spectacles" % len(shows)
print "%d places" % available_slots
print "%d participants" % len(members)
print "%d demandes" % len(choices)
print "%d places demandées" % (len(choices) + len(choices.filter(double = True).all()))
print "%d places demandées" % (len(choices)
+ len(choices.filter(double=True).all()))
print "%.02f€ à brasser" % total_price
print "Récupération: %.2fs" % (time.time() - start)
start_init = time.time()
@ -54,7 +57,8 @@ if __name__ == "__main__":
members2[member].append(show)
member.total += show.price
if len(members) < show.slots:
print "%d place(s) invendue(s) pour %s" % (show.slots - len(members), show)
print "%d place(s) invendue(s) pour %s" \
% (show.slots - len(members), show)
members2 = members2.items()
print "Temps total: %.2fs" % (time.time() - start)
print "Requêtes SQL:"