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 604ac883..26d9a865 100644
--- a/bda/admin.py
+++ b/bda/admin.py
@@ -4,38 +4,64 @@ 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
+ extra = 0
+
+ def get_queryset(self, request):
+ qs = super(AttributionInline, self).get_queryset(request)
+ return qs.filter(spectacle__listing=False)
+
+
+class AttributionInlineListing(admin.TabularInline):
+ model = Attribution
+ exclude = ('given', )
+ extra = 0
+
+ def get_queryset(self, request):
+ qs = super(AttributionInlineListing, self).get_queryset(request)
+ return qs.filter(spectacle__listing=True)
+
class ParticipantAdmin(admin.ModelAdmin):
- inlines = [AttributionInline]
+ inlines = [AttributionInline, AttributionInlineListing]
+
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 +75,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 +96,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 +122,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,42 +134,57 @@ 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
- list_display = ("title", "date", "tirage", "location", "slots", "price")
+ list_display = ("title", "date", "tirage", "location", "slots", "price",
+ "listing")
list_filter = ("location", "tirage",)
search_fields = ("title", "location__name")
+ readonly_fields = ("rappel_sent", )
+
class TirageAdmin(admin.ModelAdmin):
model = Tirage
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/migrations/0004_mails-rappel.py b/bda/migrations/0004_mails-rappel.py
new file mode 100644
index 00000000..f17b711f
--- /dev/null
+++ b/bda/migrations/0004_mails-rappel.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('bda', '0003_update_tirage_and_spectacle'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='spectacle',
+ name='listing',
+ field=models.BooleanField(default=False, verbose_name=b'Les places sont sur listing'),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='spectacle',
+ name='rappel_sent',
+ field=models.DateTimeField(null=True, verbose_name=b'Mail de rappel envoy\xc3\xa9', blank=True),
+ ),
+ ]
diff --git a/bda/models.py b/bda/models.py
index 16ea4818..349a71e4 100644
--- a/bda/models.py
+++ b/bda/models.py
@@ -4,7 +4,17 @@ import calendar
from django.db import models
from django.contrib.auth.models import User
-from django.utils.translation import ugettext_lazy as _
+from django.template import loader, Context
+from django.core import mail
+from django.conf import settings
+from django.utils import timezone
+
+
+def render_template(template_name, data):
+ tmpl = loader.get_template(template_name)
+ ctxt = Context(data)
+ return tmpl.render(ctxt)
+
class Tirage(models.Model):
title = models.CharField("Titre", max_length=300)
@@ -18,14 +28,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")
@@ -36,12 +48,15 @@ class Spectacle(models.Model):
slots = models.IntegerField("Places")
priority = models.IntegerField("Priorité", default=1000)
tirage = models.ForeignKey(Tirage)
+ listing = models.BooleanField("Les places sont sur listing")
+ rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True,
+ null=True)
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 +65,65 @@ 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)
+
+ def send_rappel(self):
+ # On récupère la liste des participants
+ members = {}
+ for attr in Attribution.objects.filter(spectacle=self).all():
+ member = attr.participant.user
+ if member.id in members:
+ members[member.id].nb_attr = 2
+ else:
+ member.nb_attr = 1
+ members[member.id] = member
+ # On écrit un mail personnalisé à chaque participant
+ mails_to_send = []
+ mail_object = "%s - %s - %s" % (self.title, self.date_no_seconds(),
+ self.location)
+ for member in members.values():
+ mail_body = render_template('mail-rappel.txt', {
+ 'member': member,
+ 'show': self})
+ mail_tot = mail.EmailMessage(
+ mail_object, mail_body,
+ settings.RAPPEL_FROM, [member.email],
+ [], headers={'Reply-To': settings.RAPPEL_REPLY_TO})
+ mails_to_send.append(mail_tot)
+ # On envoie les mails
+ connection = mail.get_connection()
+ connection.send_messages(mails_to_send)
+ # On enregistre le fait que l'envoi a bien eu lieu
+ self.rappel_sent = timezone.now()
+ self.save()
+ # On renvoie la liste des destinataires
+ return members.values()
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 +132,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 +155,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/templates/mail-rappel.txt b/bda/templates/mail-rappel.txt
new file mode 100644
index 00000000..5152b1db
--- /dev/null
+++ b/bda/templates/mail-rappel.txt
@@ -0,0 +1,23 @@
+Bonjour {{ member.get_full_name }},
+
+Nous te rappellons que tu as eu la chance d'obtenir {{ member.nb_attr|pluralize:"une place,deux places" }}
+pour {{ show.title }}, le {{ show.date_no_seconds }} au {{ show.location }}. N'oublie pas de t'y rendre !
+{% if member.nb_attr == 2 %}
+Tu as obtenu deux places pour ce spectacle. Nous te rappelons que
+ces places sont strictement réservées aux personnes de moins de 28 ans.
+{% endif %}
+{% if show.listing %}Pour ce spectacle, tu as reçu des places sur
+listing. Il te faudra donc te rendre 15 minutes en avance sur les lieux de la représentation
+pour retirer {{ member.nb_attr|pluralize:"ta place,tes places" }}.
+{% else %}Pour assister à ce spectacle, tu dois présenter les billets qui ont
+été distribués au burô.
+{% endif %}
+
+Si tu ne peux plus assister à cette représentation, tu peux
+revendre ta place via BdA-revente, accessible directement sur
+GestioCOF (lien "revendre une place du premier tirage" sur la page
+d'accueil https://www.cof.ens.fr/gestion/).
+
+En te souhaitant un excellent spectacle,
+
+Le Bureau des Arts
diff --git a/bda/templates/mails-rappel.html b/bda/templates/mails-rappel.html
new file mode 100644
index 00000000..3fc15fa2
--- /dev/null
+++ b/bda/templates/mails-rappel.html
@@ -0,0 +1,35 @@
+{% extends "base_title.html" %}
+
+{% block realcontent %}
+{% if sent %}
+
Les mails de rappel pour le spectacle {{ show.title }} ont bien été envoyés aux personnes suivantes
+
+{% for member in members %}
+
{{ member.get_full_name }} ({{ member.email }})
+{% endfor %}
+
+{% else %}
+
Voulez vous envoyer les mails de rappel pour le spectacle
+ {{ show.title }} ?
+ {% if show.rappel_sent %}
+
Attention, les mails ont déjà été envoyés le
+ {{ show.rappel_sent }}
+ {% endif %}
+{% endif %}
+
+{% if not sent %}
+
+{% endif %}
+
+
Forme des mails
+
+ Une seule place
+
{{ exemple_mail_1place }}
+
+ Deux places
+
{{ exemple_mail_2places }}
+{% endblock %}
diff --git a/bda/urls.py b/bda/urls.py
index d3f4fe2f..47a946ba 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,15 +22,16 @@ 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"),
+ url(r'mails-rappel/(?P\d+)$', "bda.views.send_rappel"),
)
diff --git a/bda/views.py b/bda/views.py
index 1eab066d..844ef60c 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, render_template
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:
@@ -237,10 +249,10 @@ def do_tirage(request, tirage_id):
# À partir d'ici, le tirage devient effectif
# FIXME: Établir les conditions de validations (formulaire ?)
# cf. issue #32
- if False:
+ if True:
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,84 @@ 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")
+
+@buro_required
+def send_rappel(request, spectacle_id):
+ show = get_object_or_404(Spectacle, id=spectacle_id)
+ # Mails d'exemples
+ fake_member = request.user
+ fake_member.nb_attr = 1
+ exemple_mail_1place = render_template('mail-rappel.txt', {
+ 'member': fake_member,
+ 'show': show})
+ fake_member.nb_attr = 2
+ exemple_mail_2places = render_template('mail-rappel.txt', {
+ 'member': fake_member,
+ 'show': show})
+ # Contexte
+ ctxt = {'show': show,
+ 'exemple_mail_1place': exemple_mail_1place,
+ 'exemple_mail_2places': exemple_mail_2places}
+ # Envoi confirmé
+ if request.method == 'POST':
+ members = show.send_rappel()
+ ctxt['sent'] = True
+ ctxt['members'] = members
+ # Demande de confirmation
+ else:
+ ctxt['sent'] = False
+ return render(request, "mails-rappel.html", ctxt)
diff --git a/cof/settings_dev.py b/cof/settings_dev.py
index 75f67646..1ef05f8c 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
@@ -130,6 +136,9 @@ PETITS_COURS_FROM = "Le COF "
PETITS_COURS_BCC = "archivescof@gmail.com"
PETITS_COURS_REPLYTO = "cof@ens.fr"
+RAPPEL_FROM = 'Le BdA '
+RAPPEL_REPLY_TO = RAPPEL_FROM
+
LOGIN_URL = "/login"
LOGIN_REDIRECT_URL = "/"
@@ -148,11 +157,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 %}