forked from DGNum/gestioCOF
1783196a9c
Except for Revente creation, every form is now handled with revente objects, to use the display option in the previous commit.
866 lines
32 KiB
Python
866 lines
32 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from collections import defaultdict
|
|
import random
|
|
import hashlib
|
|
import time
|
|
import json
|
|
from custommail.shortcuts import send_mass_custom_mail, send_custom_mail
|
|
from custommail.models import CustomMail
|
|
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, Prefetch
|
|
from django.template.defaultfilters import pluralize
|
|
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, CategorieSpectacle
|
|
)
|
|
from bda.algorithm import Algorithm
|
|
from bda.forms import (
|
|
TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, SoldForm,
|
|
InscriptionInlineFormSet, ReventeTirageForm, ReventeTirageAnnulForm
|
|
)
|
|
|
|
|
|
@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', {})
|
|
|
|
participant, _ = (
|
|
Participant.objects.select_related('tirage')
|
|
.get_or_create(user=request.user, tirage=tirage)
|
|
)
|
|
|
|
if timezone.now() > tirage.fermeture:
|
|
# Le tirage est fermé.
|
|
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})
|
|
|
|
BdaFormSet = inlineformset_factory(
|
|
Participant,
|
|
ChoixSpectacle,
|
|
fields=("spectacle", "double_choice", "priority"),
|
|
formset=InscriptionInlineFormSet,
|
|
)
|
|
|
|
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
|
|
|
|
# Suppression des reventes demandées/enregistrées
|
|
# (si le tirage est relancé)
|
|
(
|
|
ChoixRevente.objects
|
|
.filter(spectacle__tirage=tirage_elt)
|
|
.delete()
|
|
)
|
|
(
|
|
SpectacleRevente.objects
|
|
.filter(attribution__spectacle__tirage=tirage_elt)
|
|
.delete()
|
|
)
|
|
|
|
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_manage(request, tirage_id):
|
|
"""
|
|
Gestion de ses propres reventes :
|
|
- Création d'une revente
|
|
- Annulation d'une revente
|
|
- Confirmation d'une revente = transfert de la place à la personne qui
|
|
rachète
|
|
- Annulation d'une revente après que le tirage a eu lieu
|
|
"""
|
|
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/revente/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.reset()
|
|
|
|
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():
|
|
reventes = annulform.cleaned_data["reventes"]
|
|
for revente in reventes:
|
|
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():
|
|
reventes = soldform.cleaned_data['reventes']
|
|
for reventes in reventes:
|
|
revente.attribution.participant = revente.soldTo
|
|
revente.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():
|
|
reventes = soldform.cleaned_data['reventes']
|
|
for revente in reventes:
|
|
if revente.attribution.spectacle.date > timezone.now():
|
|
# On antidate pour envoyer le mail plus vite
|
|
new_date = (timezone.now()
|
|
- SpectacleRevente.remorse_time)
|
|
revente.reset(new_date=new_date)
|
|
|
|
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/revente/manage.html",
|
|
{'tirage': tirage, 'overdue': overdue, "soldform": soldform,
|
|
"annulform": annulform, "resellform": resellform})
|
|
|
|
|
|
@login_required
|
|
def revente_tirages(request, tirage_id):
|
|
"""
|
|
Affiche à un participant la liste de toutes les reventes en cours (pour un
|
|
tirage donné) et lui permet de s'inscrire et se désinscrire à ces reventes.
|
|
"""
|
|
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
participant, _ = Participant.objects.get_or_create(
|
|
user=request.user, tirage=tirage)
|
|
subform = ReventeTirageForm(participant, prefix="subscribe")
|
|
annulform = ReventeTirageAnnulForm(participant, prefix="annul")
|
|
|
|
if request.method == 'POST':
|
|
if "subscribe" in request.POST:
|
|
subform = ReventeTirageForm(participant, request.POST,
|
|
prefix="subscribe")
|
|
if subform.is_valid():
|
|
reventes = subform.cleaned_data['reventes']
|
|
count = reventes.count()
|
|
for revente in reventes:
|
|
revente.confirmed_entry.add(participant)
|
|
if count > 0:
|
|
messages.success(
|
|
request,
|
|
"Tu as bien été inscrit à {} revente{}"
|
|
.format(count, pluralize(count))
|
|
)
|
|
elif "annul" in request.POST:
|
|
annulform = ReventeTirageAnnulForm(participant, request.POST,
|
|
prefix="annul")
|
|
if annulform.is_valid():
|
|
reventes = annulform.cleaned_data['reventes']
|
|
count = reventes.count()
|
|
for revente in reventes:
|
|
revente.confirmed_entry.remove(participant)
|
|
if count > 0:
|
|
messages.success(
|
|
request,
|
|
"Tu as bien été désinscrit de {} revente{}"
|
|
.format(count, pluralize(count))
|
|
)
|
|
|
|
return render(request, "bda/revente/tirages.html",
|
|
{"annulform": annulform, "subform": subform})
|
|
|
|
|
|
@login_required
|
|
def revente_confirm(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 not revente.notif_sent or revente.shotgun:
|
|
return render(request, "bda/revente/wrongtime.html",
|
|
{"revente": revente})
|
|
|
|
revente.confirmed_entry.add(participant)
|
|
return render(request, "bda/revente/confirmed.html",
|
|
{"spectacle": revente.attribution.spectacle,
|
|
"date": revente.date_tirage})
|
|
|
|
|
|
@login_required
|
|
def revente_subscribe(request, tirage_id):
|
|
"""
|
|
Permet à un participant de sélectionner ses préférences pour les reventes.
|
|
Il recevra des notifications pour les spectacles qui l'intéressent et il
|
|
est automatiquement inscrit aux reventes en cours au moment où il ajoute un
|
|
spectacle à la liste des spectacles qui l'intéressent.
|
|
"""
|
|
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('confirmed_entry'))
|
|
.order_by('nb_subscribers')
|
|
.first()
|
|
)
|
|
if min_resell is not None:
|
|
min_resell.confirmed_entry.add(participant)
|
|
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/revente/subscribe.html", {"form": form})
|
|
|
|
|
|
@login_required
|
|
def revente_buy(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.
|
|
own_reventes = reventes.filter(seller=participant)
|
|
if len(own_reventes) > 0:
|
|
own_reventes[0].delete()
|
|
return HttpResponseRedirect(reverse("bda-revente-shotgun",
|
|
args=[tirage.id]))
|
|
|
|
reventes_shotgun = reventes.filter(shotgun=True)
|
|
|
|
if not reventes_shotgun:
|
|
return render(request, "bda/revente/none.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/revente/mail-success.html",
|
|
{"seller": revente.attribution.participant.user,
|
|
"spectacle": spectacle})
|
|
|
|
return render(request, "bda/revente/confirm-shotgun.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())
|
|
.select_related('location')
|
|
.prefetch_related(Prefetch(
|
|
'attribues',
|
|
queryset=(
|
|
Attribution.objects
|
|
.filter(revente__shotgun=True,
|
|
revente__soldTo__isnull=True)
|
|
),
|
|
to_attr='shotguns',
|
|
))
|
|
)
|
|
shotgun = [sp for sp in spectacles if len(sp.shotguns) > 0]
|
|
|
|
return render(request, "bda/revente/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
|
|
.select_related('participant', 'participant__user')
|
|
)
|
|
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
|
|
.select_related('location')
|
|
)
|
|
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)
|
|
.select_related('user')
|
|
)
|
|
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
|
|
custommail = CustomMail.objects.get(shortname="bda-rappel")
|
|
exemple_mail_1place = custommail.render({
|
|
'member': request.user,
|
|
'show': show,
|
|
'nb_attr': 1
|
|
})
|
|
exemple_mail_2places = custommail.render({
|
|
'member': request.user,
|
|
'show': show,
|
|
'nb_attr': 2
|
|
})
|
|
# Contexte
|
|
ctxt = {
|
|
'show': show,
|
|
'exemple_mail_1place': exemple_mail_1place,
|
|
'exemple_mail_2places': exemple_mail_2places,
|
|
'custommail': custommail,
|
|
}
|
|
# Envoi confirmé
|
|
if request.method == 'POST':
|
|
members = show.send_rappel()
|
|
ctxt['sent'] = True
|
|
ctxt['members'] = members
|
|
# Demande de confirmation
|
|
else:
|
|
ctxt['sent'] = False
|
|
if show.rappel_sent:
|
|
messages.warning(
|
|
request,
|
|
"Attention, un mail de rappel pour ce spectale a déjà été "
|
|
"envoyé le {}".format(formats.localize(
|
|
timezone.template_localtime(show.rappel_sent)
|
|
))
|
|
)
|
|
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
|
|
.select_related('location')
|
|
.prefetch_related('quote_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})
|
|
|
|
|
|
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
|
|
.select_related('location')
|
|
.prefetch_related('quote_set')
|
|
)
|
|
if categories_id and 0 not in categories_id:
|
|
shows_qs = shows_qs.filter(category__id__in=categories_id)
|
|
if locations_id and 0 not in 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': [dict(author=quote.author,
|
|
text=quote.text)
|
|
for quote in spectacle.quote_set.all()],
|
|
'image': spectacle.getImgUrl(),
|
|
'ext_link': spectacle.ext_link,
|
|
'price': spectacle.price,
|
|
'slots': spectacle.slots
|
|
}
|
|
for spectacle in shows_qs
|
|
]
|
|
return JsonResponse(data_return, safe=False)
|
|
# Si la requête n'est pas de la forme attendue, on quitte avec une erreur
|
|
return HttpResponseBadRequest()
|