diff --git a/bda/admin.py b/bda/admin.py
index e07a5d49..87a269d4 100644
--- a/bda/admin.py
+++ b/bda/admin.py
@@ -5,12 +5,13 @@ from __future__ import print_function
from __future__ import unicode_literals
from django.core.mail import send_mail
-
from django.contrib import admin
from django.db.models import Sum, Count
+from django.template.defaultfilters import pluralize
+from django.utils import timezone
+from django import forms
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
-from django import forms
from datetime import timedelta
@@ -211,9 +212,15 @@ class SalleAdmin(admin.ModelAdmin):
class SpectacleReventeAdmin(admin.ModelAdmin):
+ """
+ Administration des reventes de spectacles
+ """
model = SpectacleRevente
def spectacle(self, obj):
+ """
+ Raccourci vers le spectacle associé à la revente.
+ """
return obj.attribution.spectacle
list_display = ("spectacle", "seller", "date", "soldTo")
@@ -224,6 +231,48 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
'seller__user__first_name',
'seller__user__last_name']
+ actions = ['transfer', 'reinit']
+ actions_on_bottom = True
+
+ def transfer(self, request, queryset):
+ """
+ Effectue le transfert des reventes pour lesquels on connaît l'acheteur.
+ """
+ reventes = queryset.exclude(soldTo__isnull=True).all()
+ count = reventes.count()
+ for revente in reventes:
+ attrib = revente.attribution
+ attrib.participant = revente.soldTo
+ attrib.save()
+ self.message_user(
+ request,
+ "%d attribution%s %s été transférée%s avec succès." % (
+ count, pluralize(count),
+ pluralize(count, "a,ont"), pluralize(count))
+ )
+ transfer.short_description = "Transférer les reventes sélectionnées"
+
+ def reinit(self, request, queryset):
+ """
+ Réinitialise les reventes.
+ """
+ count = queryset.count()
+ for revente in queryset.all():
+ revente.date = timezone.now() - timedelta(hours=1)
+ revente.soldTo = None
+ revente.notif_sent = False
+ revente.tirage_done = False
+ if revente.answered_mail:
+ revente.answered_mail.clear()
+ revente.save()
+ self.message_user(
+ request,
+ "%d attribution%s %s été réinitialisée%s avec succès." % (
+ count, pluralize(count),
+ pluralize(count, "a,ont"), pluralize(count))
+ )
+ reinit.short_description = "Réinitialiser les reventes sélectionnées"
+
admin.site.register(CategorieSpectacle)
admin.site.register(Spectacle, SpectacleAdmin)
diff --git a/bda/models.py b/bda/models.py
index 08f17bbe..6fb22189 100644
--- a/bda/models.py
+++ b/bda/models.py
@@ -320,33 +320,51 @@ class SpectacleRevente(models.Model):
self.save()
def tirage(self):
- inscrits = self.answered_mail
+ inscrits = list(self.answered_mail.all())
spectacle = self.attribution.spectacle
seller = self.seller
- if inscrits.exists():
- winner = random.choice(inscrits.all())
+
+ if inscrits:
+ mails = []
+ mail_subject = "BdA-Revente : {:s}".format(spectacle.title)
+
+ # Envoie un mail au gagnant et au vendeur
+ winner = random.choice(inscrits)
self.soldTo = winner
- mail_buyer = """Bonjour,
+ context = {
+ 'acheteur': winner.user,
+ 'vendeur': seller.user,
+ 'spectacle': spectacle,
+ }
+ mails.append(mail.EmailMessage(
+ mail_subject, loader.render_to_string('mail-revente-winner.txt', context),
+ from_email=settings.MAIL_DATA['revente']['FROM'],
+ to=[winner.user.email],
+ reply_to=[seller.user.email],
+ ))
+ mails.append(mail.EmailMessage(
+ mail_subject, loader.render_to_string('mail-revente-seller.txt', context),
+ from_email=settings.MAIL_DATA['revente']['FROM'],
+ to=[seller.user.email],
+ reply_to=[winner.user.email],
+ ))
-Tu as été tiré-e au sort pour racheter une place pour %s le %s (%s) à %0.02f€.
-Tu peux contacter le/la vendeur-se à l'adresse %s.
+ # Envoie un mail aux perdants
+ for inscrit in inscrits:
+ if inscrit == winner:
+ continue
-Chaleureusement,
-Le BdA""" % (spectacle.title, spectacle.date_no_seconds(),
- spectacle.location, spectacle.price, seller.user.email)
-
- mail.send_mail("BdA-Revente : %s" % spectacle.title,
- mail_buyer, "bda@ens.fr", [winner.user.email],
- fail_silently=False)
- mail_seller = """Bonjour,
-La personne tirée au sort pour racheter ta place pour %s est %s.
-Tu peux le/la contacter à l'adresse %s.
-
-Chaleureusement,
-Le BdA""" % (spectacle.title, winner.user.get_full_name(), winner.user.email)
-
- mail.send_mail("BdA-Revente : %s" % spectacle.title,
- mail_seller, "bda@ens.fr", [seller.user.email],
- fail_silently=False)
+ mail_body = loader.render_to_string('mail-revente-loser.txt', {
+ 'acheteur': inscrit.user,
+ 'vendeur': seller.user,
+ 'spectacle': spectacle,
+ })
+ mails.append(mail.EmailMessage(
+ mail_subject, mail_body,
+ from_email=settings.MAIL_DATA['revente']['FROM'],
+ to=[inscrit.user.email],
+ reply_to=[settings.MAIL_DATA['revente']['REPLYTO']],
+ ))
+ mail.get_connection().send_messages(mails)
self.tirage_done = True
self.save()
diff --git a/bda/templates/bda-revente.html b/bda/templates/bda-revente.html
index a5f64678..569a3f3a 100644
--- a/bda/templates/bda-revente.html
+++ b/bda/templates/bda-revente.html
@@ -62,9 +62,9 @@
{{attrib.spectacle}} |
{{attrib.revente.soldTo.user.get_full_name}} |
|
+ value="{{attrib.revente.id}}">Transférer
|
+ value="{{attrib.revente.id}}">Réinitialiser
{% endfor %}
diff --git a/bda/templates/mail-revente-loser.txt b/bda/templates/mail-revente-loser.txt
new file mode 100644
index 00000000..6b50944d
--- /dev/null
+++ b/bda/templates/mail-revente-loser.txt
@@ -0,0 +1,9 @@
+Bonjour {{ acheteur.first_name }},
+
+Tu t'étais inscrit-e pour la revente de la place de {{ vendeur.get_full_name }}
+pour {{ spectacle.title }}.
+Malheureusement, une autre personne a été tirée au sort pour racheter la place.
+Tu pourras certainement retenter ta chance pour une autre revente !
+
+À très bientôt,
+Le Bureau des Arts
diff --git a/bda/templates/mail-revente-new.txt b/bda/templates/mail-revente-new.txt
new file mode 100644
index 00000000..ffba3083
--- /dev/null
+++ b/bda/templates/mail-revente-new.txt
@@ -0,0 +1,13 @@
+Bonjour {{ vendeur.first_name }},
+
+Tu t’es bien inscrit-e pour la revente de {{ spectacle.title }}.
+
+{% with revente.expiration_time as time %}
+Le tirage au sort entre tout-e-s les racheteuse-eur-s potentiel-le-s aura lieu
+le {{ time|date:"DATE_FORMAT" }} à {{ time|time:"TIME_FORMAT" }} (dans {{time|timeuntil }}).
+Si personne ne s’est inscrit pour racheter la place, celle-ci apparaitra parmi
+les « Places disponibles immédiatement à la revente » sur GestioCOF.
+{% endwith %}
+
+Bonne revente !
+Le Bureau des Arts
diff --git a/bda/templates/mail-revente-seller.txt b/bda/templates/mail-revente-seller.txt
new file mode 100644
index 00000000..ec99b98b
--- /dev/null
+++ b/bda/templates/mail-revente-seller.txt
@@ -0,0 +1,7 @@
+Bonjour {{ vendeur.first_name }},
+
+La personne tirée au sort pour racheter ta place pour {{ spectacle.title }} est {{ acheteur.get_full_name }}.
+Tu peux le/la contacter à l'adresse {{ acheteur.email }}, ou en répondant à ce mail.
+
+Chaleureusement,
+Le BdA
diff --git a/bda/templates/mail-revente-winner.txt b/bda/templates/mail-revente-winner.txt
new file mode 100644
index 00000000..8ca7236a
--- /dev/null
+++ b/bda/templates/mail-revente-winner.txt
@@ -0,0 +1,7 @@
+Bonjour {{ acheteur.first_name }},
+
+Tu as été tiré-e au sort pour racheter une place pour {{ spectacle.title }} le {{ spectacle.date_no_seconds }} ({{ spectacle.location }}) à {{ spectacle.price|floatformat:2 }}€.
+Tu peux contacter le/la vendeur-se à l'adresse {{ vendeur.email }}, ou en répondant à ce mail.
+
+Chaleureusement,
+Le BdA
diff --git a/bda/views.py b/bda/views.py
index 9b667cc2..43ee816c 100644
--- a/bda/views.py
+++ b/bda/views.py
@@ -8,15 +8,17 @@ import random
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
-from django.db import models
+from django.db import models, transaction
from django.db.models import Count, Q
-from django.core import serializers
+from django.core import serializers, mail
from django.forms.models import inlineformset_factory
from django.http import HttpResponseBadRequest, HttpResponseRedirect
from django.core.urlresolvers import reverse
+from django.conf import settings
import hashlib
from django.core.mail import send_mail
+from django.template import loader
from django.utils import timezone
from django.views.generic.list import ListView
@@ -292,15 +294,30 @@ def revente(request, tirage_id):
resellform = ResellForm(participant, request.POST, prefix='resell')
annulform = AnnulForm(participant, prefix='annul')
if resellform.is_valid():
+ mails = []
attributions = resellform.cleaned_data["attributions"]
- for attribution in attributions:
- revente, created = SpectacleRevente.objects.get_or_create(
- attribution=attribution,
- defaults={'seller': participant})
- if not created:
- revente.seller = participant
- revente.date = timezone.now()
- revente.save()
+ with transaction.atomic():
+ for attribution in attributions:
+ revente, created = SpectacleRevente.objects.get_or_create(
+ attribution=attribution,
+ defaults={'seller': participant})
+ if not created:
+ revente.seller = participant
+ revente.date = timezone.now()
+ mail_subject = "BdA-Revente : {:s}".format(attribution.spectacle.title)
+ mail_body = loader.render_to_string('mail-revente-new.txt', {
+ 'vendeur': participant.user,
+ 'spectacle': attribution.spectacle,
+ 'revente': revente,
+ })
+ mails.append(mail.EmailMessage(
+ mail_subject, mail_body,
+ from_email=settings.MAIL_DATA['revente']['FROM'],
+ to=[participant.user.email],
+ reply_to=[settings.MAIL_DATA['revente']['REPLYTO']],
+ ))
+ revente.save()
+ mail.get_connection().send_messages(mails)
elif 'annul' in request.POST:
annulform = AnnulForm(participant, request.POST, prefix='annul')
@@ -334,6 +351,8 @@ def revente(request, tirage_id):
if revente.attribution.spectacle.date > timezone.now():
revente.date = timezone.now() - timedelta(hours=1)
revente.soldTo = None
+ revente.notif_sent = False
+ revente.tirage_done = False
if revente.answered_mail:
revente.answered_mail.clear()
revente.save()
diff --git a/cof/settings_dev.py b/cof/settings_dev.py
index b2bda35d..6747963b 100644
--- a/cof/settings_dev.py
+++ b/cof/settings_dev.py
@@ -29,9 +29,6 @@ SECRET_KEY = 'q()(zn4m63i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
-ALLOWED_HOSTS = ['127.0.0.1']
-
-
# Application definition
INSTALLED_APPS = (
'gestioncof',
@@ -126,7 +123,7 @@ STATIC_URL = '/static/'
STATIC_ROOT = '/var/www/static/'
STATICFILES_DIRS = (
- os.path.join(BASE_DIR, 'static/'),
+ os.path.join(BASE_DIR, 'static/'),
)
# Media upload (through ImageField, SiteField)
diff --git a/gestioncof/shared.py b/gestioncof/shared.py
index 87021a00..8fe17d43 100644
--- a/gestioncof/shared.py
+++ b/gestioncof/shared.py
@@ -29,6 +29,12 @@ class COFCASBackend(CASBackend):
request.session['attributes'] = attributes
if not username:
return None
+
+ # Le CAS de l'ENS accepte les logins avec des espaces au début
+ # et à la fin, ainsi qu’avec une casse variable. On normalise pour
+ # éviter les doublons.
+ username = username.strip().lower()
+
profiles = CofProfile.objects.filter(login_clipper=username)
if len(profiles) > 0:
profile = profiles.order_by('-is_cof')[0]
diff --git a/gestioncof/views.py b/gestioncof/views.py
index 80f528f7..4f1e5009 100644
--- a/gestioncof/views.py
+++ b/gestioncof/views.py
@@ -14,6 +14,7 @@ from django.http import Http404, HttpResponse, HttpResponseForbidden
from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import login as django_login_view
from django.contrib.auth.models import User
+from django.contrib.sites.models import Site
from django.utils import timezone
import django.utils.six as six
@@ -710,12 +711,15 @@ def calendar_ics(request, token):
tirage__active=True)
shows = shows.distinct()
vcal = Calendar()
+ site = Site.objects.get_current()
for show in shows:
vevent = Vevent()
vevent.add('dtstart', show.date)
vevent.add('dtend', show.date + timedelta(seconds=7200))
vevent.add('summary', show.title)
vevent.add('location', show.location.name)
+ vevent.add('uid', 'show-{:d}-{:d}@{:s}'.format(
+ show.pk, show.tirage_id, site.domain))
vcal.add_component(vevent)
if subscription.subscribe_to_events:
for event in Event.objects.filter(old=False).all():
@@ -725,6 +729,8 @@ def calendar_ics(request, token):
vevent.add('summary', event.title)
vevent.add('location', event.location)
vevent.add('description', event.description)
+ vevent.add('uid', 'event-{:d}@{:s}'.format(
+ event.pk, site.domain))
vcal.add_component(vevent)
response = HttpResponse(content=vcal.to_ical())
response['Content-Type'] = "text/calendar"
diff --git a/requirements.txt b/requirements.txt
index 2d1109eb..23f4d618 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,8 +1,8 @@
configparser==3.5.0
-Django==1.8
+Django==1.8.*
django-autocomplete-light==2.3.3
django-autoslug==1.9.3
-git+https://github.com/xapantu/django-cas-ng.git#egg=django-cas-ng
+django-cas-ng==3.5.5
django-grappelli==2.8.1
django-recaptcha==1.0.5
mysqlclient==1.3.7
@@ -15,7 +15,7 @@ django-bootstrap-form==3.2.1
asgiref==0.14.0
daphne==0.14.3
asgi-redis==0.14.0
--e git+https://github.com/Aureplop/channels.git#egg=channel
+git+https://github.com/Aureplop/channels.git#egg=channel
statistics==1.0.3.5
future==0.15.2
django-widget-tweaks==1.4.1