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 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())

View file

@ -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