kpsul/bda/views.py

912 lines
32 KiB
Python
Raw Normal View History

import hashlib
import json
import random
import time
from collections import defaultdict
from django.conf import settings
from django.contrib import messages
from django.core import serializers
from django.core.exceptions import NON_FIELD_ERRORS
2020-09-15 11:49:05 +02:00
from django.core.mail import send_mail, send_mass_mail
from django.db import transaction
from django.db.models import Count, Prefetch
from django.forms.models import inlineformset_factory
from django.http import HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, render
2020-09-15 11:49:05 +02:00
from django.template import loader
from django.template.defaultfilters import pluralize
from django.urls import reverse
from django.utils import formats, timezone
from django.views.generic.list import ListView
from bda.algorithm import Algorithm
2017-03-31 18:54:31 +02:00
from bda.forms import (
AnnulForm,
InscriptionInlineFormSet,
InscriptionReventeForm,
ResellForm,
ReventeTirageAnnulForm,
ReventeTirageForm,
SoldForm,
TokenForm,
2017-03-31 18:54:31 +02:00
)
from bda.models import (
Attribution,
CategorieSpectacle,
ChoixSpectacle,
Participant,
Salle,
Spectacle,
SpectacleRevente,
Tirage,
)
from gestioncof.decorators import BuroRequiredMixin, buro_required, cof_required
from shared.views import Select2QuerySetView
2013-10-01 15:27:19 +02:00
@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)
2017-01-29 22:49:32 +01:00
# 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 le
client demande le formulaire et le moment 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
2017-04-21 18:22:53 +02:00
)
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"),
2017-04-21 18:22:53 +02:00
formset=InscriptionInlineFormSet,
error_messages={
NON_FIELD_ERRORS: {
2018-11-12 22:52:20 +01:00
"unique_together": "Vous avez déjà demandé ce voeu plus haut !"
}
},
)
2017-04-21 18:22:53 +02:00
2012-07-11 17:39:20 +02:00
if request.method == "POST":
# use *this* queryset
dbstate = _hash_queryset(participant.choixspectacle_set.all())
if "dbstate" in request.POST and dbstate != request.POST["dbstate"]:
formset = BdaFormSet(instance=participant)
2018-11-12 22:22:49 +01:00
messages.error(
request,
"Impossible d'enregistrer vos modifications "
": vous avez apporté d'autres modifications "
"entre temps.",
)
else:
formset = BdaFormSet(request.POST, instance=participant)
if formset.is_valid():
formset.save()
formset = BdaFormSet(instance=participant)
2019-09-18 19:36:02 +02:00
participant.accepte_charte = True
participant.save()
2018-11-12 22:22:49 +01:00
messages.success(
request, "Votre inscription a été mise à jour avec succès !"
)
else:
2018-11-12 22:22:49 +01:00
messages.error(
request,
"Une erreur s'est produite lors de l'enregistrement de vos vœux. "
"Avez-vous demandé plusieurs fois le même spectacle ?",
)
2012-07-11 17:39:20 +02:00
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
return render(
request,
"bda/inscription-tirage.html",
{
"formset": formset,
"total_price": total_price,
"dbstate": dbstate,
"tirage": tirage,
2019-09-18 19:36:02 +02:00
"charte": participant.accepte_charte,
},
)
2017-02-03 17:07:50 +01:00
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()
2017-02-03 17:07:50 +01:00
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,
2017-02-03 17:07:50 +01:00
}
# On lance le tirage
choices = (
ChoixSpectacle.objects.filter(spectacle__tirage=tirage_elt)
.order_by("participant", "priority")
.select_related("participant", "participant__user", "spectacle")
2017-02-03 17:07:50 +01:00
)
results = Algorithm(data["shows"], data["members"], choices)(token)
2017-02-03 17:07:50 +01:00
# 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)
2017-02-03 17:07:50 +01:00
# 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"]
2017-02-03 17:07:50 +01:00
# 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
)
2017-02-03 17:07:50 +01:00
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
]
)
2017-02-03 17:07:50 +01:00
# On inscrit à BdA-Revente ceux qui n'ont pas eu les places voulues
ChoixRevente = Participant.choicesrevente.through
2017-06-02 18:32:23 +02:00
# 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
)
2017-02-03 17:07:50 +01:00
data["duration"] = time.time() - start
2017-02-03 17:07:50 +01:00
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"])
2017-02-03 17:07:50 +01:00
return render(request, "bda-attrib-extra.html", results)
else:
form = TokenForm()
return render(request, "bda-token.html", {"form": form})
2019-01-10 10:31:58 +01:00
@cof_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.annotate_paid().get_or_create(
user=request.user, tirage=tirage
)
2017-02-16 04:52:44 +01:00
resellform = ResellForm(participant, prefix="resell")
annulform = AnnulForm(participant, prefix="annul")
soldform = SoldForm(participant, prefix="sold")
2017-02-16 04:52:44 +01:00
if request.method == "POST":
2016-12-20 22:24:07 +01:00
# On met en vente une place
if "resell" in request.POST:
resellform = ResellForm(participant, request.POST, prefix="resell")
2016-07-27 13:08:00 +02:00
if resellform.is_valid():
2020-09-15 11:49:05 +02:00
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:
2017-10-23 18:59:30 +02:00
revente.reset()
2017-10-26 12:40:11 +02:00
context = {
"vendeur": participant.user,
"show": attribution.spectacle,
"revente": revente,
}
2020-09-15 11:49:05 +02:00
mails.append(
(
2020-09-15 11:49:05 +02:00
"BdA-Revente : {}".format(attribution.spectacle),
loader.render_to_string(
"bda/mails/revente-seller.txt", context=context
),
settings.MAIL_DATA["revente"]["FROM"],
[participant.user.email],
)
)
2020-09-15 11:49:05 +02:00
send_mass_mail(mails)
2016-12-20 22:24:07 +01:00
# On annule une revente
elif "annul" in request.POST:
annulform = AnnulForm(participant, request.POST, prefix="annul")
2016-07-27 13:08:00 +02:00
if annulform.is_valid():
reventes = annulform.cleaned_data["reventes"]
for revente in reventes:
revente.delete()
2016-12-20 22:24:07 +01:00
# 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")
2017-02-16 04:52:44 +01:00
if soldform.is_valid():
reventes = soldform.cleaned_data["reventes"]
for revente in reventes:
revente.attribution.participant = revente.soldTo
revente.attribution.save()
2017-02-16 04:52:44 +01:00
2016-12-20 22:24:07 +01:00
# 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")
2017-02-16 04:52:44 +01:00
if soldform.is_valid():
reventes = soldform.cleaned_data["reventes"]
for revente in reventes:
if revente.attribution.spectacle.date > timezone.now():
2017-10-26 12:40:11 +02:00
# On antidate pour envoyer le mail plus vite
new_date = timezone.now() - SpectacleRevente.remorse_time
2017-10-26 12:40:11 +02:00
revente.reset(new_date=new_date)
sold_exists = soldform.fields["reventes"].queryset.exists()
annul_exists = annulform.fields["reventes"].queryset.exists()
resell_exists = resellform.fields["attributions"].queryset.exists()
return render(
request,
"bda/revente/manage.html",
{
"tirage": tirage,
"soldform": soldform,
"annulform": annulform,
"resellform": resellform,
"sold_exists": sold_exists,
"annul_exists": annul_exists,
"resell_exists": resell_exists,
},
)
2019-01-10 10:31:58 +01:00
@cof_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"]
2017-10-26 12:40:11 +02:00
count = reventes.count()
for revente in reventes:
revente.confirmed_entry.add(participant)
2017-10-26 12:40:11 +02:00
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"]
2017-10-26 12:40:11 +02:00
count = reventes.count()
for revente in reventes:
revente.confirmed_entry.remove(participant)
2017-10-26 12:40:11 +02:00
if count > 0:
messages.success(
request,
"Tu as bien été désinscrit de {} revente{}".format(
count, pluralize(count)
),
)
2018-12-07 17:35:40 +01:00
annul_exists = annulform.fields["reventes"].queryset.exists()
sub_exists = subform.fields["reventes"].queryset.exists()
return render(
request,
"bda/revente/tirages.html",
2018-12-08 10:41:46 +01:00
{
"annulform": annulform,
"subform": subform,
"annul_exists": annul_exists,
"sub_exists": sub_exists,
},
)
2019-01-10 10:31:58 +01:00
@cof_required
def revente_confirm(request, revente_id):
2016-09-04 11:14:09 +02:00
revente = get_object_or_404(SpectacleRevente, id=revente_id)
participant, _ = Participant.objects.get_or_create(
user=request.user, tirage=revente.attribution.spectacle.tirage
)
2017-10-23 18:59:30 +02:00
if not revente.notif_sent or revente.shotgun:
return render(request, "bda/revente/wrongtime.html", {"revente": revente})
2016-09-04 11:14:09 +02:00
revente.confirmed_entry.add(participant)
return render(
request,
"bda/revente/confirmed.html",
{"spectacle": revente.attribution.spectacle, "date": revente.date_tirage},
)
2016-09-04 11:14:09 +02:00
2019-01-10 10:31:58 +01:00
@cof_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 il ajoute un
spectacle à la liste des spectacles qui l'intéressent.
"""
2016-07-25 02:52:49 +02:00
tirage = get_object_or_404(Tirage, id=tirage_id)
participant, _ = Participant.objects.get_or_create(user=request.user, tirage=tirage)
deja_revente = False
2016-09-27 17:35:29 +02:00
success = False
inscrit_revente = []
if request.method == "POST":
2016-07-27 13:08:00 +02:00
form = InscriptionReventeForm(tirage, request.POST)
if form.is_valid():
choices = form.cleaned_data["spectacles"]
participant.choicesrevente.set(choices)
2016-07-27 13:08:00 +02:00
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)
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()}
)
# Messages
if success:
2018-11-21 16:14:52 +01:00
messages.success(request, "Votre inscription a bien été prise en compte")
if deja_revente:
messages.info(
request,
"Des reventes existent déjà pour certains de "
2018-11-21 16:14:52 +01:00
"ces spectacles, vérifiez les places "
"disponibles sans tirage !",
)
if inscrit_revente:
shows = map("<li>{!s}</li>".format, inscrit_revente)
msg = (
2018-11-21 16:14:52 +01:00
"Vous avez été inscrit·e à 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})
2016-07-25 02:52:49 +02:00
2019-01-10 10:31:58 +01:00
@cof_required
def revente_buy(request, spectacle_id):
2016-07-25 02:52:49 +02:00
spectacle = get_object_or_404(Spectacle, id=spectacle_id)
tirage = spectacle.tirage
participant, _ = Participant.objects.get_or_create(user=request.user, tirage=tirage)
2016-07-25 02:52:49 +02:00
reventes = SpectacleRevente.objects.filter(
attribution__spectacle=spectacle, soldTo__isnull=True
)
2016-11-13 01:45:52 +01:00
# 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]))
2016-09-03 18:47:38 +02:00
reventes_shotgun = reventes.filter(shotgun=True)
2016-11-13 01:45:52 +01:00
if not reventes_shotgun:
return render(request, "bda/revente/none.html", {})
2016-09-03 18:47:38 +02:00
2016-07-25 02:52:49 +02:00
if request.POST:
2016-11-13 01:45:52 +01:00
revente = random.choice(reventes_shotgun)
2016-09-19 16:08:12 +02:00
revente.soldTo = participant
revente.save()
context = {
"show": spectacle,
"acheteur": request.user,
"vendeur": revente.seller.user,
}
2020-09-15 11:49:05 +02:00
send_mail(
"BdA-Revente : {}".format(spectacle.title),
loader.render_to_string(
"bda/mails/revente-shotgun-seller.txt", context=context
),
request.user.email,
2017-02-11 16:15:17 +01:00
[revente.seller.user.email],
)
2020-09-15 11:49:05 +02:00
return render(
request,
"bda/revente/mail-success.html",
{"seller": revente.attribution.participant.user, "spectacle": spectacle},
)
2016-07-25 02:52:49 +02:00
return render(
request,
"bda/revente/confirm-shotgun.html",
{"spectacle": spectacle, "user": request.user},
)
2016-07-25 02:52:49 +02:00
2019-01-10 10:31:58 +01:00
@cof_required
2016-10-06 13:46:18 +02:00
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]
2016-10-06 13:46:18 +02:00
2018-12-07 17:35:53 +01:00
return render(request, "bda/revente/shotgun.html", {"spectacles": shotgun})
2016-10-06 13:46:18 +02:00
@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),
2020-10-28 14:35:45 +01:00
"paid": attrib.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
participants[participant.id]["paid"] &= attrib.paid
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):
2018-01-16 16:22:52 +01:00
context = super().get_context_data(**kwargs)
context["tirage_id"] = self.tirage.id
context["tirage_name"] = self.tirage.title
return context
class UnpaidParticipants(BuroRequiredMixin, ListView):
context_object_name = "unpaid"
template_name = "bda-unpaid.html"
def get_queryset(self):
2019-06-17 21:36:09 +02:00
return (
Participant.objects.annotate_paid()
.filter(tirage__id=self.kwargs["tirage_id"], paid=False)
.select_related("user")
)
@buro_required
def send_rappel(request, spectacle_id):
show = get_object_or_404(Spectacle, id=spectacle_id)
# Mails d'exemples
2020-09-15 11:49:05 +02:00
subject = show.title
body_mail_1place = loader.render_to_string(
"bda/mails/rappel.txt",
context={"member": request.user, "show": show, "nb_attr": 1},
)
2020-09-15 11:49:05 +02:00
body_mail_2places = loader.render_to_string(
"bda/mails/rappel.txt",
context={"member": request.user, "show": show, "nb_attr": 2},
)
2020-09-15 11:49:05 +02:00
# Contexte
ctxt = {
"show": show,
2020-09-15 11:49:05 +02:00
"exemple_mail_1place": (subject, body_mail_1place),
"exemple_mail_2places": (subject, body_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
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))
),
)
2016-12-23 10:25:28 +01:00
return render(request, "bda/mails-rappel.html", ctxt)
2017-03-31 02:51:58 +02:00
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":
2017-03-31 02:51:58 +02:00
# 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")
)
2017-03-31 03:15:40 +02:00
return JsonResponse(data_return, safe=False)
if request_type == "details":
2017-03-31 02:51:58 +02:00
# 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}
2017-03-31 03:15:40 +02:00
return JsonResponse(data_return, safe=False)
if request_type == "descriptions":
2017-03-31 02:51:58 +02:00
# 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", "[]"),
)
2017-04-05 00:51:22 +02:00
)
tirage = get_object_or_404(Tirage, id=tirage_id)
shows_qs = tirage.spectacle_set.select_related("location").prefetch_related(
"quote_set"
)
2017-09-20 18:21:59 +02:00
if categories_id and 0 not in categories_id:
shows_qs = shows_qs.filter(category__id__in=categories_id)
2017-09-20 18:21:59 +02:00
if locations_id and 0 not in locations_id:
shows_qs = shows_qs.filter(location__id__in=locations_id)
2017-03-31 02:51:58 +02:00
# 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,
2017-03-31 02:51:58 +02:00
}
for spectacle in shows_qs
2017-03-31 02:51:58 +02:00
]
2017-03-31 03:15:40 +02:00
return JsonResponse(data_return, safe=False)
2017-03-31 02:51:58 +02:00
# Si la requête n'est pas de la forme attendue, on quitte avec une erreur
return HttpResponseBadRequest()
##
# Autocomplete views
#
# https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#create-an-autocomplete-view
##
class ParticipantAutocomplete(Select2QuerySetView):
model = Participant
search_fields = ("user__username", "user__first_name", "user__last_name")
participant_autocomplete = buro_required(ParticipantAutocomplete.as_view())
class SpectacleAutocomplete(Select2QuerySetView):
model = Spectacle
search_fields = ("title",)
spectacle_autocomplete = buro_required(SpectacleAutocomplete.as_view())