Fewer db requests on bda views.

bda.views.etat_places
- Use select_related on spectacles_set,  avoid query issue for each spectacle.
- `slots__sum` is computed in view, instead of a query.
- Cleaner code (and avoid useless computations).

bda.views.places
- Add select_related on places, avoid query issue for each spectacle.

bda.views.inscription
- 1 query for spectacle field choices, instead of (#forms in formset *
  #spectacles)
- Delete BaseBdaFormSet. The validation was redundant with
  `unique_together` of ChoixSpectacle model.
This commit is contained in:
Aurélien Delobelle 2017-04-07 13:25:50 +02:00
parent b8aa5d8bbe
commit 3e0bd2e758
2 changed files with 98 additions and 77 deletions

View file

@ -5,33 +5,10 @@ from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
from django import forms from django import forms
from django.forms.models import BaseInlineFormSet
from django.utils import timezone from django.utils import timezone
from bda.models import Attribution, Spectacle 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): class TokenForm(forms.Form):
token = forms.CharField(widget=forms.widgets.Textarea()) token = forms.CharField(widget=forms.widgets.Textarea())

View file

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from functools import partial
import random import random
import hashlib import hashlib
import time import time
@ -11,9 +12,9 @@ from custommail.shortcuts import (
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib import messages from django.contrib import messages
from django.db import models, transaction from django.db import transaction
from django.core import serializers 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.forms.models import inlineformset_factory
from django.http import ( from django.http import (
HttpResponseBadRequest, HttpResponseRedirect, JsonResponse HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
@ -29,8 +30,7 @@ from bda.models import (
) )
from bda.algorithm import Algorithm from bda.algorithm import Algorithm
from bda.forms import ( from bda.forms import (
BaseBdaFormSet, TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, SoldForm,
SoldForm
) )
@ -44,39 +44,44 @@ def etat_places(request, tirage_id):
Et le total de toutes les demandes Et le total de toutes les demandes
""" """
tirage = get_object_or_404(Tirage, id=tirage_id) tirage = get_object_or_404(Tirage, id=tirage_id)
spectacles1 = ChoixSpectacle.objects \
.filter(spectacle__tirage=tirage) \ spectacles = tirage.spectacle_set.select_related('location')
.filter(double_choice="1") \ spectacles_dict = {} # index of spectacle by id
.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
for spectacle in spectacles: for spectacle in spectacles:
spectacle.total = 0 spectacle.total = 0 # init total requests
spectacle.ratio = 0.0
spectacles_dict[spectacle.id] = spectacle spectacles_dict[spectacle.id] = spectacle
for spectacle in spectacles1:
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"] choices = (
spectacles_dict[spectacle["spectacle"]].ratio = \ ChoixSpectacle.objects
spectacles_dict[spectacle["spectacle"]].total / \ .filter(spectacle__in=spectacles)
spectacles_dict[spectacle["spectacle"]].slots .values('spectacle')
total += spectacle["total"] .annotate(total=Count('spectacle'))
for spectacle in spectacles2: )
spectacles_dict[spectacle["spectacle"]].total += 2*spectacle["total"]
spectacles_dict[spectacle["spectacle"]].ratio = \ # choices *by spectacles* whose only 1 place is requested
spectacles_dict[spectacle["spectacle"]].total / \ choices1 = choices.filter(double_choice="1")
spectacles_dict[spectacle["spectacle"]].slots # choices *by spectacles* whose 2 places is requested
total += 2*spectacle["total"] 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 = { context = {
"proposed": tirage.spectacle_set.aggregate(Sum('slots'))['slots__sum'], "proposed": slots,
"spectacles": spectacles, "spectacles": spectacles,
"total": total, "total": total,
'tirage': tirage 'tirage': tirage
@ -94,11 +99,16 @@ def _hash_queryset(queryset):
@cof_required @cof_required
def places(request, tirage_id): def places(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id) tirage = get_object_or_404(Tirage, id=tirage_id)
participant, created = Participant.objects.get_or_create( participant, _ = (
user=request.user, tirage=tirage) Participant.objects
places = participant.attribution_set.order_by( .get_or_create(user=request.user, tirage=tirage)
"spectacle__date", "spectacle").all() )
total = sum([place.spectacle.price for place in places]) 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 = [] filtered_places = []
places_dict = {} places_dict = {}
spectacles = [] spectacles = []
@ -148,33 +158,62 @@ def inscription(request, tirage_id):
return render(request, 'bda/resume-inscription-tirage.html', {}) return render(request, 'bda/resume-inscription-tirage.html', {})
if timezone.now() > tirage.fermeture: if timezone.now() > tirage.fermeture:
# Le tirage est fermé. # Le tirage est fermé.
participant, created = Participant.objects.get_or_create( participant, _ = (
user=request.user, tirage=tirage) Participant.objects
choices = participant.choixspectacle_set.order_by("priority").all() .get_or_create(user=request.user, tirage=tirage)
)
choices = participant.choixspectacle_set.order_by("priority")
messages.error(request, messages.error(request,
" C'est fini : tirage au sort dans la journée !") " C'est fini : tirage au sort dans la journée !")
return render(request, "bda/resume-inscription-tirage.html", return render(request, "bda/resume-inscription-tirage.html",
{"choices": choices}) {"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. formfield = f.formfield(**kwargs)
Restreint les spectacles proposés aux spectacles du bo tirage. if to_choices:
""" if f.name in to_choices:
if f.name == "spectacle": choices = [('', '---------')] + to_choices[f.name]
kwargs['queryset'] = tirage.spectacle_set formfield._choices = choices
return f.formfield(**kwargs) 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( BdaFormSet = inlineformset_factory(
Participant, Participant,
ChoixSpectacle, ChoixSpectacle,
fields=("spectacle", "double_choice", "priority"), fields=("spectacle", "double_choice", "priority"),
formset=BaseBdaFormSet, formfield_callback=formfield_callback,
formfield_callback=formfield_callback) )
participant, created = Participant.objects.get_or_create( participant, _ = (
user=request.user, tirage=tirage) Participant.objects
.get_or_create(user=request.user, tirage=tirage)
)
success = False success = False
stateerror = False stateerror = False
if request.method == "POST": if request.method == "POST":
# use *this* queryset
dbstate = _hash_queryset(participant.choixspectacle_set.all()) dbstate = _hash_queryset(participant.choixspectacle_set.all())
if "dbstate" in request.POST and dbstate != request.POST["dbstate"]: if "dbstate" in request.POST and dbstate != request.POST["dbstate"]:
stateerror = True stateerror = True
@ -187,9 +226,14 @@ def inscription(request, tirage_id):
formset = BdaFormSet(instance=participant) formset = BdaFormSet(instance=participant)
else: else:
formset = BdaFormSet(instance=participant) formset = BdaFormSet(instance=participant)
# use *this* queryset
dbstate = _hash_queryset(participant.choixspectacle_set.all()) dbstate = _hash_queryset(participant.choixspectacle_set.all())
total_price = 0 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 total_price += choice.spectacle.price
if choice.double: if choice.double:
total_price += choice.spectacle.price total_price += choice.spectacle.price