from django.contrib.auth.decorators import login_required from django.db.models import Q from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.shortcuts import render, get_object_or_404 from django.views.decorators.http import require_http_methods from django.urls import reverse import pendulum from jarvis.people.models import Gymnast from .forms import ( SkillForm, PasseForm, CombinationForm, CombinationSkillForm, ) from .models import ( Skill, Passe, Routine, Educative, RoutineSkill, TrainingProgram, PrerequisiteClosure, ) @login_required @require_http_methods(["POST"]) def educative_lookup(request): """ Récupère la liste des skill à la volée suivant des caractères de recherche entrés (min 3 caractères). """ results = [] pattern = request.POST.get("pattern", None) # Ignore queries shorter than length 2 if pattern is not None and len(pattern) > 2: model_results = Educative.objects.filter( Q(short_label__icontains=pattern) | Q(long_label__icontains=pattern) ) results = [ {"ID": x.id, "long_label": x.long_label, "short_label": x.short_label} for x in model_results ] return JsonResponse(results, safe=False) @login_required @require_http_methods(["POST"]) def skill_lookup(request): """ Récupère la liste des skill à la volée suivant des caractères de recherche entrés (min 3 caractères). """ results = [] pattern = request.POST.get("pattern", None) # Ignore queries shorter than length 2 if pattern is not None and len(pattern) > 2: model_results = Skill.objects.filter( Q(short_label__icontains=pattern) | Q(long_label__icontains=pattern) | Q(notation__icontains=pattern) ) results = [ {"ID": x.id, "Name": str(x), "Notation": x.notation} for x in model_results ] return JsonResponse(results, safe=False) @login_required @require_http_methods(["GET"]) def skill_without_prerequisite_listing(request): """ Récupère la liste des skills n'ayant aucun prérequis. """ skill_list = Skill.objects.filter(prerequisites=None) context = {"skill_list": skill_list} return render(request, "skills/list.html", context) @login_required @require_http_methods(["GET"]) 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. Args: field (str) champ sur lequel doit être effectué la recherche expression (str) expression de comparaison : le, leq, ge, geq, … value (str) valeur de la recherche level (int) niveau requis pour les skills cherchés. """ 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(long_label__icontains=pattern) | Q(short_label__icontains=pattern) ) elif field and expression and value: kwargs = {f"{field}__{expression}": value} skill_list = Skill.objects.filter(**kwargs) elif level is not None: skill_list = Skill.objects.filter(level=level) else: skill_list = Skill.objects.all() context = {"skill_list": skill_list} return render(request, "skills/list.html", context) @login_required @require_http_methods(["GET"]) def skill_tree(request, skill_id): """Récupère tout l'arbre des prérequis pour une saut passé en paramètre Args: skill_id (int) identifiant de la classe Skill """ skill = get_object_or_404(Skill, pk=skill_id) node_dict = {} skill_closure = PrerequisiteClosure.objects.filter(descendant=skill) for closure in skill_closure: node_dict[closure.ancestor] = closure.ancestor.prerequisites.all() context = {"skill": skill, "node_dict": node_dict} return render(request, "skills/learning_line.html", context) @login_required @require_http_methods(["GET"]) def skill_details(request, skill_id): """Récupère toutes les informations d'un skill. La méthode en profite pour vérifier les champs level, rank, age_boy et age_girl par rapport aux pré-requis. Args: skill_id (int) identifiant de la classe Skill """ skill = get_object_or_404(Skill, pk=skill_id) for prerequisite in skill.prerequisites.all(): skill.level = max(prerequisite.level + 1, skill.level) skill.rank = max(prerequisite.rank + 1, skill.rank) skill.age_boy_with_help = max( skill.age_boy_with_help, prerequisite.age_boy_with_help ) skill.age_boy_without_help = max( skill.age_boy_without_help, prerequisite.age_boy_without_help ) skill.age_boy_chained = max(skill.age_boy_chained, prerequisite.age_boy_chained) skill.age_boy_masterised = max( skill.age_boy_masterised, prerequisite.age_boy_masterised ) skill.age_girl_with_help = max( skill.age_girl_with_help, prerequisite.age_girl_with_help ) skill.age_girl_without_help = max( skill.age_girl_without_help, prerequisite.age_girl_without_help ) skill.age_girl_chained = max( skill.age_girl_chained, prerequisite.age_girl_chained ) skill.age_girl_masterised = max( skill.age_girl_masterised, prerequisite.age_girl_masterised ) skill.save() # Je sépare les educatifs de type "skill" et les éducatifs de type "combination" pour mieux # gérer l'affichage. Du code bien dégueulasse ! # QTF : une idée pour faire ca de manière plus propre, plus élégante ? combination_dict = {} educative_skill = skill.educatives.filter(id__in=Skill.objects.all()) for educative in skill.educatives.filter(id__in=Routine.objects.all()): combination = Routine.objects.get(pk=educative.id) combination_dict[combination] = [] for educ_skill in combination.routine.skill_links.all(): combination_dict[combination].append(educ_skill) context = { "skill": skill, "combination_dict": combination_dict, "educative_skill": educative_skill, } return render(request, "skills/details.html", context) @login_required @require_http_methods(["GET", "POST"]) def skill_create_or_update(request, skill_id=None): """Création ou modification d'un saut. Args: skill_id (int) identifiant d'un object de classe . """ if skill_id: skill = get_object_or_404(Skill, pk=skill_id) else: skill = None if request.method == "POST": form = SkillForm(request.POST, instance=skill) if form.is_valid(): skill = form.save() return HttpResponseRedirect(reverse("skill_details", args=(skill.pk,))) return render(request, "skill/create.html", {"form": form}) form = SkillForm(instance=skill) context = {"form": form, "skill_id": skill_id} return render(request, "skills/create.html", context) @login_required @require_http_methods(["GET"]) def competition_routine_listing(request, gymnast_id=None): """Récupère la liste des routines (séries) de compétition suivant un pattern si celui-ci est défini. Args: gymnast_id (int) identifiant de la classe Gymnast """ gymnast = None pattern = request.GET.get("pattern", None) if pattern is not None and len(pattern) > 2: routine_list = Routine.objects.filter( is_routine=True, is_competitive=True ).filter(Q(long_label__icontains=pattern) | Q(short_label__icontains=pattern)) else: if gymnast_id: routine_list = Routine.objects.filter( is_routine=True, is_competitive=True ).filter(done_by_gymnast__gymnast=gymnast_id) gymnast = Gymnast.objects.get(pk=gymnast_id) else: routine_list = Routine.objects.filter(is_routine=True, is_competitive=True) context = { "title": "Competition Routines", "routine_list": routine_list, "gymnast_id": gymnast_id, "gymnast": gymnast, } return render(request, "combinations/list.html", context) @login_required @require_http_methods(["GET"]) def routine_listing(request, gymnast_id=None): """Récupère la liste des routines (série) suivant un pattern si celui-ci est défini Args: gymnast_id (int) identifiant de la classe Gymnast """ gymnast = None pattern = request.GET.get("pattern", None) base_queryset = Routine.objects.filter(is_routine=True) if pattern is not None and len(pattern) > 2: routine_list = base_queryset.filter( Q(long_label__icontains=pattern) | Q(short_label__icontains=pattern) ) else: if gymnast_id: routine_list = base_queryset.filter(done_by_gymnast__gymnast=gymnast_id) gymnast = Gymnast.objects.get(pk=gymnast_id) else: routine_list = base_queryset context = { "title": "Routines", "routine_list": routine_list, "gymnast_id": gymnast_id, "gymnast": gymnast, } return render(request, "combinations/list.html", context) @login_required @require_http_methods(["GET"]) def educative_combination_listing(request, gymnast_id=None): """Récupère la liste des educatifs suivant un pattern si celui-ci est défini Args: gymnast_id (int) identifiant de la classe Gymnast """ gymnast = None pattern = request.GET.get("pattern", None) base_queryset = Routine.objects.filter(is_routine=False) if pattern is not None and len(pattern) > 2: routine_list = base_queryset.filter( Q(long_label__icontains=pattern) | Q(short_label__icontains=pattern) ) else: if gymnast_id: routine_list = base_queryset.filter(done_by_gymnast__gymnast=gymnast_id) gymnast = Gymnast.objects.get(pk=gymnast_id) else: routine_list = base_queryset context = { "title": "Educative", "routine_list": routine_list, "gymnast_id": gymnast_id, "gymnast": gymnast, } return render(request, "combinations/list.html", context) @login_required @require_http_methods(["GET"]) def combination_listing(request, gymnast_id=None): """Récupère la liste des combinaisons suivant un pattern si celui-ci est défini Args: gymnast_id (int) identifiant de la classe Gymnast """ gymnast = None pattern = request.GET.get("pattern", None) if pattern is not None and len(pattern) > 2: routine_list = Routine.objects.filter( Q(long_label__icontains=pattern) | Q(short_label__icontains=pattern) ) else: if gymnast_id: routine_list = Routine.objects.filter(done_by_gymnast__gymnast=gymnast_id) gymnast = Gymnast.objects.get(pk=gymnast_id) else: routine_list = Routine.objects.all() context = { "title": "Combinations", "routine_list": routine_list, "gymnast_id": gymnast_id, "gymnast": gymnast, } return render(request, "combinations/list.html", context) @login_required @require_http_methods(["POST"]) def combination_lookup(request, search_type=None): """ Récupère la liste des combinaisons à la volée suivant des caractères de recherche entrés. """ pattern = request.POST.get("pattern", None) if pattern is not None and len(pattern) > 2: results = Routine.objects.filter( Q(long_label__icontains=pattern) | Q(short_label__icontains=pattern) ) if search_type: if search_type == "competitive": results = results.filter(is_competitive=1) elif search_type == "routine": results = results.filter(is_routine=1) combination_list = [{"id": x.id, "label": str(x)} for x in results] return JsonResponse(combination_list, safe=False) @login_required @require_http_methods(["GET"]) def combination_details(request, combination_id): """ Récupère toutes les informations d'une combinaison. Args: combination_id (int) identifiant d'une routine """ combination = get_object_or_404(Routine, pk=combination_id) combination.compute_informations() combination_string = combination.inline_str() context = { "combination": combination, "combination_string": combination_string, "skill_link_list": combination.skill_links.all(), } return render(request, "combinations/details.html", context) @login_required @require_http_methods(["GET", "POST"]) def combination_create_or_update(request, combination_id=None): """Création d'une série. Args: combination_id (int) identifiant d'un object de classe . """ if combination_id: combination = get_object_or_404(Routine, pk=combination_id) else: combination = None if request.method == "POST": form = CombinationForm(request.POST, instance=combination) if form.is_valid(): combination = 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 # QTF : can you help me ? return HttpResponseRedirect( reverse("combination_details", args=(combination.pk,)) ) return render(request, "combinations/create.html", {"form": form}) form = CombinationForm(instance=combination) context = {"form": form, "combination_id": combination_id} return render(request, "combinations/create.html", context) @login_required @require_http_methods(["GET"]) def compose_combination(request, combination_id): """ Récupère une routine et les sauts associés sur base d'un id passé en paramètre. Args: combination_id (int) identifiant d'un object de classe . """ routine = get_object_or_404(Routine, pk=combination_id) skill_link_list = routine.skill_links.all() skill_list = Skill.objects.all() context = { "routine": routine, "skill_link_list": skill_link_list, "number_of_skill": skill_link_list.count(), "skill_list": skill_list, } return render(request, "combinations/compose.html", context) @require_http_methods(["POST"]) def link_skill_to_combination(request): """ Recoit dans request trois informations permettant de lier complètement un saut à une routine : - combination_id (int) identifiant d'un object de classe - skill_id (int) identifiant d'un object de classe - rank (int) numéro de place du skill dans la routine """ data = { "routine": get_object_or_404(Routine, pk=request.POST.get("combination_id", 0)), "skill": get_object_or_404(Skill, pk=request.POST.get("skill_id", 0)), "rank": request.POST.get("rank", 0), } form = CombinationSkillForm(data) if form.is_valid(): link, created = RoutineSkill.objects.get_or_create( routine=form.cleaned_data["routine"], skill=form.cleaned_data["skill"], rank=form.cleaned_data["rank"], ) return HttpResponse(200, (link, created)) return HttpResponse(406) @require_http_methods(["POST"]) def unlink_skill_from_combination(request): """ Recoit dans request deux informations permettant d'enlever un skill d'une routine : - rank (int) numéro de place du skill dans la routine - combination_id (int) identifiant d'un object de classe """ rank = request.POST.get("rank", None) combination_id = request.POST.get("combination_id", None) combination = get_object_or_404(Routine, pk=combination_id) try: RoutineSkill.objects.get(routine=combination, rank=rank).delete() except Exception: return HttpResponse(409) return HttpResponse(200) @login_required @require_http_methods(["GET"]) def passe_listing(request): """Liste des passages.""" passe_listing = Passe.objects.all() context = {"passe_listing": passe_listing} return render(request, "passes/list.html", context) @login_required @require_http_methods(["GET"]) def passe_details(request, passe_id): """Détails d'un passage.""" is_skill = False passe = get_object_or_404(Passe, pk=passe_id) educative_list = passe.educatives.all() number_of_educative = educative_list.count() context = { "passe": passe, "is_skill": is_skill, "educative_list": educative_list, "difficulty": passe.difficulty, "number_of_skill": passe.number_of_skill, "number_of_educative": number_of_educative, } return render(request, "passes/details.html", context) @login_required @require_http_methods(["GET", "POST"]) def passe_create_or_update(request, passe_id=None): """Création d'un passage. Args: passe_id (int) identifiant d'un object de classe . """ if passe_id: passe = get_object_or_404(Passe, pk=passe_id) else: passe = None if request.method == "POST": form = PasseForm(request.POST, instance=passe) if form.is_valid(): passe = form.save() return HttpResponseRedirect(reverse("passe_details", args=(passe.pk,))) return render(request, "passes/create.html", {"form": form}) form = PasseForm(instance=passe) context = {"form": form, "passe_id": passe_id} return render(request, "passes/create.html", context) @login_required @require_http_methods(["GET"]) def trainingprogram_details(request, date=None, gymnast_id=None): """Détails d'un entraînement.""" trainingprogram_id = None if trainingprogram_id is not None: trainingprogram = get_object_or_404(TrainingProgram, pk=trainingprogram_id) trainingprogram_list = None else: trainingprogram = None trainingprogram_list = TrainingProgram.objects.all() parsed_date = pendulum.parse(date).date() if date is not None: trainingprogram_list = trainingprogram_list.filter(date=parsed_date) if gymnast_id is not None: trainingprogram_list = trainingprogram_list.filter(gymnast=gymnast_id) # trainingprogram_list = trainingprogram_list.order_by("rank") difficulty = 0 number_of_skill = 0 for trainingprogram in trainingprogram_list: difficulty += trainingprogram.difficulty number_of_skill += trainingprogram.number_of_skill context = { "date": parsed_date, "difficulty": difficulty, "number_of_skill": number_of_skill, "trainingprogram": trainingprogram, "trainingprogram_list": trainingprogram_list, } return render(request, "trainingprograms/details.html", context) @require_http_methods(["POST"]) def switch_trainingprogram_line(request): """ Recoit dans request deux identifiants de trainingprogram qu'il faut échanger () : - tp1 (int) identifiant d'une instance de TraiingProgram - tp2 (int) identifiant d'une instance de TraiingProgram J'utilise `32767` comme valeur intermédiaire pour le `rank` car c'est la limite supérieure d'un PositiveSmallIntegerField. """ try: target_trainingprogram_id = request.POST.get("tp1", None) source_trainingprogram_id = request.POST.get("tp2", None) target_trainingprogram = get_object_or_404( TrainingProgram, pk=target_trainingprogram_id ) source_trainingprogram = get_object_or_404( TrainingProgram, pk=source_trainingprogram_id ) saved_source_rank = source_trainingprogram.rank saved_target_rank = target_trainingprogram.rank source_trainingprogram.rank = 32767 source_trainingprogram.save() target_trainingprogram.rank = saved_source_rank target_trainingprogram.save() source_trainingprogram.rank = saved_target_rank source_trainingprogram.save() except Exception: return HttpResponse(409) return HttpResponse(200)