Merge branch 'master' of https://git.eleves.ens.fr/cof-geek/gestioCOF into Aufinal/fix_reinit_past

This commit is contained in:
Ludovic Stephan 2016-11-08 10:28:59 -02:00
commit a9a4bf6b4a
12 changed files with 175 additions and 44 deletions

View file

@ -5,12 +5,13 @@ from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.mail import send_mail from django.core.mail import send_mail
from django.contrib import admin from django.contrib import admin
from django.db.models import Sum, Count 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,\ from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
from django import forms
from datetime import timedelta from datetime import timedelta
@ -211,9 +212,15 @@ class SalleAdmin(admin.ModelAdmin):
class SpectacleReventeAdmin(admin.ModelAdmin): class SpectacleReventeAdmin(admin.ModelAdmin):
"""
Administration des reventes de spectacles
"""
model = SpectacleRevente model = SpectacleRevente
def spectacle(self, obj): def spectacle(self, obj):
"""
Raccourci vers le spectacle associé à la revente.
"""
return obj.attribution.spectacle return obj.attribution.spectacle
list_display = ("spectacle", "seller", "date", "soldTo") list_display = ("spectacle", "seller", "date", "soldTo")
@ -224,6 +231,48 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
'seller__user__first_name', 'seller__user__first_name',
'seller__user__last_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(CategorieSpectacle)
admin.site.register(Spectacle, SpectacleAdmin) admin.site.register(Spectacle, SpectacleAdmin)

View file

@ -320,33 +320,51 @@ class SpectacleRevente(models.Model):
self.save() self.save()
def tirage(self): def tirage(self):
inscrits = self.answered_mail inscrits = list(self.answered_mail.all())
spectacle = self.attribution.spectacle spectacle = self.attribution.spectacle
seller = self.seller 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 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. # Envoie un mail aux perdants
Tu peux contacter le/la vendeur-se à l'adresse %s. for inscrit in inscrits:
if inscrit == winner:
continue
Chaleureusement, mail_body = loader.render_to_string('mail-revente-loser.txt', {
Le BdA""" % (spectacle.title, spectacle.date_no_seconds(), 'acheteur': inscrit.user,
spectacle.location, spectacle.price, seller.user.email) 'vendeur': seller.user,
'spectacle': spectacle,
mail.send_mail("BdA-Revente : %s" % spectacle.title, })
mail_buyer, "bda@ens.fr", [winner.user.email], mails.append(mail.EmailMessage(
fail_silently=False) mail_subject, mail_body,
mail_seller = """Bonjour, from_email=settings.MAIL_DATA['revente']['FROM'],
La personne tirée au sort pour racheter ta place pour %s est %s. to=[inscrit.user.email],
Tu peux le/la contacter à l'adresse %s. reply_to=[settings.MAIL_DATA['revente']['REPLYTO']],
))
Chaleureusement, mail.get_connection().send_messages(mails)
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)
self.tirage_done = True self.tirage_done = True
self.save() self.save()

View file

@ -62,9 +62,9 @@
<td>{{attrib.spectacle}}</td> <td>{{attrib.spectacle}}</td>
<td>{{attrib.revente.soldTo.user.get_full_name}}</td> <td>{{attrib.revente.soldTo.user.get_full_name}}</td>
<td><button type="submit" class="btn btn-primary" name="transfer" <td><button type="submit" class="btn btn-primary" name="transfer"
value={{attrib.revente.id}}>Transférer</button></td> value="{{attrib.revente.id}}">Transférer</button></td>
<td><button type="submit" class="btn btn-primary" name="reinit" <td><button type="submit" class="btn btn-primary" name="reinit"
value={{attrib.revente.id}}>Réinitialiser</button></td> value="{{attrib.revente.id}}">Réinitialiser</button></td>
</form> </form>
</tr> </tr>
{% endfor %} {% endfor %}

View file

@ -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

View file

@ -0,0 +1,13 @@
Bonjour {{ vendeur.first_name }},
Tu tes 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 sest 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

View file

@ -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

View file

@ -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

View file

