forked from DGNum/gestioCOF
9f307c1bd0
bda.algorithm - use iterator to find max_groups, instead of a db request bda.views.do_tirage - select_related() are now focused on some relationships (they were taking useless relationships) - bda-revente filling takes 1 request (each save and add was issuing 1 request)
802 lines
30 KiB
Python
802 lines
30 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from collections import defaultdict
|
|
from functools import partial
|
|
import random
|
|
import hashlib
|
|
import time
|
|
import json
|
|
from datetime import timedelta
|
|
from custommail.shortcuts import (
|
|
send_mass_custom_mail, send_custom_mail, render_custom_mail
|
|
)
|
|
from django.shortcuts import render, get_object_or_404
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.contrib import messages
|
|
from django.db import transaction
|
|
from django.core import serializers
|
|
from django.db.models import Count, Q
|
|
from django.forms.models import inlineformset_factory
|
|
from django.http import (
|
|
HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
|
|
)
|
|
from django.core.urlresolvers import reverse
|
|
from django.conf import settings
|
|
from django.utils import timezone, formats
|
|
from django.views.generic.list import ListView
|
|
from gestioncof.decorators import cof_required, buro_required
|
|
from bda.models import (
|
|
Spectacle, Participant, ChoixSpectacle, Attribution, Tirage,
|
|
SpectacleRevente, Salle, Quote, CategorieSpectacle
|
|
)
|
|
from bda.algorithm import Algorithm
|
|
from bda.forms import (
|
|
TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, SoldForm,
|
|
)
|
|
|
|
|
|
@cof_required
|
|
def etat_places(request, tirage_id):
|
|
"""
|
|
Résumé des spectacles d'un tirage avec pour chaque spectacle :
|
|
- Le nombre de places en jeu
|
|
- Le nombre de demandes
|
|
- Le ratio demandes/places
|
|
Et le total de toutes les demandes
|
|
"""
|
|
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
|
|
spectacles = tirage.spectacle_set.select_related('location')
|
|
spectacles_dict = {} # index of spectacle by id
|
|
|
|
for spectacle in spectacles:
|
|
spectacle.total = 0 # init total requests
|
|
spectacles_dict[spectacle.id] = spectacle
|
|
|
|
choices = (
|
|
ChoixSpectacle.objects
|
|
.filter(spectacle__in=spectacles)
|
|
.values('spectacle')
|
|
.annotate(total=Count('spectacle'))
|
|
)
|
|
|
|
# choices *by spectacles* whose only 1 place is requested
|
|
choices1 = choices.filter(double_choice="1")
|
|
# choices *by spectacles* whose 2 places is requested
|
|
choices2 = choices.exclude(double_choice="1")
|
|
|
|
for spectacle in choices1:
|
|
pk = spectacle['spectacle']
|
|
spectacles_dict[pk].total += spectacle['total']
|
|
for spectacle in choices2:
|
|
pk = spectacle['spectacle']
|
|
spectacles_dict[pk].total += 2*spectacle['total']
|
|
|
|
# here, each spectacle.total contains the number of requests
|
|
|
|
slots = 0 # proposed slots
|
|
total = 0 # requests
|
|
for spectacle in spectacles:
|
|
slots += spectacle.slots
|
|
total += spectacle.total
|
|
spectacle.ratio = spectacle.total / spectacle.slots
|
|
|
|
context = {
|
|
"proposed": slots,
|
|
"spectacles": spectacles,
|
|
"total": total,
|
|
'tirage': tirage
|
|
}
|
|
return render(request, "bda/etat-places.html", context)
|
|
|
|
|
|
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, _ = (
|
|
Participant.objects
|
|
.get_or_create(user=request.user, tirage=tirage)
|
|
)
|
|
places = (
|
|
participant.attribution_set
|
|
.order_by("spectacle__date", "spectacle")
|
|
.select_related("spectacle", "spectacle__location")
|
|
)
|
|
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)
|
|
# On prévient l'utilisateur s'il a deux places à la même date
|
|
if warning:
|
|
messages.warning(request, "Attention, vous avez reçu des places pour "
|
|
"des spectacles différents à la même date.")
|
|
return render(request, "bda/resume_places.html",
|
|
{"participant": participant,
|
|
"places": filtered_places,
|
|
"tirage": tirage,
|
|
"total": total})
|
|
|
|
|
|
@cof_required
|
|
def inscription(request, tirage_id):
|
|
"""
|
|
Vue d'inscription à un tirage BdA.
|
|
- On vérifie qu'on se situe bien entre la date d'ouverture et la date de
|
|
fermeture des inscriptions.
|
|
- On vérifie que l'inscription n'a pas été modifiée entre le moment où le
|
|
client demande le formulaire et le moment où il soumet son inscription
|
|
(autre session par exemple).
|
|
"""
|
|
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
if timezone.now() < tirage.ouverture:
|
|
# Le tirage n'est pas encore ouvert.
|
|
opening = formats.localize(
|
|
timezone.template_localtime(tirage.ouverture))
|
|
messages.error(request, "Le tirage n'est pas encore ouvert : "
|
|
"ouverture le {:s}".format(opening))
|
|
return render(request, 'bda/resume-inscription-tirage.html', {})
|
|
if timezone.now() > tirage.fermeture:
|
|
# Le tirage est fermé.
|
|
participant, _ = (
|
|
Participant.objects
|
|
.get_or_create(user=request.user, tirage=tirage)
|
|
)
|
|
choices = participant.choixspectacle_set.order_by("priority")
|
|
messages.error(request,
|
|
" C'est fini : tirage au sort dans la journée !")
|
|
return render(request, "bda/resume-inscription-tirage.html",
|
|
{"choices": choices})
|
|
|
|
def force_for(f, to_choices=None, **kwargs):
|
|
"""Overrides choices for ModelChoiceField.
|
|
|
|
Args:
|
|
f (models.Field): To render as forms.Field
|
|
to_choices (dict): If a key `f.name` exists, f._choices is set to
|
|
its value.
|
|
|
|
"""
|
|
formfield = f.formfield(**kwargs)
|
|
if to_choices:
|
|
if f.name in to_choices:
|
|
choices = [('', '---------')] + to_choices[f.name]
|
|
formfield._choices = choices
|
|
return formfield
|
|
|
|
# Restrict spectacles choices to spectacles for this tirage.
|
|
spectacles = (
|
|
tirage.spectacle_set
|
|
.select_related('location')
|
|
)
|
|
spectacles_field_choices = [(sp.pk, str(sp)) for sp in spectacles]
|
|
|
|
# Allow for spectacle choices to be set once for all.
|
|
# Form display use 1 request instead of (#forms of formset * #spectacles).
|
|
# FIXME: Validation still generates too much requests...
|
|
formfield_callback = partial(
|
|
force_for,
|
|
to_choices={
|
|
'spectacle': spectacles_field_choices,
|
|
},
|
|
)
|
|
BdaFormSet = inlineformset_factory(
|
|
Participant,
|
|
ChoixSpectacle,
|
|
fields=("spectacle", "double_choice", "priority"),
|
|
formfield_callback=formfield_callback,
|
|
)
|
|
participant, _ = (
|
|
Participant.objects
|
|
.get_or_create(user=request.user, tirage=tirage)
|
|
)
|
|
success = False
|
|
stateerror = False
|
|
if request.method == "POST":
|
|
# use *this* queryset
|
|
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)
|
|
# use *this* queryset
|
|
dbstate = _hash_queryset(participant.choixspectacle_set.all())
|
|
total_price = 0
|
|
choices = (
|
|
participant.choixspectacle_set
|
|
.select_related('spectacle')
|
|
)
|
|
for choice in choices:
|
|
total_price += choice.spectacle.price
|
|
if choice.double:
|
|
total_price += choice.spectacle.price
|
|
# Messages
|
|
if success:
|
|
messages.success(request, "Votre inscription a été mise à jour avec "
|
|
"succès !")
|
|
if stateerror:
|
|
messages.error(request, "Impossible d'enregistrer vos modifications "
|
|
": vous avez apporté d'autres modifications "
|
|
"entre temps.")
|
|
return render(request, "bda/inscription-tirage.html",
|
|
{"formset": formset,
|
|
"total_price": total_price,
|
|
"dbstate": dbstate,
|
|
'tirage': tirage})
|
|
|
|
|
|
def do_tirage(tirage_elt, token):
|
|
"""
|
|
Fonction auxiliaire à la vue ``tirage`` qui lance effectivement le tirage
|
|
après qu'on a vérifié que c'est légitime et que le token donné en argument
|
|
est correct.
|
|
Rend les résultats
|
|
"""
|
|
# Initialisation du dictionnaire data qui va contenir les résultats
|
|
start = time.time()
|
|
data = {
|
|
'shows': tirage_elt.spectacle_set.select_related('location'),
|
|
'token': token,
|
|
'members': tirage_elt.participant_set.select_related('user'),
|
|
'total_slots': 0,
|
|
'total_losers': 0,
|
|
'total_sold': 0,
|
|
'total_deficit': 0,
|
|
'opera_deficit': 0,
|
|
}
|
|
|
|
# On lance le tirage
|
|
choices = (
|
|
ChoixSpectacle.objects
|
|
.filter(spectacle__tirage=tirage_elt)
|
|
.order_by('participant', 'priority')
|
|
.select_related('participant', 'participant__user', 'spectacle')
|
|
)
|
|
results = Algorithm(data['shows'], data['members'], choices)(token)
|
|
|
|
# On compte les places attribuées et les déçus
|
|
for (_, members, losers) in results:
|
|
data['total_slots'] += len(members)
|
|
data['total_losers'] += len(losers)
|
|
|
|
# On calcule le déficit et les bénéfices pour le BdA
|
|
# FIXME: le traitement de l'opéra est sale
|
|
for (show, members, _) in results:
|
|
deficit = (show.slots - len(members)) * show.price
|
|
data['total_sold'] += show.slots * show.price
|
|
if deficit >= 0:
|
|
if "Opéra" in show.location.name:
|
|
data['opera_deficit'] += deficit
|
|
data['total_deficit'] += deficit
|
|
data["total_sold"] -= data['total_deficit']
|
|
|
|
# Participant objects are not shared accross spectacle results,
|
|
# so assign a single object for each Participant id
|
|
members_uniq = {}
|
|
members2 = {}
|
|
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
|
|
# ---
|
|
|
|
# On suppression les vieilles attributions, on sauvegarde le token et on
|
|
# désactive le tirage
|
|
Attribution.objects.filter(spectacle__tirage=tirage_elt).delete()
|
|
tirage_elt.tokens += '{:s}\n"""{:s}"""\n'.format(
|
|
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
|
|
token)
|
|
tirage_elt.enable_do_tirage = False
|
|
tirage_elt.save()
|
|
|
|
# On enregistre les nouvelles attributions
|
|
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
|
|
ChoixRevente = Participant.choicesrevente.through
|
|
|
|
lost_by = defaultdict(set)
|
|
for show, _, losers in results:
|
|
for loser, _, _, _ in losers:
|
|
lost_by[loser].add(show)
|
|
|
|
ChoixRevente.objects.bulk_create(
|
|
ChoixRevente(participant=member, spectacle=show)
|
|
for member, shows in lost_by.items()
|
|
for show in shows
|
|
)
|
|
|
|
data["duration"] = time.time() - start
|
|
data["results"] = results
|
|
return 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():
|
|
results = do_tirage(tirage_elt, form.cleaned_data['token'])
|
|
return render(request, "bda-attrib-extra.html", results)
|
|
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", {})
|
|
|
|
resellform = ResellForm(participant, prefix='resell')
|
|
annulform = AnnulForm(participant, prefix='annul')
|
|
soldform = SoldForm(participant, prefix='sold')
|
|
|
|
if request.method == 'POST':
|
|
# On met en vente une place
|
|
if 'resell' in request.POST:
|
|
resellform = ResellForm(participant, request.POST, prefix='resell')
|
|
if resellform.is_valid():
|
|
datatuple = []
|
|
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.soldTo = None
|
|
revente.notif_sent = False
|
|
revente.tirage_done = False
|
|
revente.shotgun = False
|
|
context = {
|
|
'vendeur': participant.user,
|
|
'show': attribution.spectacle,
|
|
'revente': revente
|
|
}
|
|
datatuple.append((
|
|
'bda-revente-new', context,
|
|
settings.MAIL_DATA['revente']['FROM'],
|
|
[participant.user.email]
|
|
))
|
|
revente.save()
|
|
send_mass_custom_mail(datatuple)
|
|
# On annule une revente
|
|
elif 'annul' in request.POST:
|
|
annulform = AnnulForm(participant, request.POST, prefix='annul')
|
|
if annulform.is_valid():
|
|
attributions = annulform.cleaned_data["attributions"]
|
|
for attribution in attributions:
|
|
attribution.revente.delete()
|
|
# On confirme une vente en transférant la place à la personne qui a
|
|
# gagné le tirage
|
|
elif 'transfer' in request.POST:
|
|
soldform = SoldForm(participant, request.POST, prefix='sold')
|
|
if soldform.is_valid():
|
|
attributions = soldform.cleaned_data['attributions']
|
|
for attribution in attributions:
|
|
attribution.participant = attribution.revente.soldTo
|
|
attribution.save()
|
|
|
|
# On annule la revente après le tirage au sort (par exemple si
|
|
# la personne qui a gagné le tirage ne se manifeste pas). La place est
|
|
# alors remise en vente
|
|
elif 'reinit' in request.POST:
|
|
soldform = SoldForm(participant, request.POST, prefix='sold')
|
|
if soldform.is_valid():
|
|
attributions = soldform.cleaned_data['attributions']
|
|
for attribution in attributions:
|
|
if attribution.spectacle.date > timezone.now():
|
|
revente = attribution.revente
|
|
revente.date = timezone.now() - timedelta(minutes=65)
|
|
revente.soldTo = None
|
|
revente.notif_sent = False
|
|
revente.tirage_done = False
|
|
revente.shotgun = False
|
|
if revente.answered_mail:
|
|
revente.answered_mail.clear()
|
|
revente.save()
|
|
|
|
overdue = participant.attribution_set.filter(
|
|
spectacle__date__gte=timezone.now(),
|
|
revente__isnull=False,
|
|
revente__seller=participant,
|
|
revente__notif_sent=True)\
|
|
.filter(
|
|
Q(revente__soldTo__isnull=True) | Q(revente__soldTo=participant))
|
|
|
|
return render(request, "bda/reventes.html",
|
|
{'tirage': tirage, 'overdue': overdue, "soldform": soldform,
|
|
"annulform": annulform, "resellform": resellform})
|
|
|
|
|
|
@login_required
|
|
def revente_interested(request, revente_id):
|
|
revente = get_object_or_404(SpectacleRevente, id=revente_id)
|
|
participant, _ = 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, _ = Participant.objects.get_or_create(
|
|
user=request.user, tirage=tirage)
|
|
deja_revente = False
|
|
success = False
|
|
inscrit_revente = []
|
|
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.filter(shotgun=True, soldTo__isnull=True).exists():
|
|
# Une place est disponible au shotgun, on suggère à
|
|
# l'utilisateur d'aller la récupérer
|
|
deja_revente = True
|
|
else:
|
|
# La place n'est pas disponible au shotgun, si des reventes
|
|
# pour ce spectacle existent déjà, on inscrit la personne à
|
|
# la revente ayant le moins d'inscrits
|
|
min_resell = (
|
|
qset.filter(shotgun=False)
|
|
.annotate(nb_subscribers=Count('answered_mail'))
|
|
.order_by('nb_subscribers')
|
|
.first()
|
|
)
|
|
if min_resell is not None:
|
|
min_resell.answered_mail.add(participant)
|
|
min_resell.save()
|
|
inscrit_revente.append(spectacle)
|
|
success = True
|
|
else:
|
|
form = InscriptionReventeForm(
|
|
tirage,
|
|
initial={'spectacles': participant.choicesrevente.all()}
|
|
)
|
|
# Messages
|
|
if success:
|
|
messages.success(request, "Ton inscription a bien été prise en compte")
|
|
if deja_revente:
|
|
messages.info(request, "Des reventes existent déjà pour certains de "
|
|
"ces spectacles, vérifie les places "
|
|
"disponibles sans tirage !")
|
|
if inscrit_revente:
|
|
shows = map("<li>{!s}</li>".format, inscrit_revente)
|
|
msg = (
|
|
"Tu as été inscrit à des reventes en cours pour les spectacles "
|
|
"<ul>{:s}</ul>".format('\n'.join(shows))
|
|
)
|
|
messages.info(request, msg, extra_tags="safe")
|
|
|
|
return render(request, "bda/liste-reventes.html", {"form": form})
|
|
|
|
|
|
@login_required
|
|
def buy_revente(request, spectacle_id):
|
|
spectacle = get_object_or_404(Spectacle, id=spectacle_id)
|
|
tirage = spectacle.tirage
|
|
participant, _ = 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()
|
|
context = {
|
|
'show': spectacle,
|
|
'acheteur': request.user,
|
|
'vendeur': revente.seller.user
|
|
}
|
|
send_custom_mail(
|
|
'bda-buy-shotgun',
|
|
'bda@ens.fr',
|
|
[revente.seller.user.email],
|
|
context=context,
|
|
)
|
|
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:
|
|
reventes = SpectacleRevente.objects.filter(
|
|
attribution__spectacle=spectacle,
|
|
shotgun=True,
|
|
soldTo__isnull=True)
|
|
if reventes.exists():
|
|
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
|
|
exemple_mail_1place = render_custom_mail('bda-rappel', {
|
|
'member': request.user,
|
|
'show': show,
|
|
'nb_attr': 1
|
|
})
|
|
exemple_mail_2places = render_custom_mail('bda-rappel', {
|
|
'member': request.user,
|
|
'show': show,
|
|
'nb_attr': 2
|
|
})
|
|
# 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, "bda/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()})
|
|
|
|
|
|
def catalogue(request, request_type):
|
|
"""
|
|
Vue destinée à communiquer avec un client AJAX, fournissant soit :
|
|
- la liste des tirages
|
|
- les catégories et salles d'un tirage
|
|
- les descriptions d'un tirage (filtrées selon la catégorie et la salle)
|
|
"""
|
|
if request_type == "list":
|
|
# Dans ce cas on retourne la liste des tirages et de leur id en JSON
|
|
data_return = list(
|
|
Tirage.objects.filter(appear_catalogue=True).values('id', 'title')
|
|
)
|
|
return JsonResponse(data_return, safe=False)
|
|
if request_type == "details":
|
|
# Dans ce cas on retourne une liste des catégories et des salles
|
|
tirage_id = request.GET.get('id', None)
|
|
if tirage_id is None:
|
|
return HttpResponseBadRequest(
|
|
"Missing GET parameter: id <int>"
|
|
)
|
|
try:
|
|
tirage = get_object_or_404(Tirage, id=int(tirage_id))
|
|
except ValueError:
|
|
return HttpResponseBadRequest(
|
|
"Bad format: int expected for `id`"
|
|
)
|
|
shows = tirage.spectacle_set.values_list("id", flat=True)
|
|
categories = list(
|
|
CategorieSpectacle.objects
|
|
.filter(spectacle__in=shows)
|
|
.distinct()
|
|
.values('id', 'name')
|
|
)
|
|
locations = list(
|
|
Salle.objects
|
|
.filter(spectacle__in=shows)
|
|
.distinct()
|
|
.values('id', 'name')
|
|
)
|
|
data_return = {'categories': categories, 'locations': locations}
|
|
return JsonResponse(data_return, safe=False)
|
|
if request_type == "descriptions":
|
|
# Ici on retourne les descriptions correspondant à la catégorie et
|
|
# à la salle spécifiées
|
|
|
|
tirage_id = request.GET.get('id', '')
|
|
categories = request.GET.get('category', '[]')
|
|
locations = request.GET.get('location', '[]')
|
|
try:
|
|
tirage_id = int(tirage_id)
|
|
categories_id = json.loads(categories)
|
|
locations_id = json.loads(locations)
|
|
# Integers expected
|
|
if not all(isinstance(id, int) for id in categories_id):
|
|
raise ValueError
|
|
if not all(isinstance(id, int) for id in locations_id):
|
|
raise ValueError
|
|
except ValueError: # Contient JSONDecodeError
|
|
return HttpResponseBadRequest(
|
|
"Parse error, please ensure the GET parameters have the "
|
|
"following types:\n"
|
|
"id: int, category: [int], location: [int]\n"
|
|
"Data received:\n"
|
|
"id = {}, category = {}, locations = {}"
|
|
.format(request.GET.get('id', ''),
|
|
request.GET.get('category', '[]'),
|
|
request.GET.get('location', '[]'))
|
|
)
|
|
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
|
|
shows_qs = tirage.spectacle_set
|
|
if categories_id:
|
|
shows_qs = shows_qs.filter(category__id__in=categories_id)
|
|
if locations_id:
|
|
shows_qs = shows_qs.filter(location__id__in=locations_id)
|
|
|
|
# On convertit les descriptions à envoyer en une liste facilement
|
|
# JSONifiable (il devrait y avoir un moyen plus efficace en
|
|
# redéfinissant le serializer de JSON)
|
|
data_return = [{
|
|
'title': spectacle.title,
|
|
'category': str(spectacle.category),
|
|
'date': str(formats.date_format(
|
|
timezone.localtime(spectacle.date),
|
|
"SHORT_DATETIME_FORMAT")),
|
|
'location': str(spectacle.location),
|
|
'vips': spectacle.vips,
|
|
'description': spectacle.description,
|
|
'slots_description': spectacle.slots_description,
|
|
'quotes': list(Quote.objects.filter(spectacle=spectacle).values(
|
|
'author', 'text')),
|
|
'image': spectacle.getImgUrl(),
|
|
'ext_link': spectacle.ext_link,
|
|
'price': spectacle.price,
|
|
'slots': spectacle.slots
|
|
}
|
|
for spectacle in shows_qs.all()
|
|
]
|
|
return JsonResponse(data_return, safe=False)
|
|
# Si la requête n'est pas de la forme attendue, on quitte avec une erreur
|
|
return HttpResponseBadRequest()
|