forked from DGNum/gestioCOF
3bea20a52e
Cela permet de demander une confirmation avant l'envoi dans la vue correspondante quand les rappels ont déjà été envoyés.
400 lines
15 KiB
Python
400 lines
15 KiB
Python
# coding: utf-8
|
|
|
|
from __future__ import division
|
|
|
|
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.models import Count
|
|
from django.core import serializers
|
|
from django.forms.models import inlineformset_factory
|
|
import hashlib
|
|
|
|
from django.core.mail import send_mail
|
|
from django.utils import timezone
|
|
from django.views.generic.list import ListView
|
|
|
|
from datetime import timedelta
|
|
import time
|
|
|
|
from gestioncof.decorators import cof_required, buro_required
|
|
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)
|
|
spectacles1 = ChoixSpectacle.objects \
|
|
.filter(spectacle__tirage=tirage) \
|
|
.filter(double_choice="1") \
|
|
.all() \
|
|
.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') \
|
|
.annotate(total=models.Count('spectacle'))
|
|
spectacles = tirage.spectacle_set.all()
|
|
spectacles_dict = {}
|
|
total = 0
|
|
for spectacle in spectacles:
|
|
spectacle.total = 0
|
|
spectacle.ratio = 0.0
|
|
spectacles_dict[spectacle.id] = spectacle
|
|
for spectacle in spectacles1:
|
|
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
|
|
spectacles_dict[spectacle["spectacle"]].ratio = \
|
|
spectacles_dict[spectacle["spectacle"]].total / \
|
|
spectacles_dict[spectacle["spectacle"]].slots
|
|
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"]
|
|
return render(request, "etat-places.html",
|
|
{"spectacles": spectacles, "total": total, 'tirage': tirage})
|
|
|
|
|
|
def _hash_queryset(queryset):
|
|
data = serializers.serialize("json", queryset)
|
|
hasher = hashlib.sha256()
|
|
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)
|
|
places = participant.attribution_set.order_by(
|
|
"spectacle__date", "spectacle").all()
|
|
total = sum([place.spectacle.price for place in places])
|
|
filtered_places = []
|
|
places_dict = {}
|
|
spectacles = []
|
|
dates = []
|
|
warning = False
|
|
for place in places:
|
|
if place.spectacle in spectacles:
|
|
places_dict[place.spectacle].double = True
|
|
else:
|
|
place.double = False
|
|
places_dict[place.spectacle] = place
|
|
spectacles.append(place.spectacle)
|
|
filtered_places.append(place)
|
|
date = place.spectacle.date.date()
|
|
if date in dates:
|
|
warning = True
|
|
else:
|
|
dates.append(date)
|
|
return render(request, "resume_places.html",
|
|
{"participant": participant,
|
|
"places": filtered_places,
|
|
"tirage": tirage,
|
|
"total": total,
|
|
"warning": warning})
|
|
|
|
|
|
@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)
|
|
places = participant.attribution_set.order_by(
|
|
"spectacle__date", "spectacle").all()
|
|
filtered_places = []
|
|
places_dict = {}
|
|
spectacles = []
|
|
for place in places:
|
|
if place.spectacle in spectacles:
|
|
places_dict[place.spectacle].double = True
|
|
else:
|
|
place.double = False
|
|
place.spectacle.dtend = place.spectacle.date \
|
|
+ timedelta(seconds=7200)
|
|
places_dict[place.spectacle] = place
|
|
spectacles.append(place.spectacle)
|
|
filtered_places.append(place)
|
|
return render(request, "resume_places.ics",
|
|
{"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'))
|
|
return render(request, 'resume_inscription.html',
|
|
{"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)
|
|
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})
|
|
|
|
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, created = Participant.objects.get_or_create(
|
|
user=request.user, tirage=tirage)
|
|
success = False
|
|
stateerror = False
|
|
if request.method == "POST":
|
|
dbstate = _hash_queryset(participant.choixspectacle_set.all())
|
|
if "dbstate" in request.POST and dbstate != request.POST["dbstate"]:
|
|
stateerror = True
|
|
formset = BdaFormSet(instance=participant)
|
|
else:
|
|
formset = BdaFormSet(request.POST, instance=participant)
|
|
if formset.is_valid():
|
|
formset.save()
|
|
success = True
|
|
formset = BdaFormSet(instance=participant)
|
|
else:
|
|
formset = BdaFormSet(instance=participant)
|
|
dbstate = _hash_queryset(participant.choixspectacle_set.all())
|
|
total_price = 0
|
|
for choice in participant.choixspectacle_set.all():
|
|
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})
|
|
|
|
|
|
def do_tirage(request, tirage_id):
|
|
tirage_elt = get_object_or_404(Tirage, id=tirage_id)
|
|
form = TokenForm(request.POST)
|
|
if not form.is_valid():
|
|
return tirage(request)
|
|
tirage_elt.token = form.cleaned_data['token']
|
|
tirage_elt.save()
|
|
start = time.time()
|
|
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()
|
|
algo = Algorithm(shows, members, choices)
|
|
results = algo(form.cleaned_data["token"])
|
|
total_slots = 0
|
|
total_losers = 0
|
|
for (_, members, losers) in results:
|
|
total_slots += len(members)
|
|
total_losers += len(losers)
|
|
data["total_slots"] = total_slots
|
|
data["total_losers"] = total_losers
|
|
data["shows"] = shows
|
|
data["token"] = form.cleaned_data["token"]
|
|
data["members"] = members
|
|
data["results"] = results
|
|
total_sold = 0
|
|
total_deficit = 0
|
|
opera_deficit = 0
|
|
for (show, members, _) in results:
|
|
deficit = (show.slots - len(members)) * show.price
|
|
total_sold += show.slots * show.price
|
|
if deficit >= 0:
|
|
if u"Opéra" in show.location.name:
|
|
opera_deficit += deficit
|
|
total_deficit += deficit
|
|
data["total_sold"] = total_sold - total_deficit
|
|
data["total_deficit"] = total_deficit
|
|
data["opera_deficit"] = opera_deficit
|
|
data["duration"] = time.time() - start
|
|
if request.user.is_authenticated():
|
|
members2 = {}
|
|
# 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:
|
|
members_uniq[member.id] = member
|
|
members2[member] = []
|
|
member.total = 0
|
|
member = members_uniq[member.id]
|
|
members2[member].append(show)
|
|
member.total += show.price
|
|
members2 = members2.items()
|
|
data["members2"] = sorted(members2, key=lambda m: m[0].user.last_name)
|
|
# À partir d'ici, le tirage devient effectif
|
|
# FIXME: Établir les conditions de validations (formulaire ?)
|
|
# cf. issue #32
|
|
if True:
|
|
Attribution.objects.filter(
|
|
spectacle__tirage=tirage_elt
|
|
).delete()
|
|
for (show, members, _) in results:
|
|
for (member, _, _, _) in members:
|
|
attrib = Attribution(spectacle=show, participant=member)
|
|
attrib.save()
|
|
return render(request, "bda-attrib-extra.html", data)
|
|
else:
|
|
return render(request, "bda-attrib.html", data)
|
|
|
|
|
|
@buro_required
|
|
def tirage(request, tirage_id):
|
|
if request.POST:
|
|
form = TokenForm(request.POST)
|
|
if form.is_valid():
|
|
return do_tirage(request, tirage_id)
|
|
else:
|
|
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"]
|
|
places = "2 places" if count == "2" else "une place"
|
|
mail = u"""Bonjour,
|
|
|
|
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)
|
|
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})
|
|
|
|
|
|
@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)
|
|
if not participant.paid:
|
|
return render(request, "bda-notpaid.html", {})
|
|
if request.POST:
|
|
form = ResellForm(participant, request.POST)
|
|
if form.is_valid():
|
|
return do_resell(request, form)
|
|
else:
|
|
form = ResellForm(participant)
|
|
return render(request, "bda-revente.html",
|
|
{"form": form, 'tirage': tirage})
|
|
|
|
|
|
@buro_required
|
|
def spectacle(request, tirage_id, spectacle_id):
|
|
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
spectacle = get_object_or_404(Spectacle, id=spectacle_id, tirage=tirage)
|
|
attributions = spectacle.attribues.all()
|
|
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,
|
|
'nb_places': 1}
|
|
if participant.id in participants:
|
|
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})
|
|
|
|
|
|
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()
|
|
return render(request, "bda-unpaid.html", {"unpaid": unpaid})
|
|
|
|
|
|
@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")
|
|
|
|
|
|
@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)
|