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 from jarvis.people.models import Gymnast from jarvis.followup.models import GymnastHasRoutine from .validators import is_valid_routine_type from .forms import ( SkillForm, CombinationForm, TrainingRoundForm, GymnastTrainingForm, CombinationSkillForm, GymnastTrainingRoundForm, ) from .models import ( Skill, TrainingRound, Routine, Educative, RoutineSkill, GymnastTraining, GymnastTrainingRound, 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(["GET"]) def educative_details(request, educative_id): """Renvoie les détails d'un educatif suivant que ce soit une combination ou un skill""" try: Routine.objects.get(pk=educative_id) return combination_details(request, educative_id) except Routine.DoesNotExist: return skill_details(request, educative_id) @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(): form.save() return HttpResponse(200) 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 traininground_listing(request): """Liste des passages.""" passe_listing = TrainingRound.objects.all() context = {"passe_listing": passe_listing} return render(request, "trainingrounds/list.html", context) @login_required @require_http_methods(["GET"]) def traininground_details(request, traininground_id, date=None): """Détails d'un passage.""" is_skill = False traininground = get_object_or_404(TrainingRound, pk=traininground_id) educative_list = traininground.educatives.all() routine = None skill_link_list = None traininground.update_traininground() # Decryptage de la regexp if traininground.regexp is not None and traininground.regexp != "": arguments = traininground.regexp.split(" ") if is_valid_routine_type(arguments[0]) and date is not None: # get routine_type routine_type = 0 ghr = GymnastHasRoutine.objects.filter( gymnast=traininground.gymnast, date_begin__lte=date, routine_type=routine_type, ).first() routine = ghr.routine skill_link_list = routine.skill_links.all() if len(arguments) == 2: content = arguments[1].replace("[", "").replace("]", "") ranks = content.split("-") if ranks[0] != "": skill_link_list = skill_link_list.filter(rank__gte=ranks[0]) if ranks[1] != "": skill_link_list = skill_link_list.filter(rank__lte=ranks[1]) content = False if skill_link_list or routine or educative_list: content = True context = { "is_skill": is_skill, "traininground": traininground, "educative_list": educative_list, "routine": routine, "skill_link_list": skill_link_list, "content": content, "is_wc": traininground.is_regexp_wc # "number_of_educative": number_of_educative, } return render(request, "trainingrounds/details.html", context) @login_required @require_http_methods(["GET", "POST"]) def traininground_create_or_update(request, traininground_id=None): """Création d'un passage. Args: traininground_id (int) identifiant d'un object de classe . """ if traininground_id: traininground = get_object_or_404(TrainingRound, pk=traininground_id) else: traininground = None if request.method == "POST": form = TrainingRoundForm(request.POST, instance=traininground) if form.is_valid(): traininground = form.save() return HttpResponseRedirect( reverse("traininground_details", args=(traininground.pk,)) ) return render(request, "trainingrounds/create.html", {"form": form}) form = TrainingRoundForm(instance=traininground) context = {"form": form, "traininground_id": traininground_id} return render(request, "trainingrounds/create.html", context) @login_required @require_http_methods(["GET"]) def gymnast_training_details(request, gymnast_training_id): """Détails d'un entraînement.""" gymnast_training = get_object_or_404(GymnastTraining, pk=gymnast_training_id) training_rounds = gymnast_training.rounds_links.all() context = { "gymnast": gymnast_training.gymnast, "gymnast_training": gymnast_training, "training_rounds": training_rounds, } return render(request, "gymnasttrainings/details.html", context) @require_http_methods(["POST"]) def switch_traininground(request): """ Recoit dans request deux identifiants de trainingprogram qu'il faut échanger : - tp_id (int) identifiant d'une instance de TraiingProgram - direction (str) la direction du changement (0: haut, 1: bas) J'utilise `32767` comme valeur intermédiaire pour le `rank` car c'est la limite supérieure d'un PositiveSmallIntegerField. TODO: ramener un rank d'un seul coup. """ try: target_trainingprogram_id = request.POST.get("tpid", None) direction = int(request.POST.get("direction", 0)) target_trainingpround = get_object_or_404( GymnastTrainingRound, pk=target_trainingprogram_id ) if direction == 0: source_traininground = ( GymnastTrainingRound.objects.filter(rank__lt=target_trainingpround.rank) .order_by("-id") .first() ) new_rank = (target_trainingpround.rank - source_traininground.rank) - 1 is_switchable = (source_traininground.rank == new_rank) else: source_traininground = ( GymnastTrainingRound.objects.filter(rank__gt=target_trainingpround.rank) .order_by("id") .first() ) new_rank = (source_traininground.rank - target_trainingpround.rank) - 1 is_switchable = (source_traininground.rank == new_rank) if is_switchable: saved_source_rank = source_traininground.rank saved_target_rank = target_trainingpround.rank source_traininground.rank = 32767 source_traininground.save() target_trainingpround.rank = saved_source_rank target_trainingpround.save() source_traininground.rank = saved_target_rank source_traininground.save() else: if direction == 0: new_rank = source_traininground.rank + 1 else: new_rank = source_traininground.rank - 1 target_trainingpround.rank = new_rank target_trainingpround.save() return HttpResponse(200) except Exception: return HttpResponse(409) @login_required @require_http_methods(["GET", "POST"]) def gymnast_training_create_or_update(request, gymnasttraining_id=None, gymnast_id=None): """Création d'une série. Args: gymnasttraining_id (int) identifiant d'un objet de la classe gymnast_id (int) identifiant d'un objet de la classe """ if gymnasttraining_id: gymnasttraining = get_object_or_404(GymnastTraining, pk=gymnasttraining_id) else: gymnasttraining = None data = {} if gymnast_id is not None: gymnast = get_object_or_404(Gymnast, pk=gymnast_id) data["gymnast"] = gymnast_id data["gymnast_related"] = str(gymnast) if request.method == "POST": form = GymnastTrainingForm(request.POST, instance=gymnasttraining) if form.is_valid(): gymnasttraining = form.save() return HttpResponseRedirect( reverse("gymnast_training_details", args=(gymnasttraining.pk,)) ) return render(request, "gymnasttrainings/create.html", {"form": form}) form = GymnastTrainingForm(instance=gymnasttraining, initial=data) context = {"form": form, "gymnasttraining_id": gymnasttraining_id} return render(request, "gymnasttrainings/create.html", context) @login_required @require_http_methods(["GET"]) def gymnast_training_listing(request, gymnast_id=None): """Liste des entraînements.""" if gymnast_id: gymnast = Gymnast.objects.get(pk=gymnast_id) gymnasttrainings = gymnast.trainings.all() else: gymnast = None gymnasttrainings = GymnastTraining.objects.all() context = {"gymnasttrainings": gymnasttrainings, "gymnast": gymnast} return render(request, "gymnasttrainings/list.html", context) @login_required @require_http_methods(["GET"]) def gymnast_training_compose(request, gymnast_training_id): """ Récupère un entraînement et les sauts associés sur base d'un id passé en paramètre. Args: gymnast_training_id (int) identifiant d'un object de classe . """ gymnast_training = get_object_or_404(GymnastTraining, pk=gymnast_training_id) gtr_list = gymnast_training.rounds_links.all() rank = gtr_list.count() if not rank: print("rank vide") rank = 1 print(rank) context = { "gymnast_training": gymnast_training, "gtr_list": gtr_list, "rank": rank, } return render(request, "gymnasttrainings/compose.html", context) @login_required @require_http_methods(["POST"]) def traininground_lookup(request): """ Récupère la liste des skill à la volée suivant des caractères de recherche entrés (min 3 caractères). """ trainingrounds = [] pattern = request.POST.get("pattern", None) if pattern is not None and len(pattern) > 1: traininground_list = TrainingRound.objects.filter(label__icontains=pattern) trainingrounds = [ {"ID": x.id, "Label": str(x)} for x in traininground_list ] return JsonResponse(trainingrounds, safe=False) @login_required @require_http_methods(["POST"]) def link_round_to_training(request): """ Recoit dans request trois informations permettant de lier complètement un saut à une routine : - gymnasttraining_id (int) identifiant d'un object de classe - traininground_id (int) identifiant d'un object de classe - repetition (int) nombre de répétition du round dans l'entraînement - rank (int) numéro de place du round dans l'entraînement """ data = { "gymnast_training": get_object_or_404(GymnastTraining, pk=request.POST.get("gymnast_training_id", 0)), "training_round": get_object_or_404(TrainingRound, pk=request.POST.get("traininground_id", 0)), "repetition": request.POST.get("repetition", 0), "rank": request.POST.get("rank", 0), } form = GymnastTrainingRoundForm(data) if form.is_valid(): form.save() return HttpResponse(200) return HttpResponse(406)