khana/khana/people/views.py

609 lines
20 KiB
Python

from django.contrib.auth.decorators import login_required
from django.db.models import Q, Count, Min
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import render, get_object_or_404
from django.template import RequestContext
from django.views.decorators.http import require_http_methods
from django.views.decorators.cache import cache_page
import simplejson
import pendulum
from .forms import AccidentForm, GymnastForm, GymnastHasRoutineForm, UserForm
from .models import Gymnast, Accident, CanDoRelation
from khana.competition.models import Point
from khana.planning.models import Event
from khana.planning.views import suggest_program
from khana.objective.models import Educative, Skill, Chrono
@login_required
@require_http_methods(["GET"])
def gymnast_listing(request):
"""
Récupère la liste des gymnastes suivant un pattern si celui-ci est défini.
"""
pattern = request.GET.get("pattern", None)
if pattern:
gymnasts_list = Gymnast.objects.filter(
Q(user__last_name__icontains=pattern)
| Q(user__first_name__icontains=pattern)
)
else:
gymnasts_list = Gymnast.objects.filter(user__is_active=True)
context = {"gymnasts_list": gymnasts_list}
print(context)
return render(request, "gymnast_list.html", context)
@login_required
@require_http_methods(["GET"])
def gymnast_lookup(request):
"""
Récupère la liste des gymnastes à la volée suivant des caractères de
recherche entrés. (min 3 caractères)
"""
results = []
pattern = request.GET.get("pattern", None)
# Ignore queries shorter than length 3
if pattern is not None and len(pattern) > 3:
model_results = Gymnast.objects.filter(
Q(lastname__icontains=pattern) | Q(firstname__icontains=pattern)
)
results = [{"ID": x.id, "Name": str(x)} for x in model_results]
json = simplejson.dumps(results)
return HttpResponse(json, content_type="application/json")
@login_required
@require_http_methods(["GET"])
def gymnast_detail(request, gymnastid, tab=None):
"""
Récupère toutes les informations d'un gymnaste.
"""
gymnast = get_object_or_404(Gymnast, pk=gymnastid)
context = {"gymnast": gymnast, "tab": tab}
maxLevelEducative = Educative.objects.filter(cando__gymnast=gymnastid).order_by(
"-level"
)[:1]
context["totalKnownSkill"] = CanDoRelation.objects.filter(gymnast=gymnastid).count()
if maxLevelEducative:
context["totalSkill"] = Skill.objects.filter(
level__lte=maxLevelEducative[0].level
).count()
else:
context["totalSkill"] = Skill.objects.all().count()
context["totalUnknownSkill"] = context["totalSkill"] - context["totalKnownSkill"]
context["nbAccident"] = Accident.objects.filter(gymnast=gymnastid).count()
context["nbEvent"] = Event.objects.filter(gymnasts=gymnastid).count()
context["nbChrono"] = Chrono.objects.filter(gymnast=gymnastid).count()
return render(request, "gymnast_details.html", context)
@cache_page(60 * 3)
@require_http_methods(["GET"])
def gymnast_display_routines(request, gymnastid):
"""
Tag affichant les séries d'un gymnaste.
"""
gymnast = get_object_or_404(Gymnast, pk=gymnastid)
ghr_list = gymnast.has_routine.prefetch_related("routine")
context = {"ghr_list": ghr_list, "gymnastid": gymnast.id}
return render(request, "gymnast_routine.html", context)
# @cache_page(60 * 1)
@require_http_methods(["GET"])
def gymnast_display_scores_chrono(request, gymnastid):
"""
Selectionne tous les scores réalisés par le gymnaste
"""
score_list = Point.objects.filter(gymnast=gymnastid).order_by("-event__datebegin")
score_routine1_list = score_list.filter(routine_type=0)
score_routine2_list = score_list.filter(routine_type=1)
score_routine3_list = score_list.filter(routine_type=2)
chrono_list = Chrono.objects.filter(gymnast=gymnastid).order_by("date")
chrono_10c = chrono_list.filter(routine_type=0)
chrono_r1 = chrono_list.filter(routine_type=1)
chrono_r2 = chrono_list.filter(routine_type=2)
chrono_rf = chrono_list.filter(routine_type=3)
context = {
"score_list": score_list,
"score_routine1_list": score_routine1_list,
"score_routine2_list": score_routine2_list,
"score_routine3_list": score_routine3_list,
"chrono_list": chrono_list,
"chrono_10c": chrono_10c,
"chrono_r1": chrono_r1,
"chrono_r2": chrono_r2,
"chrono_rf": chrono_rf,
"gymnastid": gymnastid,
}
return render(request, "gymnast_scores_chrono.html", context)
@cache_page(60 * 5)
@require_http_methods(["GET"])
def gymnast_display_event_accident(request, gymnastid):
"""
Renvoie deux listes d'évènements : ceux à venir et ceux passés.
"""
today = pendulum.now().date()
next_event_list = Event.objects.filter(gymnasts=gymnastid, datebegin__gte=today)
previous_event_list = Event.objects.filter(gymnasts=gymnastid, datebegin__lte=today)
accident_list = Accident.objects.filter(gymnast=gymnastid)
context = {"accidents": accident_list}
context = {
"next_event_list": next_event_list,
"previous_event_list": previous_event_list,
"accidents": accident_list,
}
return render(request, "gymnast_event_accident.html", context)
@login_required
@require_http_methods(["GET", "POST"])
def gymnast_create_or_update(request, gymnastid=None):
"""
Formulaire de creation et modification d'un gymnaste.
"""
if gymnastid:
gymnast = get_object_or_404(Gymnast, pk=gymnastid)
else:
gymnast = None
if request.method == "POST":
user_form = UserForm(request.POST, instance=gymnast.user)
#user_form["username"] = user_form["first_name"] + "_" + user_form["last_name"]
gymnast_form = GymnastForm(request.POST, instance=gymnast)
if user_form.is_valid():
user = user_form.save()
#gymnast_form["user"] = user
if gymnast_form.is_valid():
gymnast = gymnast_form.save()
return HttpResponseRedirect("/gymnast/" + gymnast.id)
else:
form = GymnastForm(instance=gymnast)
context = {"form": form, "gymnastid": gymnastid}
return render(request, "gymnast_create.html", context)
def __getInformationsFromLevel(gymnast, totalKnownSkill):
"""
Calcule toutes les statistiques par rapport au niveau.
"""
context = {}
maxLevelEducative = Educative.objects.filter(cando__gymnast=gymnast.id).order_by(
"-level"
)[:1]
# Nombre de Skill que le gymnaste sait faire, groupé par niveau
nbKnownSkillByLevel = (
Skill.objects.values("level")
.filter(cando__gymnast=gymnast.id)
.order_by("level")
.annotate(nbknownskill=Count("id"))
)
if maxLevelEducative:
# Niveau maximum de l'élève
context["maxLevelEducative"] = maxLevelEducative[0].level
# Nombre de Skill qui ont un niveau inférieur ou égal
context["totalSkill"] = Skill.objects.filter(
level__lte=context["maxLevelEducative"]
).count()
# Nombre de Skill qui ont un niveau intérieur ou égal, groupé par niveau
nbSkillByLevel = (
Skill.objects.values("level")
.filter(level__lte=context["maxLevelEducative"])
.order_by("level")
.annotate(nbskill=Count("id"))
)
j = 0
tmp = None
percentages = []
for skill in nbSkillByLevel:
tmp = None
if skill["level"] == nbKnownSkillByLevel[j]["level"]:
if skill["nbskill"] != nbKnownSkillByLevel[j]["nbknownskill"]:
tmp = (
skill["level"],
skill["nbskill"],
nbKnownSkillByLevel[j]["nbknownskill"],
int(
(nbKnownSkillByLevel[j]["nbknownskill"] / skill["nbskill"])
* 100
),
)
j += 1
else:
tmp = (skill["level"], skill["nbskill"], 0, 0)
if tmp:
percentages.append(tmp)
context["percentages"] = percentages
# Liste des Skill que le gymnaste ne sait PAS faire, classé par niveau (ayant un niveau inférieur ou égal au niveau max du gym)
context["skillByLevel"] = Skill.objects.filter(
level__lte=context["maxLevelEducative"]
).exclude(cando__gymnast=gymnast.id)
# Liste des Skill que le gymnaste ne sais PAS faire (ayant un niveau plus grand que le niveau max du gym)
context["newUnknownSkill"] = Skill.objects.filter(
level__gt=context["maxLevelEducative"]
).exclude(cando__gymnast=gymnast.id)
else:
context["maxLevelEducative"] = 0
context["totalSkill"] = Skill.objects.all().count()
context["newUnknownSkill"] = Skill.objects.all()
# Calcul des statistiques
if context["totalSkill"] != 0:
context["completude"] = "%s%%" % (
int((totalKnownSkill / context["totalSkill"]) * 100)
)
context["evaluatedLevel"] = int(
context["maxLevelEducative"] * (totalKnownSkill / context["totalSkill"])
)
else:
context["completude"] = 0
context["evaluatedLevel"] = 0
return context
def __getInformationsFromRank(gymnast, totalKnownSkill):
"""
Calcule toutes les statistiques par rapport au rang.
"""
context = {}
min_rank = (
Skill.objects.values("rank")
.exclude(cando__gymnast=gymnast.id)
.order_by("rank")
.aggregate(Min("rank"))["rank__min"]
)
# print(min_rank)
# Nombre de Skill que le gymnaste sait faire, groupé par niveau
nbKnownSkillByRank = (
Skill.objects.values("rank")
.filter(cando__gymnast=gymnast.id)
.order_by("rank")
.annotate(nbknownskill=Count("id"))
)
# Rang maximum dans les éducatifs que le gymnaste sait faire
maxRankEducative = Educative.objects.filter(cando__gymnast=gymnast.id).order_by(
"-rank"
)[:1]
if maxRankEducative:
# Niveau maximum de l'élève
context["maxRankEducative"] = maxRankEducative[0].rank
# Nombre de Skill qui ont un niveau inférieur ou égal
context["totalSkill"] = Skill.objects.filter(
level__lte=context["maxRankEducative"]
).count()
# Nombre de Skill qui ont un niveau intérieur ou égal, groupé par niveau
nbSkillByRank = (
Skill.objects.values("rank")
.filter(level__lte=context["maxRankEducative"])
.order_by("rank")
.annotate(nbskill=Count("id"))
)
j = 0
tmp = None
percentages = []
for skill in nbSkillByRank:
# les deux lignes ci-derssous doiuvent partir
if j >= nbKnownSkillByRank.count():
break
tmp = None
# print(j)
if skill["rank"] == nbKnownSkillByRank[j]["rank"]:
if skill["nbskill"] != nbKnownSkillByRank[j]["nbknownskill"]:
tmp = (
skill["rank"],
skill["nbskill"],
nbKnownSkillByRank[j]["nbknownskill"],
int(
(nbKnownSkillByRank[j]["nbknownskill"] / skill["nbskill"])
* 100
),
)
j += 1
else:
# print('dans le else')
tmp = (skill["rank"], skill["nbskill"], 0, 0)
if tmp:
percentages.append(tmp)
context["percentages"] = percentages
# Liste des Skill que le gymnaste ne sait PAS faire, classé par niveau (ayant un niveau inférieur ou égal au niveau max du gym)
context["skillByRank"] = Skill.objects.filter(
level__lte=context["maxRankEducative"]
).exclude(cando__gymnast=gymnast.id)
# Liste des Skill que le gymnaste ne sais PAS faire (ayant un niveau plus grand que le niveau max du gym)
context["newUnknownSkill"] = Skill.objects.filter(
level__gt=context["maxRankEducative"]
).exclude(cando__gymnast=gymnast.id)
else:
context["maxRankEducative"] = 0
context["totalSkill"] = Skill.objects.all().count()
context["newUnknownSkill"] = Skill.objects.all()
# Calcul des statistiques
if context["totalSkill"] != 0:
context["completude"] = "%s%%" % (
int((totalKnownSkill / context["totalSkill"]) * 100)
)
context["evaluatedLevel"] = (min_rank - 1) + (
int(context["maxRankEducative"] * (totalKnownSkill / context["totalSkill"]))
)
else:
context["completude"] = 0
context["evaluatedLevel"] = 0
return context
# @cache_page(60 * 1)
@require_http_methods(["GET"])
def gymnast_display_stats(request, gymnastid):
"""
Tag affichant les statistiques d'un gymnaste : le nombre de saut qu'il sait faire (total,
par niveau, par rank, …), calcule la complétude, …
.. todo:: Générer UNE fois la liste de skill que le gymnaste ne sait pas faire (1 query)
et les counts puis, dans le template on parcourt plusieurs fois cette même liste mais on
affiche conditionnellement (par age, par rank, ...)
"""
gymnast = get_object_or_404(Gymnast, pk=gymnastid)
# Nombre de Skill que le gymnaste sait faire
gymnast_known_skills = gymnast.known_skills.count()
context = __getInformationsFromLevel(gymnast, gymnast_known_skills)
context.update(__getInformationsFromRank(gymnast, gymnast_known_skills))
if gymnast.gender:
context["skillByAge"] = Skill.objects.filter(age_girl__lte=gymnast.age).exclude(
cando__gymnast=gymnast.id
)
else:
context["skillByAge"] = Skill.objects.filter(age_boy__lte=gymnast.age).exclude(
cando__gymnast=gymnast.id
)
context["gymnast"] = gymnast
context["totalKnownSkill"] = gymnast_known_skills
return render(request, "gymnast_statistics.html", context)
@cache_page(60 * 2)
@require_http_methods(["GET"])
def gymnast_display_level(request, gymnastid):
"""
Tag affichant les statistiques d'un gymnaste : le nombre de saut qu'il sait faire (total,
par niveau, par rank, …), calcule la complétude, …
.. todo:: Générer UNE fois la liste de skill que le gymnaste ne sait pas faire (1 query)
et les counts puis, dans le template on parcourt plusieurs fois cette même liste mais on
affiche conditionnellement (par age, par rank, ...)
"""
gymnast = get_object_or_404(Gymnast, pk=gymnastid)
gymnast_known_skills = gymnast.known_skills.count()
context = __getInformationsFromRank(gymnast, gymnast_known_skills)
context["gymnast"] = gymnast
context["totalKnownSkill"] = gymnast_known_skills
if gymnast.gender:
context["skillByAge"] = Skill.objects.filter(age_girl__lte=gymnast.age).exclude(
cando__gymnast=gymnast.id
)
else:
context["skillByAge"] = Skill.objects.filter(age_boy__lte=gymnast.age).exclude(
cando__gymnast=gymnast.id
)
return render(request, "gymnast_display_level.html", context)
@login_required
@require_http_methods(["GET"])
def gymnast_display_program(request, gymnastid, date=None):
"""Fais une proposition de programme pour le prochain cours si un programme
n'existe pas déjà.
1) trouver la date du prochain cours de l'élève
2) vérifier si un programme existe pour ce cours
2.1) s'il en existe un, aller le chercher pour l'afficher
2.2) s'il n'existe pas, proposer un programme (non validé)
"""
if date is None:
date = pendulum.now().date()
else:
date = pendulum.datetime(date.year(), date.month(), date.day())
iso_day_number = int(date.format("E"))
gymnast = get_object_or_404(Gymnast, pk=gymnastid)
next_course = (
gymnast.courses.filter(iso_day_number__gte=iso_day_number)
.order_by("iso_day_number")
.first()
)
if next_course is None:
next_course = (
gymnast.courses.filter(iso_day_number__gte=0)
.order_by("iso_day_number")
.first()
)
diff = (7 - iso_day_number) + next_course.iso_day_number
else:
diff = iso_day_number - next_course.iso_day_number
course_date = date.add(days=diff)
# dans le cas ou il y a des plusiuers cours le même jours… il faut changer le code !
training = gymnast.trainings.filter(trainingdate=course_date).first()
round_list = None
if training is None:
print("Dans le IF")
round_list = suggest_program(None, gymnastid, date, next_course.duration)
else:
print("Dans le ELSE")
round_list = training.rounds.order_by("round_number")
context = {"course_date": course_date, "round_list": round_list}
return render(request, "gymnast_program.html", context)
@login_required
@require_http_methods(["GET", "POST"])
def link_routine_to_gymnast(request, gymnastid=None):
"""
"""
if gymnastid:
gymnast = get_object_or_404(Gymnast, pk=gymnastid)
data = {"gymnast": gymnast.id, "gymnast_related": str(gymnast), "dateend": None}
else:
gymnast = None
data = {"dateend": None}
if request.method == "POST":
print(request.POST)
form = GymnastHasRoutineForm(request.POST)
if form.is_valid():
form.save()
if gymnastid is not None:
return HttpResponseRedirect("/gymnast/" + str(gymnastid) + "/")
else:
return HttpResponseRedirect("/gymnast/")
else:
form = GymnastHasRoutineForm(instance=gymnast, initial=data)
context = {"form": form, "gymnastid": gymnastid}
return render(request, "gymnast_to_routine.html", context)
@login_required
@require_http_methods(["GET"])
def accident_listing(request):
"""
Récupère la liste des accidents suivant un pattern si celui-ci est définit.
"""
pattern = request.GET.get("pattern", None)
if pattern:
accident_list = Accident.objects.filter(
Q(gymnast__lastname__icontains=pattern)
| Q(gymnast__firstname__icontains=pattern)
)
else:
accident_list = Accident.objects.all()
context = {"accident_list": accident_list}
return render(request, "accident_list.html", context)
@login_required
@require_http_methods(["GET", "POST"])
def accident_create_or_update(request, accidentid=None):
"""
Formulaire de création d'un nouvel accident.
"""
if accidentid:
accident = get_object_or_404(Accident, pk=accidentid)
data = {
"gymnast_related": accident.gymnast,
"educative_related": accident.educative,
}
else:
accident = None
data = {}
if request.method == "POST":
form = AccidentForm(request.POST, instance=accident)
if form.is_valid():
form.save()
if accidentid:
return HttpResponseRedirect("/accident/" + str(accidentid) + "/")
else:
return HttpResponseRedirect("/accident/")
else:
form = AccidentForm(instance=accident, initial=data)
context = {"form": form, "accidentid": accidentid}
return render(request, "accident_create.html", context)
@login_required
@require_http_methods(["GET"])
def accident_detail(request, accidentid):
"""
Récupère toutes les informations d'un accident.
"""
accident = get_object_or_404(Accident, pk=accidentid)
context = {"accident": accident}
return render(request, "accident_details.html", context)
@login_required
def getRandomKnownSkill(request, gymnastid, numberOfSkill):
"""
Récupére, au hasard, `numberOfSkill` skill que le gymnaste sait déjà faire.
"""
# improvement : http://stackoverflow.com/questions/1731346/how-to-get-two-random-records-with-django/6405601#6405601
skillList = Skill.objects.filter(cando__gymnast=gymnastid).order_by("?")[
0:numberOfSkill
]
context = {"skillList": skillList}
return render(request, "gymnast_details.html", context)