gestioCOF/bda/views.py

607 lines
24 KiB
Python
Raw Normal View History

2016-07-15 00:02:56 +02:00
# -*- coding: utf-8 -*-
2012-07-11 17:39:20 +02:00
from __future__ import division
2016-07-15 00:02:56 +02:00
from __future__ import print_function
from __future__ import unicode_literals
2016-07-25 02:52:49 +02:00
import random
from django.shortcuts import render, get_object_or_404
2012-07-11 17:39:20 +02:00
from django.contrib.auth.decorators import login_required
from django.db import models, transaction
from django.db.models import Count, Q
from django.core import serializers, mail
from django.forms.models import inlineformset_factory
2016-09-21 15:39:01 +02:00
from django.http import HttpResponseBadRequest, HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.conf import settings
import hashlib
2012-07-11 17:39:20 +02:00
from django.core.mail import send_mail
from django.template import loader
from django.utils import timezone
from django.views.generic.list import ListView
import time
2016-09-03 05:21:27 +02:00
from datetime import timedelta
from gestioncof.decorators import cof_required, buro_required
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\
Tirage, SpectacleRevente
from bda.algorithm import Algorithm
2012-07-11 17:39:20 +02:00
2016-07-27 13:08:00 +02:00
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\
2016-09-19 16:08:12 +02:00
InscriptionReventeForm
2012-07-11 17:39:20 +02:00
2013-10-01 15:27:19 +02:00
@cof_required
def etat_places(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
2016-06-06 11:11:33 +02:00
spectacles1 = ChoixSpectacle.objects \
.filter(spectacle__tirage=tirage) \
.filter(double_choice="1") \
.all() \
.values('spectacle', 'spectacle__title') \
2016-06-06 11:11:33 +02:00
.annotate(total=models.Count('spectacle'))
spectacles2 = ChoixSpectacle.objects \
.filter(spectacle__tirage=tirage) \
.exclude(double_choice="1") \
.all() \
.values('spectacle', 'spectacle__title') \
2016-06-06 11:11:33 +02:00
.annotate(total=models.Count('spectacle'))
spectacles = tirage.spectacle_set.all()
spectacles_dict = {}
total = 0
for spectacle in spectacles:
spectacle.total = 0
2015-01-06 11:01:15 +01:00
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).encode('utf-8')
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 inscription(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
if timezone.now() < tirage.ouverture:
error_desc = tirage.ouverture.strftime('Ouverture le %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":
"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)
2012-07-11 17:39:20 +02:00
success = False
stateerror = False
2012-07-11 17:39:20 +02:00
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)
2012-07-11 17:39:20 +02:00
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():
2016-07-15 00:02:56 +02:00
return tirage(request, tirage_id)
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 "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
Attribution.objects.filter(spectacle__tirage=tirage_elt).delete()
tirage_elt.tokens += "%s\n\"\"\"%s\"\"\"\n" % (
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
form.cleaned_data['token'])
tirage_elt.enable_do_tirage = False
tirage_elt.save()
2016-07-15 13:48:51 +02:00
Attribution.objects.bulk_create([
Attribution(spectacle=show, participant=member)
for show, members, _ in results
for member, _, _, _ in members])
2016-09-03 19:39:28 +02:00
# On inscrit à BdA-Revente ceux qui n'ont pas eu les places voulues
for (show, _, losers) in results:
for (loser, _, _, _) in losers:
loser.choicesrevente.add(show)
loser.save()
return render(request, "bda-attrib-extra.html", data)
else:
return render(request, "bda-attrib.html", data)
@buro_required
def tirage(request, tirage_id):
tirage_elt = get_object_or_404(Tirage, id=tirage_id)
if not (tirage_elt.enable_do_tirage
and tirage_elt.fermeture < timezone.now()):
return render(request, "tirage-failed.html", {'tirage': tirage_elt})
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 = """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", {})
2016-07-25 23:03:33 +02:00
if request.method == 'POST':
2016-07-27 13:08:00 +02:00
if 'resell' in request.POST:
resellform = ResellForm(participant, request.POST, prefix='resell')
annulform = AnnulForm(participant, prefix='annul')
if resellform.is_valid():
mails = []
2016-07-27 13:08:00 +02:00
attributions = resellform.cleaned_data["attributions"]
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('bda/mails/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)
2016-07-27 13:08:00 +02:00
elif 'annul' in request.POST:
annulform = AnnulForm(participant, request.POST, prefix='annul')
resellform = ResellForm(participant, prefix='resell')
if annulform.is_valid():
attributions = annulform.cleaned_data["attributions"]
for attribution in attributions:
attribution.revente.delete()
elif 'transfer' in request.POST:
resellform = ResellForm(participant, prefix='resell')
annulform = AnnulForm(participant, prefix='annul')
revente_id = request.POST['transfer'][0]
rev = SpectacleRevente.objects.filter(soldTo__isnull=False,
id=revente_id)
if rev.exists():
revente = rev.get()
attrib = revente.attribution
attrib.participant = revente.soldTo
attrib.save()
elif 'reinit' in request.POST:
resellform = ResellForm(participant, prefix='resell')
annulform = AnnulForm(participant, prefix='annul')
2016-09-21 15:39:01 +02:00
revente_id = request.POST['reinit'][0]
rev = SpectacleRevente.objects.filter(soldTo__isnull=False,
id=revente_id)
if rev.exists():
revente = rev.get()
revente.date = timezone.now() - timedelta(hours=1)
revente.soldTo = None
2016-10-28 03:47:11 +02:00
revente.notif_sent = False
revente.tirage_done = False
2016-10-06 14:26:47 +02:00
if revente.answered_mail:
revente.answered_mail.clear()
revente.save()
2016-07-27 13:08:00 +02:00
else:
resellform = ResellForm(participant, prefix='resell')
annulform = AnnulForm(participant, prefix='annul')
2016-07-25 23:03:33 +02:00
else:
2016-07-27 13:08:00 +02:00
resellform = ResellForm(participant, prefix='resell')
annulform = AnnulForm(participant, prefix='annul')
2016-07-27 13:08:00 +02:00
overdue = participant.attribution_set.filter(
spectacle__date__gte=timezone.now(),
revente__isnull=False,
revente__seller=participant,
revente__date__lte=timezone.now()-timedelta(hours=1)).filter(
Q(revente__soldTo__isnull=True) | Q(revente__soldTo=participant))
sold = participant.attribution_set.filter(
spectacle__date__gte=timezone.now(),
revente__isnull=False,
revente__soldTo__isnull=False).exclude(
revente__soldTo=participant)
2016-07-27 13:08:00 +02:00
return render(request, "bda-revente.html",
{'tirage': tirage, 'overdue': overdue, "sold": sold,
2016-07-27 13:08:00 +02:00
"annulform": annulform, "resellform": resellform})
2016-09-04 11:14:09 +02:00
@login_required
def revente_interested(request, revente_id):
revente = get_object_or_404(SpectacleRevente, id=revente_id)
participant, created = Participant.objects.get_or_create(
user=request.user, tirage=revente.attribution.spectacle.tirage)
if timezone.now() < revente.date + timedelta(hours=1) or revente.shotgun:
return render(request, "bda-wrongtime.html", {})
2016-09-27 15:44:27 +02:00
revente.answered_mail.add(participant)
2016-09-04 11:14:09 +02:00
return render(request, "bda-interested.html",
{"spectacle": revente.attribution.spectacle,
"date": revente.expiration_time})
2016-07-25 02:52:49 +02:00
@login_required
def list_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)
2016-07-27 13:08:00 +02:00
spectacles = tirage.spectacle_set.filter(
date__gte=timezone.now())
2016-07-25 02:52:49 +02:00
shotgun = []
deja_revente = False
2016-09-27 17:35:29 +02:00
success = False
2016-07-25 02:52:49 +02:00
for spectacle in spectacles:
revente_objects = SpectacleRevente.objects.filter(
2016-09-03 18:47:38 +02:00
attribution__spectacle=spectacle,
soldTo__isnull=True)
2016-07-25 02:52:49 +02:00
revente_count = 0
for revente in revente_objects:
if revente.shotgun:
revente_count += 1
if revente_count:
spectacle.revente_count = revente_count
shotgun.append(spectacle)
2016-07-27 13:08:00 +02:00
if request.method == 'POST':
form = InscriptionReventeForm(tirage, request.POST)
if form.is_valid():
choices = form.cleaned_data['spectacles']
participant.choicesrevente = choices
participant.save()
for spectacle in choices:
qset = SpectacleRevente.objects.filter(
attribution__spectacle=spectacle)
if qset.exists():
# On l'inscrit à l'un des tirages au sort
for revente in qset.all():
2016-09-26 15:53:58 +02:00
if revente.shotgun and not revente.soldTo:
2016-09-25 12:12:31 +02:00
deja_revente = True
else:
2016-09-27 15:44:27 +02:00
revente.answered_mail.add(participant)
2016-09-27 17:35:29 +02:00
revente.save()
break
2016-09-27 17:35:29 +02:00
success = True
2016-07-27 13:08:00 +02:00
else:
form = InscriptionReventeForm(
tirage,
initial={'spectacles': participant.choicesrevente.all()})
2016-07-25 02:52:49 +02:00
return render(request, "liste-reventes.html",
{"form": form, 'shotgun': shotgun,
2016-09-27 17:35:29 +02:00
"deja_revente": deja_revente, "success": success})
2016-07-25 02:52:49 +02:00
@login_required
def buy_revente(request, spectacle_id):
spectacle = get_object_or_404(Spectacle, id=spectacle_id)
tirage = spectacle.tirage
participant, created = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
reventes = SpectacleRevente.objects.filter(
2016-09-03 18:47:38 +02:00
attribution__spectacle=spectacle,
soldTo__isnull=True)
2016-09-21 15:39:01 +02:00
if reventes.filter(seller=participant).exists():
revente = reventes.filter(seller=participant)[0]
revente.delete()
2016-10-13 16:02:52 +02:00
return HttpResponseRedirect(reverse("bda-shotgun",
2016-09-21 15:39:01 +02:00
args=[tirage.id]))
2016-09-26 15:53:58 +02:00
reventes_shotgun = []
for revente in reventes.all():
if revente.shotgun:
reventes_shotgun.append(revente)
2016-09-03 18:47:38 +02:00
2016-09-28 16:47:46 +02:00
if not reventes_shotgun:
2016-09-03 18:47:38 +02:00
return render(request, "bda-no-revente.html", {})
2016-07-25 02:52:49 +02:00
if request.POST:
2016-09-26 15:53:58 +02:00
revente = random.choice(reventes_shotgun)
2016-09-19 16:08:12 +02:00
revente.soldTo = participant
revente.save()
mail = loader.render_to_string('bda/mails/buy-shotgun.txt', {
'spectacle': spectacle,
'acheteur': request.user,
'vendeur': revente.seller.user,
})
2016-09-19 16:08:12 +02:00
send_mail("BdA-Revente : %s" % spectacle.title, mail,
request.user.email,
[revente.seller.user.email],
fail_silently=False)
return render(request, "bda-success.html",
2016-09-25 14:39:18 +02:00
{"seller": revente.attribution.participant.user,
2016-09-19 16:08:12 +02:00
"spectacle": spectacle})
2016-07-25 02:52:49 +02:00
return render(request, "revente-confirm.html",
2016-09-03 18:47:38 +02:00
{"spectacle": spectacle,
"user": request.user})
2016-07-25 02:52:49 +02:00
2016-10-06 13:46:18 +02:00
@login_required
def revente_shotgun(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
spectacles = tirage.spectacle_set.filter(
date__gte=timezone.now())
shotgun = []
for spectacle in spectacles:
revente_objects = SpectacleRevente.objects.filter(
attribution__spectacle=spectacle,
soldTo__isnull=True)
revente_count = 0
for revente in revente_objects:
if revente.shotgun:
revente_count += 1
if revente_count:
shotgun.append(spectacle)
2016-10-06 18:51:19 +02:00
return render(request, "bda-shotgun.html",
2016-10-06 13:46:18 +02:00
{"shotgun": shotgun})
@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}
2016-06-12 19:29:50 +02:00
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 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 = loader.render_to_string('bda/mails/rappel.txt', {
'member': fake_member,
'show': show})
fake_member.nb_attr = 2
exemple_mail_2places = loader.render_to_string('bda/mails/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)
def descriptions_spectacles(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
shows_qs = tirage.spectacle_set
category_name = request.GET.get('category', '')
location_id = request.GET.get('location', '')
if category_name:
shows_qs = shows_qs.filter(category__name=category_name)
if location_id:
try:
shows_qs = shows_qs.filter(location__id=int(location_id))
except ValueError:
return HttpResponseBadRequest(
"La variable GET 'location' doit contenir un entier")
return render(request, 'descriptions.html', {'shows': shows_qs.all()})