khana/khana/objective/views.py

569 lines
18 KiB
Python

# coding=UTF-8
from django.db.models import Q
from django.shortcuts import render, get_object_or_404
from django.template import RequestContext
from django.http import HttpResponse, HttpResponseRedirect
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.urls import reverse
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.views.decorators.clickjacking import xframe_options_exempt
import simplejson
from .models import Skill, Educative, Routine, Chrono, RoutineSkill
from .forms import RoutineForm, ChronoForm
from people.models import Gymnast, CanDoRelation
from datetime import datetime
import random
import math
def __lookup(lookup_class, lookup_value):
"""
Renvoie une liste d' `objective` basée sur un pattern de recherche de 3
caractères au moins.
:param lookup_class: classe fille dans laquelle la recherche doit être
faite.
:type lookup_class: string (`skill` ou `routine`).
:param lookup_value: pattern de recherche.
:type lookup_value: string.
:return: liste d' `objective`.
"""
if lookup_value is not None and len(lookup_value) >= 3:
model_results = lookup_class.objects.filter(
Q(longLabel__icontains=lookup_value) | Q(shortLabel__icontains=lookup_value)
).order_by("shortLabel")
return [
{"id": x.id, "label": str(x), "notation": x.notation} for x in model_results
]
return []
@login_required
def skill_listing(request, field=None, expression=None, value=None, level=None):
"""
Récupère la liste des skills suivant un pattern si celui-ci est définit.
"""
pattern = None
if not field or not value or not expression:
pattern = request.GET.get("pattern", None)
if pattern:
skill_list = Skill.objects.filter(
Q(longLabel__icontains=pattern) | Q(shortLabel__icontains=pattern)
)
elif field and expression and value:
kwargs = {"{0}__{1}".format(field, expression): value}
# print(kwargs)
# skill_list = Skill.objects.filter(level__lte=4)
skill_list = Skill.objects.filter(**kwargs)
elif level is not None:
skill_list = Skill.objects.filter
else:
skill_list = Skill.objects.all()
### Paginator test
# Question 2 FRED : et pour le tri par JS ? Pour un tri, le JS doit recevoir TOUTES les données... :-/ Comment ?
# paginator = Paginator(skill_list, 25)
# page = request.GET.get('page', 1)
# try:
# skills = paginator.page(page)
# except PageNotAnInteger:
# # If page is not an integer, deliver first page.
# skills = paginator.page(1)
# except EmptyPage:
# # If page is out of range (e.g. 9999), deliver last page of results.
# skills = paginator.page(paginator.num_pages)
context = {"skills": skill_list}
return render(request, "skill_list.html", context)
@login_required
@require_http_methods(["GET"])
def skill_lookup(request):
"""
Récupère la liste des `skill` à la volée suivant des caractères de recherche entrés.
"""
json = simplejson.dumps(__lookup(Skill, request.GET.get("pattern", 0)))
return HttpResponse(json, content_type="application/json")
@login_required
@require_http_methods(["GET"])
def skill_details(request, skillid):
"""
Récupère toutes les informations d'un skill.
:param skillig: id d'un `skill`
:type skillid: int
:return: skill
"""
context = {"skill": get_object_or_404(Skill, pk=skillid)}
return render(request, "skill_details.html", context)
@login_required
@require_http_methods(["POST"])
def link_skill_to_gymnast(request):
"""
Lie un gymnast à une figure.
"""
# utiliser un FORM pour cette fonction.
gymnastid = request.POST.get("gymnastid", None)
skillid = request.POST.get("skillid", None)
if gymnastid and skillid:
g = Gymnast.objects.get(pk=gymnastid)
s = Educative.objects.get(pk=skillid)
CanDoRelation.create(g, s, datetime.now())
return HttpResponse(status=200)
else:
print("Erreur : impossible de lier le gymnaste à la figure.")
return HttpResponse(status=500)
@login_required
def routine_listing(request):
"""
Récupère la liste des routines (série) suivant un pattern si celui-ci est définit.
"""
pattern = request.GET.get("pattern", None)
if pattern:
routine_list = Routine.objects.filter(
Q(longLabel__icontains=pattern) | Q(shortLabel__icontains=pattern)
)
else:
routine_list = Routine.objects.all()
context = {"routine_list": routine_list}
return render(request, "routine_list.html", context)
@login_required
@require_http_methods(["GET"])
def routine_lookup(request):
"""
Récupère la liste des lieux à la volée suivant des caractères de recherche entrés.
"""
pattern = request.GET.get("pattern", 0)
if pattern is not None and len(pattern) >= 3:
results = Routine.objects.filter(
Q(longLabel__icontains=pattern) | Q(shortLabel__icontains=pattern)
)
place_list = [{"id": x.id, "label": str(x)} for x in results]
json = simplejson.dumps(place_list)
return HttpResponse(json, content_type="application/json")
@login_required
@require_http_methods(["GET"])
def compose_routine(request, routineid):
"""
Récupère une routine et les sauts associés.
"""
routine = get_object_or_404(Routine, pk=routineid)
number_of_skill = routine.skill_links.all().count()
context = {
"routine": routine,
"skill_link_list": routine.skill_links.all(),
"number_of_skill": number_of_skill,
}
return render(request, "compose_routine.html", context)
# @login_required
# @xframe_options_exempt
@require_http_methods(["GET"])
def link_skill_to_routine(request, routineid, skillid, order):
"""
Recoit trois informations permettant de lier complètement un saut à une routine
"""
routine = get_object_or_404(Routine, pk=routineid)
skill = get_object_or_404(Skill, pk=skillid)
link, created = RoutineSkill.objects.get_or_create(
routine=routine, skill=skill, rank=order
)
# context = {'link':link, 'created':created}
# return render(request, 'compose_routine.html', context)
return HttpResponse(200, (link, created))
# except:
# return False
def delete_skill_from_routine(request, routineid, order):
"""
Recoit trois informations permettant de lier complètement un saut à une routine
"""
routine = get_object_or_404(Routine, pk=routineid)
try:
link = RoutineSkill.objects.get(routine=routine, rank=order).delete()
except:
return HttpResponse(409)
return HttpResponse(200)
# @login_required
# @require_http_methods(['GET'])
# def routine_lookup(request):
# """
# Récupère la liste des `routine` à la volée suivant des caractères de recherche entrés.
# """
# json = simplejson.dumps(__lookup(Routine, request.GET.get('pattern', 0)))
# return HttpResponse(json, content_type='application/json')
@login_required
def routine_details(request, routineid):
"""
Récupère toutes les informations d'une routine (série).
:param routineid: id d'une `routine`
:type routineid: int
:return: routineid
"""
routine = get_object_or_404(Routine, pk=routineid)
rank = 0
level = 0
difficulty = 0
age_boy = 0
age_girl = 0
is_competitive = True
for skill_link in routine.skill_links.all():
difficulty += skill_link.skill.difficulty
if skill_link.skill.level > level:
level = skill_link.skill.level
if skill_link.skill.rank > rank:
rank = skill_link.skill.rank + 1
if not skill_link.skill.is_competitive:
is_competitive = False
if skill_link.skill.ageBoy is not None and skill_link.skill.ageBoy > age_boy:
age_boy = skill_link.skill.ageBoy
if skill_link.skill.ageGirl is not None and skill_link.skill.ageGirl > age_girl:
age_girl = skill_link.skill.ageGirl
if routine.skill_links.all().count() != 10:
is_competitive = False
routine.difficulty = difficulty
if routine.level < level:
routine.level = level
if routine.rank is None or routine.rank < rank:
routine.rank = rank
if routine.ageBoy is None or routine.ageBoy < age_boy:
routine.ageBoy = age_boy
if routine.ageGirl is None or routine.ageGirl < age_girl:
routine.ageGirl = age_girl
routine.is_competitive = is_competitive
routine.save()
context = {"routine": routine, "skill_link_list": routine.skill_links.all()}
return render(request, "routine_details.html", context)
@login_required
@require_http_methods(["GET", "POST"])
def routine_create_or_update(request, routine_id=None):
""" Création d'une série.
Args:
routine_id (int): identifiant d'un object de classe <routine>.
"""
if routine_id:
routine = get_object_or_404(Routine, pk=routine_id)
else:
routine = None
if request.method == "POST":
form = RoutineForm(request.POST, instance=routine)
if form.is_valid():
routine = form.save()
# ici faire un FOR skill in form_skills_list:
# record.save() # ca sauve le record dans la table RoutineSkill
# something like this : http://stackoverflow.com/questions/3074938/django-m2m-form-save-through-table
# TO_FRED : can you help me ?
return HttpResponseRedirect(reverse("routine_details", args=(routine.pk,)))
else:
form = RoutineForm(instance=routine)
context = {"form": form, "routineid": routine_id}
return render(request, "routine_create.html", context)
@login_required
def random_skill(request, skill_quantity=20, is_competitive=True):
"""
Renvoie une liste aléatoire de `skill_quantity` skill.
Ces skills peuvent être de compétition ou non.
Le but est de permettre aux juges de s'entrainer
TODO : gérer le "is_competitive=False" (bien que pas sur que cela ait un intérêt)
"""
skillid_list = Skill.objects.values("id").filter(is_competitive=is_competitive)
number_of_skill = skillid_list.count()
selected_skillid_list = [
skillid_list[x]["id"]
for x in [
random.randrange(0, number_of_skill, 1) for i in range(skill_quantity)
]
]
skill_list = Skill.objects.filter(id__in=selected_skillid_list)
mylist = [
{"ID": x.id, "Label": str(x), "Difficulty": x.difficulty} for x in skill_list
]
context = {"list": mylist}
return render(request, "judge_training_diff.html", context)
def __construct_routine(
current_routine,
max_routine_length,
max_skill_difficulty,
total_difficulty_score=None,
competition=True,
logic=True,
gymnast=None,
):
""" Construit et propose des séries.
Args:
current_routine (array): la série en cours de création.
max_routine_length (int): nombre de saut souhaités dans la série.
max_skill_difficulty (int): difficulté du saut le plus difficile à considérer.
total_difficulty_score (int): score total de difficulté souhaité de la série EN DIXIEME (/!\).
competition (bool): indique si la série doit respecter les règles de compétition.
logic (bool): indique si la série doit suivre les règles de logique (sportive).
gymnast (gymnast): gymnaste.
Returns:
??? (list ?): liste des séries correspondantes aux criètres.
"""
if len(current_routine) == max_routine_length:
print(current_routine)
return
if total_difficulty_score / max_skill_difficulty > max_routine_length:
return
if total_difficulty_score <= 0:
return # Total score négatif, on est au dela du total score demandé
if current_routine:
skill_list = Skill.objects.filter(
departure=current_routine[-1].landing
) # , difficulty__lte = total_difficulty_score
if len(current_routine) == (max_routine_length - 1):
skill_list = skill_list.filter(landing__longLabel="Debout")
if logic and current_routine[-1].landing.longLabel == "Debout":
skill_list = skill_list.exclude(
rotationType=current_routine[-1].rotationType
)
else:
skill_list = Skill.objects.filter(departure__longLabel="Debout")
if competition:
skill_list = skill_list.filter(is_competitive=True)
if logic and total_difficulty_score:
if len(current_routine) == max_routine_length - 1:
min_diff_skill = total_difficulty_score
max_diff_skill = total_difficulty_score + 3
else:
if (
math.ceil(total_difficulty_score / max_routine_length)
<= max_skill_difficulty
):
min_diff_skill = math.ceil(
max((total_difficulty_score / max_routine_length) - 5, 0)
)
else:
return
if (
math.ceil(total_difficulty_score / max_routine_length) + 2
) <= max_skill_difficulty:
max_diff_skill = (
math.ceil(total_difficulty_score / max_routine_length) + 2
)
else:
return
skill_list = skill_list.filter(
difficulty__gte=(min_diff_skill / 10), difficulty__lte=(max_diff_skill / 10)
)
if gymnast:
skill_list = skill_list.filter(cando__gymnast=gymnast)
for skill in skill_list:
current_routine.append(skill)
__construct_routine(
current_routine,
max_routine_length,
max_skill_difficulty,
total_difficulty_score - (skill.difficulty * 10)
if total_difficulty_score is not None
else None,
competition,
logic,
gymnast,
)
current_routine.pop()
return current_routine
def suggest_routine(
request,
max_routine_length=2,
total_difficulty_score=None,
competition=True,
logic=True,
gymnast=None,
):
""" Construit et propose des séries.
Args:
routine_length (int): nombre de sauts qui compose la série.
total_difficulty_score (int): score total de difficulté souhaité de la série EN DIXIEME (/!\).
competition (bool): indique si la série doit respecter les règles de compétition.
logic (bool): indique si la série doit suivre les règles de logique (sportive).
gymnast (gymnast): gymnaste.
last_jump (skill): dernier saut sélectionné pour la série.
Returns:
??? (list): liste des séries correspondantes aux criètres.
>>> http://127.0.0.1:8000/routine/suggest/
"""
routine = []
if not gymnast:
gymnast = Gymnast.objects.get(pk=14)
total_difficulty_score = 26
if gymnast:
max_skill_difficulty = (
Educative.objects.values("difficulty")
.filter(cando__gymnast=gymnast)
.order_by("-difficulty")[:1][0]["difficulty"]
* 10
)
else:
max_skill_difficulty = (
Skill.objects.values("difficulty").order_by("-difficulty")[:1][0][
"difficulty"
]
* 10
)
# difficulty_scores = range(5, 45, 5)
# for total_difficulty_score in difficulty_scores:
# print("===============================================================================================")
__construct_routine(
routine,
max_routine_length,
max_skill_difficulty,
total_difficulty_score,
competition,
logic,
gymnast,
)
# print(routines)
@login_required
def chrono_listing(request, gymnastid=None, routineid=None):
"""
Liste des chronos pour un gymnaste passé en paramètre
"""
chrono_list = Chrono.objects.all()
context = {"chrono_list": chrono_list}
return render(request, "chrono_list.html", context)
@login_required
@require_http_methods(["GET", "POST"])
def chrono_create_or_update(request, chronoid=None, gymnastid=None):
"""
Création ou modification d'un chrono.
"""
if chronoid:
chrono = get_object_or_404(Chrono, pk=chronoid)
data = {
"gymnast": chrono.gymnast.id,
"gymnast_related": str(chrono.gymnast),
"routine": chrono.routine.id,
"routine_related": str(chrono.routine),
}
else:
chrono = None
data = None
if request.method == "POST":
form = ChronoForm(request.POST, instance=chrono)
if form.is_valid():
form.save()
if chronoid is not None:
return HttpResponseRedirect("/chrono/" + str(chronoid) + "/")
elif gymnastid is not None:
return HttpResponseRedirect(
"/gymnast/" + str(gymnastid) + "/tab/scores_chrono/"
)
else:
return HttpResponseRedirect("/chrono/")
else:
if data is None and gymnastid is not None:
gymnast = get_object_or_404(Gymnast, pk=gymnastid)
data = {"gymnast": gymnastid, "gymnast_related": gymnast}
form = ChronoForm(instance=chrono, initial=data)
context = {"form": form, "chronoid": chronoid}
return render(request, "chrono_create.html", context)