diff --git a/bda/forms.py b/bda/forms.py index 2029645b..6bb36c32 100644 --- a/bda/forms.py +++ b/bda/forms.py @@ -5,33 +5,10 @@ from __future__ import print_function from __future__ import unicode_literals from django import forms -from django.forms.models import BaseInlineFormSet from django.utils import timezone from bda.models import Attribution, Spectacle -class BaseBdaFormSet(BaseInlineFormSet): - def clean(self): - """Checks that no two articles have the same title.""" - super(BaseBdaFormSet, self).clean() - if any(self.errors): - # Don't bother validating the formset unless each form is valid on - # its own - return - spectacles = [] - for i in range(0, self.total_form_count()): - form = self.forms[i] - if not form.cleaned_data: - continue - spectacle = form.cleaned_data['spectacle'] - delete = form.cleaned_data['DELETE'] - if not delete and spectacle in spectacles: - raise forms.ValidationError( - "Vous ne pouvez pas vous inscrire deux fois pour le " - "même spectacle.") - spectacles.append(spectacle) - - class TokenForm(forms.Form): token = forms.CharField(widget=forms.widgets.Textarea()) diff --git a/bda/views.py b/bda/views.py index 8fda604d..089c1f29 100644 --- a/bda/views.py +++ b/bda/views.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from functools import partial import random import hashlib import time @@ -11,9 +12,9 @@ from custommail.shortcuts import ( 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 models, transaction +from django.db import transaction from django.core import serializers -from django.db.models import Count, Q, Sum +from django.db.models import Count, Q from django.forms.models import inlineformset_factory from django.http import ( HttpResponseBadRequest, HttpResponseRedirect, JsonResponse @@ -29,8 +30,7 @@ from bda.models import ( ) from bda.algorithm import Algorithm from bda.forms import ( - BaseBdaFormSet, TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, - SoldForm + TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, SoldForm, ) @@ -44,39 +44,44 @@ def etat_places(request, tirage_id): Et le total de toutes les demandes """ tirage = get_object_or_404(Tirage, id=tirage_id) - spectacles1 = ChoixSpectacle.objects \ - .filter(spectacle__tirage=tirage) \ - .filter(double_choice="1") \ - .all() \ - .values('spectacle', 'spectacle__title') \ - .annotate(total=models.Count('spectacle')) - spectacles2 = ChoixSpectacle.objects \ - .filter(spectacle__tirage=tirage) \ - .exclude(double_choice="1") \ - .all() \ - .values('spectacle', 'spectacle__title') \ - .annotate(total=models.Count('spectacle')) - spectacles = tirage.spectacle_set.all() - spectacles_dict = {} - total = 0 + + spectacles = tirage.spectacle_set.select_related('location') + spectacles_dict = {} # index of spectacle by id + for spectacle in spectacles: - spectacle.total = 0 - spectacle.ratio = 0.0 + spectacle.total = 0 # init total requests spectacles_dict[spectacle.id] = spectacle - for spectacle in spectacles1: - spectacles_dict[spectacle["spectacle"]].total += spectacle["total"] - spectacles_dict[spectacle["spectacle"]].ratio = \ - spectacles_dict[spectacle["spectacle"]].total / \ - spectacles_dict[spectacle["spectacle"]].slots - total += spectacle["total"] - for spectacle in spectacles2: - spectacles_dict[spectacle["spectacle"]].total += 2*spectacle["total"] - spectacles_dict[spectacle["spectacle"]].ratio = \ - spectacles_dict[spectacle["spectacle"]].total / \ - spectacles_dict[spectacle["spectacle"]].slots - total += 2*spectacle["total"] + + 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": tirage.spectacle_set.aggregate(Sum('slots'))['slots__sum'], + "proposed": slots, "spectacles": spectacles, "total": total, 'tirage': tirage @@ -94,11 +99,16 @@ def _hash_queryset(queryset): @cof_required def places(request, tirage_id): tirage = get_object_or_404(Tirage, id=tirage_id) - participant, created = Participant.objects.get_or_create( - user=request.user, tirage=tirage) - places = participant.attribution_set.order_by( - "spectacle__date", "spectacle").all() - total = sum([place.spectacle.price for place in places]) + 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 = [] @@ -148,33 +158,62 @@ def inscription(request, tirage_id): return render(request, 'bda/resume-inscription-tirage.html', {}) if timezone.now() > tirage.fermeture: # Le tirage est fermé. - participant, created = Participant.objects.get_or_create( - user=request.user, tirage=tirage) - choices = participant.choixspectacle_set.order_by("priority").all() + 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 formfield_callback(f, **kwargs): + 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. + """ - Fonction utilisée par inlineformset_factory ci dessous. - Restreint les spectacles proposés aux spectacles du bo tirage. - """ - if f.name == "spectacle": - kwargs['queryset'] = tirage.spectacle_set - return f.formfield(**kwargs) + 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"), - formset=BaseBdaFormSet, - formfield_callback=formfield_callback) - participant, created = Participant.objects.get_or_create( - user=request.user, tirage=tirage) + 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 @@ -187,9 +226,14 @@ def inscription(request, tirage_id): formset = BdaFormSet(instance=participant) else: formset = BdaFormSet(instance=participant) + # use *this* queryset dbstate = _hash_queryset(participant.choixspectacle_set.all()) total_price = 0 - for choice in participant.choixspectacle_set.all(): + choices = ( + participant.choixspectacle_set + .select_related('spectacle') + ) + for choice in choices: total_price += choice.spectacle.price if choice.double: total_price += choice.spectacle.price