569 lines
18 KiB
Python
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)
|