# -*- 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, Prefetch
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,
    InscriptionInlineFormSet,
)


@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(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)
                        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.
    own_reventes = reventes.filter(seller=participant)
    if len(own_reventes) > 0:
        own_reventes[0].delete()
        return HttpResponseRedirect(reverse("bda-shotgun",
                                            args=[tirage.id]))

    reventes_shotgun = reventes.filter(shotgun=True)

    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())
        .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-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
    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
        .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:
            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': [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()