@ -8,15 +8,17 @@ import random
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.db import models from django.db import models, transaction
from django.db.models import Count, Q 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.forms.models import inlineformset_factory
from django.http import HttpResponseBadRequest, HttpResponseRedirect from django.http import HttpResponseBadRequest, HttpResponseRedirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.conf import settings
import hashlib import hashlib
from django.core.mail import send_mail from django.core.mail import send_mail
from django.template import loader
from django.utils import timezone from django.utils import timezone
from django.views.generic.list import ListView from django.views.generic.list import ListView
@ -292,15 +294,30 @@ def revente(request, tirage_id):
resellform = ResellForm(participant, request.POST, prefix='resell') resellform = ResellForm(participant, request.POST, prefix='resell')
annulform = AnnulForm(participant, prefix='annul') annulform = AnnulForm(participant, prefix='annul')
if resellform.is_valid(): if resellform.is_valid():
mails = []
attributions = resellform.cleaned_data["attributions"] attributions = resellform.cleaned_data["attributions"]
for attribution in attributions: with transaction.atomic():
revente, created = SpectacleRevente.objects.get_or_create( for attribution in attributions:
attribution=attribution, revente, created = SpectacleRevente.objects.get_or_create(
defaults={'seller': participant}) attribution=attribution,
if not created: defaults={'seller': participant})
revente.seller = participant if not created:
revente.date = timezone.now() revente.seller = participant
revente.save() 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: elif 'annul' in request.POST:
annulform = AnnulForm(participant, request.POST, prefix='annul') annulform = AnnulForm(participant, request.POST, prefix='annul')
@ -334,6 +351,8 @@ def revente(request, tirage_id):
if revente.attribution.spectacle.date > timezone.now(): if revente.attribution.spectacle.date > timezone.now():
revente.date = timezone.now() - timedelta(hours=1) revente.date = timezone.now() - timedelta(hours=1)
revente.soldTo = None revente.soldTo = None
revente.notif_sent = False
revente.tirage_done = False
if revente.answered_mail: if revente.answered_mail:
revente.answered_mail.clear() revente.answered_mail.clear()
revente.save() revente.save()

View file

@ -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! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
ALLOWED_HOSTS = ['127.0.0.1']
# Application definition # Application definition
INSTALLED_APPS = ( INSTALLED_APPS = (
'gestioncof', 'gestioncof',
@ -126,7 +123,7 @@ STATIC_URL = '/static/'
STATIC_ROOT = '/var/www/static/' STATIC_ROOT = '/var/www/static/'
STATICFILES_DIRS = ( STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static/'), os.path.join(BASE_DIR, 'static/'),
) )
# Media upload (through ImageField, SiteField) # Media upload (through ImageField, SiteField)

View file

@ -29,6 +29,12 @@ class COFCASBackend(CASBackend):
request.session['attributes'] = attributes request.session['attributes'] = attributes
if not username: if not username:
return None return None
# Le CAS de l'ENS accepte les logins avec des espaces au début
# et à la fin, ainsi quavec une casse variable. On normalise pour
# éviter les doublons.
username = username.strip().lower()
profiles = CofProfile.objects.filter(login_clipper=username) profiles = CofProfile.objects.filter(login_clipper=username)
if len(profiles) > 0: if len(profiles) > 0:
profile = profiles.order_by('-is_cof')[0] profile = profiles.order_by('-is_cof')[0]

View file

@ -14,6 +14,7 @@ from django.http import Http404, HttpResponse, HttpResponseForbidden
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import login as django_login_view from django.contrib.auth.views import login as django_login_view
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.utils import timezone from django.utils import timezone
import django.utils.six as six import django.utils.six as six
@ -710,12 +711,15 @@ def calendar_ics(request, token):
tirage__active=True) tirage__active=True)
shows = shows.distinct() shows = shows.distinct()
vcal = Calendar() vcal = Calendar()
site = Site.objects.get_current()
for show in shows: for show in shows:
vevent = Vevent() vevent = Vevent()
vevent.add('dtstart', show.date) vevent.add('dtstart', show.date)
vevent.add('dtend', show.date + timedelta(seconds=7200)) vevent.add('dtend', show.date + timedelta(seconds=7200))
vevent.add('summary', show.title) vevent.add('summary', show.title)
vevent.add('location', show.location.name) 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) vcal.add_component(vevent)
if subscription.subscribe_to_events: if subscription.subscribe_to_events:
for event in Event.objects.filter(old=False).all(): 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('summary', event.title)
vevent.add('location', event.location) vevent.add('location', event.location)
vevent.add('description', event.description) vevent.add('description', event.description)
vevent.add('uid', 'event-{:d}@{:s}'.format(
event.pk, site.domain))
vcal.add_component(vevent) vcal.add_component(vevent)
response = HttpResponse(content=vcal.to_ical()) response = HttpResponse(content=vcal.to_ical())
response['Content-Type'] = "text/calendar" response['Content-Type'] = "text/calendar"

View file

@ -1,8 +1,8 @@
configparser==3.5.0 configparser==3.5.0
Django==1.8 Django==1.8.*
django-autocomplete-light==2.3.3 django-autocomplete-light==2.3.3
django-autoslug==1.9.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-grappelli==2.8.1
django-recaptcha==1.0.5 django-recaptcha==1.0.5
mysqlclient==1.3.7 mysqlclient==1.3.7
@ -15,7 +15,7 @@ django-bootstrap-form==3.2.1
asgiref==0.14.0 asgiref==0.14.0
daphne==0.14.3 daphne==0.14.3
asgi-redis==0.14.0 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 statistics==1.0.3.5
future==0.15.2 future==0.15.2
django-widget-tweaks==1.4.1 django-widget-tweaks==1.4.1