diff --git a/.gitignore b/.gitignore index 7d9fd123..2d2b0cee 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ settings.py venv/ .vagrant /src +media/ diff --git a/apache/wsgi.py b/apache/wsgi.py index 254e36f5..b7a1eb92 100644 --- a/apache/wsgi.py +++ b/apache/wsgi.py @@ -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() diff --git a/bda/admin.py b/bda/admin.py index 8644a264..48d0c955 100644 --- a/bda/admin.py +++ b/bda/admin.py @@ -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", diff --git a/bda/algorithm.py b/bda/algorithm.py index 77770377..005e714f 100644 --- a/bda/algorithm.py +++ b/bda/algorithm.py @@ -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 - diff --git a/bda/autocomplete_light_registry.py b/bda/autocomplete_light_registry.py index 3254b3c8..7aa43b07 100644 --- a/bda/autocomplete_light_registry.py +++ b/bda/autocomplete_light_registry.py @@ -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...'}) diff --git a/bda/forms.py b/bda/forms.py index acc2d4b5..213ec17b 100644 --- a/bda/forms.py +++ b/bda/forms.py @@ -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() diff --git a/bda/models.py b/bda/models.py index 01429dc2..8e235509 100644 --- a/bda/models.py +++ b/bda/models.py @@ -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") @@ -20,14 +21,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") @@ -41,9 +44,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): @@ -52,31 +55,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 = ( @@ -85,12 +90,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" @@ -106,11 +113,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) - diff --git a/bda/urls.py b/bda/urls.py index d3f4fe2f..4557ec86 100644 --- a/bda/urls.py +++ b/bda/urls.py @@ -3,7 +3,8 @@ from django.conf.urls import url, patterns from bda.views import SpectacleListView -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'inscription/(?P\d+)$', 'bda.views.inscription', name='bda-tirage-inscription'), @@ -21,14 +22,14 @@ urlpatterns = patterns('', name='bda-etat-places'), url(r'tirage/(?P\d+)$', 'bda.views.tirage'), url(r'spectacles/(?P\d+)$', - SpectacleListView.as_view() , - name ="bda-liste-spectacles"), + SpectacleListView.as_view(), + name="bda-liste-spectacles"), url(r'spectacles/(?P\d+)/(?P\d+)$', "bda.views.spectacle", name="bda-spectacle"), url(r'spectacles-ics/(?P\d+)$', 'bda.views.liste_spectacles_ics', - name ="bda-liste-spectacles-ics"), + name="bda-liste-spectacles-ics"), url(r'spectacles/unpaid/(?P\d+)$', "bda.views.unpaid", name="bda-unpaid"), diff --git a/bda/views.py b/bda/views.py index 39ed17e2..846aaeae 100644 --- a/bda/views.py +++ b/bda/views.py @@ -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) @@ -188,8 +199,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 @@ -219,8 +230,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: @@ -235,7 +247,7 @@ def do_tirage(request, tirage_id): # À partir d'ici, le tirage devient effectif Attribution.objects.filter(spectacle__tirage=tirage_elt).delete() tirage_elt.tokens += "%s\n\"\"\"%s\"\"\"\n" % ( - timezone.now().strftime("%y-%m-%d %H:%M:%S"), + timezone.now().strftime("%y-%m-%d %H:%M:%S"), form.cleaned_data['token']) tirage_elt.enable_do_tirage = False tirage_elt.save() @@ -247,6 +259,7 @@ def do_tirage(request, tirage_id): else: return render(request, "bda-attrib.html", data) + @buro_required def tirage(request, tirage_id): tirage_elt = get_object_or_404(Tirage, id=tirage_id) @@ -260,6 +273,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"] @@ -270,18 +284,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: @@ -290,7 +306,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): @@ -300,52 +318,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") diff --git a/cof/settings_dev.py b/cof/settings_dev.py index 75f67646..36cd8a3a 100644 --- a/cof/settings_dev.py +++ b/cof/settings_dev.py @@ -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(): diff --git a/cof/urls.py b/cof/urls.py index f0b3c207..b5d2b65a 100644 --- a/cof/urls.py +++ b/cof/urls.py @@ -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[\w-]+)$', - 'gestioncof.views.registration_form2', name="clipper-registration"), + 'gestioncof.views.registration_form2', name="clipper-registration"), url(r'^registration/user/(?P.+)$', - '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[\d\w]+)/(?P[\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. diff --git a/gestioncof/admin.py b/gestioncof/admin.py index 09ac61fd..bb03bbd4 100644 --- a/gestioncof/admin.py +++ b/gestioncof/admin.py @@ -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("%s" % (url, link_text(link_obj))) + url = reverse(reverse_path, args=(link_obj.id,)) + return mark_safe("%s" + % (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 - # ) + """ + hack required to make Django validate (if obj is + None, then we're a class, and classes are callable + ) + """ + 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) diff --git a/gestioncof/autocomplete.py b/gestioncof/autocomplete.py index c431a5cc..21b5c3bc 100644 --- a/gestioncof/autocomplete.py +++ b/gestioncof/autocomplete.py @@ -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) diff --git a/gestioncof/autocomplete_light_registry.py b/gestioncof/autocomplete_light_registry.py index f3283f8e..4a2737cb 100644 --- a/gestioncof/autocomplete_light_registry.py +++ b/gestioncof/autocomplete_light_registry.py @@ -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...'}) diff --git a/gestioncof/csv_views.py b/gestioncof/csv_views.py index f921953f..733768dc 100644 --- a/gestioncof/csv_views.py +++ b/gestioncof/csv_views.py @@ -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[\d\w]+)/(?P[\d\w]+)/csv/', 'util.csv_view.admin_list_export'), + (r'^admin/(?P[\d\w]+)/(?P[\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 %} {% endblock %} diff --git a/gestioncof/decorators.py b/gestioncof/decorators.py index 1a3d60c5..62c527df 100644 --- a/gestioncof/decorators.py +++ b/gestioncof/decorators.py @@ -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: diff --git a/gestioncof/forms.py b/gestioncof/forms.py index a470face..bf983df5 100644 --- a/gestioncof/forms.py +++ b/gestioncof/forms.py @@ -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) - diff --git a/gestioncof/migrations/0003_event_image.py b/gestioncof/migrations/0003_event_image.py new file mode 100644 index 00000000..6d65b1a6 --- /dev/null +++ b/gestioncof/migrations/0003_event_image.py @@ -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), + ), + ] diff --git a/gestioncof/models.py b/gestioncof/models.py index 2086557e..badf7533 100644 --- a/gestioncof/models.py +++ b/gestioncof/models.py @@ -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) diff --git a/gestioncof/petits_cours_models.py b/gestioncof/petits_cours_models.py index 47339a4e..a9aede0c 100644 --- a/gestioncof/petits_cours_models.py +++ b/gestioncof/petits_cours_models.py @@ -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) diff --git a/gestioncof/petits_cours_views.py b/gestioncof/petits_cours_views.py index a5347948..2e8c420a 100644 --- a/gestioncof/petits_cours_views.py +++ b/gestioncof/petits_cours_views.py @@ -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": ""}) + "extra": + '' + }) 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}) diff --git a/gestioncof/shared.py b/gestioncof/shared.py index 4bdd9aa5..4fb2e4bc 100644 --- a/gestioncof/shared.py +++ b/gestioncof/shared.py @@ -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) diff --git a/gestioncof/templates/registration.html b/gestioncof/templates/registration.html index d7205809..18434660 100644 --- a/gestioncof/templates/registration.html +++ b/gestioncof/templates/registration.html @@ -10,6 +10,7 @@

Inscription d'un nouveau membre

+