forked from DGNum/gestioCOF
580 lines
23 KiB
Python
580 lines
23 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
from __future__ import unicode_literals
|
|
|
|
import random
|
|
|
|
from django.shortcuts import render, get_object_or_404
|
|
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
|
|
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
|
|
|
|
import time
|
|
from datetime import timedelta
|
|
|
|
from gestioncof.decorators import cof_required, buro_required
|
|
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\
|
|
Tirage, render_template, SpectacleRevente
|
|
from bda.algorithm import Algorithm
|
|
|
|
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\
|
|
InscriptionReventeForm
|
|
|
|
|
|
@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).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)
|
|
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_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()
|
|
Attribution.objects.bulk_create([
|
|
Attribution(spectacle=show, participant=member)
|
|
for show, members, _ in results
|
|
for member, _, _, _ in members])
|
|
# 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})
|
|
|
|
|
|
@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.method == 'POST':
|
|
if 'resell' in request.POST:
|
|
resellform = ResellForm(participant, request.POST, prefix='resell')
|
|
annulform = AnnulForm(participant, prefix='annul')
|
|
if resellform.is_valid():
|
|
mails = []
|
|
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()
|
|
revente.notif_sent = False
|
|
revente.tirage_done = False
|
|
revente.shotgun = False
|
|
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')
|
|
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')
|
|
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
|
|
revente.notif_sent = False
|
|
revente.tirage_done = False
|
|
if revente.answered_mail:
|
|
revente.answered_mail.clear()
|
|
revente.save()
|
|
|
|
else:
|
|
resellform = ResellForm(participant, prefix='resell')
|
|
annulform = AnnulForm(participant, prefix='annul')
|
|
else:
|
|
resellform = ResellForm(participant, prefix='resell')
|
|
annulform = AnnulForm(participant, prefix='annul')
|
|
|
|
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)
|
|
|
|
return render(request, "bda-revente.html",
|
|
{'tirage': tirage, 'overdue': overdue, "sold": sold,
|
|
"annulform": annulform, "resellform": resellform})
|
|
|
|
|
|
@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",
|
|
{"revente": revente})
|
|
|
|
revente.answered_mail.add(participant)
|
|
return render(request, "bda-interested.html",
|
|
{"spectacle": revente.attribution.spectacle,
|
|
"date": revente.date_tirage})
|
|
|
|
|
|
@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)
|
|
deja_revente = False
|
|
success = False
|
|
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():
|
|
if revente.shotgun and not revente.soldTo:
|
|
deja_revente = True
|
|
else:
|
|
revente.answered_mail.add(participant)
|
|
revente.save()
|
|
break
|
|
success = True
|
|
else:
|
|
form = InscriptionReventeForm(
|
|
tirage,
|
|
initial={'spectacles': participant.choicesrevente.all()})
|
|
|
|
return render(request, "liste-reventes.html",
|
|
{"form": form,
|
|
"deja_revente": deja_revente, "success": success})
|
|
|
|
|
|
@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(
|
|
attribution__spectacle=spectacle,
|
|
soldTo__isnull=True)
|
|
|
|
# Si l'utilisateur veut racheter une place qu'il est en train de revendre,
|
|
# on supprime la revente en question.
|
|
if reventes.filter(seller=participant).exists():
|
|
revente = reventes.filter(seller=participant)[0]
|
|
revente.delete()
|
|
return HttpResponseRedirect(reverse("bda-shotgun",
|
|
args=[tirage.id]))
|
|
|
|
reventes_shotgun = list(reventes.filter(shotgun=True).all())
|
|
|
|
if not reventes_shotgun:
|
|
return render(request, "bda-no-revente.html", {})
|
|
|
|
if request.POST:
|
|
revente = random.choice(reventes_shotgun)
|
|
revente.soldTo = participant
|
|
revente.save()
|
|
mail = """Bonjour !
|
|
|
|
Je souhaiterais racheter ta place pour %s le %s (%s) à %.02f€.
|
|
Contacte-moi si tu es toujours intéressé·e !
|
|
|
|
%s (%s)""" % (spectacle.title, spectacle.date_no_seconds(),
|
|
spectacle.location, spectacle.price,
|
|
request.user.get_full_name(), request.user.email)
|
|
send_mail("BdA-Revente : %s" % spectacle.title, mail,
|
|
request.user.email,
|
|
[revente.seller.user.email],
|
|
fail_silently=False)
|
|
return render(request, "bda-success.html",
|
|
{"seller": revente.attribution.participant.user,
|
|
"spectacle": spectacle})
|
|
|
|
return render(request, "revente-confirm.html",
|
|
{"spectacle": spectacle,
|
|
"user": request.user})
|
|
|
|
|
|
@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)
|
|
|
|
return render(request, "bda-shotgun.html",
|
|
{"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}
|
|
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 = 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)
|
|
|
|
|
|
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()})
|