596 lines
20 KiB
Python
596 lines
20 KiB
Python
from datetime import date
|
||
from django.contrib.auth.decorators import login_required
|
||
from django.contrib.auth.models import Group
|
||
from django.contrib.auth import get_user_model
|
||
from django.contrib.contenttypes.models import ContentType
|
||
|
||
from django.db.models import (
|
||
Q,
|
||
Avg,
|
||
)
|
||
from django.http import HttpResponseRedirect, JsonResponse, HttpResponse
|
||
from django.shortcuts import render, get_object_or_404
|
||
from django.views.decorators.http import require_http_methods
|
||
from django.urls import reverse
|
||
from django.conf import settings
|
||
|
||
from django.core.mail import send_mail
|
||
|
||
import pendulum
|
||
|
||
from jarvis.followup.models import Event
|
||
from jarvis.followup.forms import GymnastHasRoutineForm
|
||
from jarvis.followup.models import (
|
||
Note,
|
||
Plan,
|
||
Skill,
|
||
Point,
|
||
Chrono,
|
||
Injury,
|
||
WellBeing,
|
||
Intensity,
|
||
LearnedSkill,
|
||
HeightWeight,
|
||
SeasonInformation,
|
||
NumberOfRoutineDone,
|
||
)
|
||
|
||
from jarvis.tools.models import Season
|
||
from jarvis.tools.clean_name import clean_name
|
||
from .models import Gymnast
|
||
from .forms import GymnastForm, UserForm
|
||
|
||
User = get_user_model()
|
||
|
||
@login_required
|
||
@require_http_methods(["POST"])
|
||
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.POST.get("pattern", None)
|
||
|
||
if pattern is not None and len(pattern) > 2:
|
||
name = clean_name(pattern)
|
||
|
||
gymnast_list = Gymnast.objects.filter(
|
||
is_active=True, pk__in=request.session["available_gymnast"]
|
||
).filter(
|
||
Q(cleaned_last_name__icontains=name) | Q(cleaned_first_name__icontains=name)
|
||
)
|
||
|
||
results = [{"ID": x.id, "Name": str(x)} for x in gymnast_list]
|
||
|
||
return JsonResponse(results, safe=False)
|
||
|
||
|
||
@login_required
|
||
@require_http_methods(["GET"])
|
||
def gymnast_listing(request):
|
||
"""
|
||
Si la personne connectée est un entraîneur : liste tous les gymnasts actifs connus mais n'aura
|
||
accès qu'aux gymnastes autorisé(e)s.
|
||
Si la personne connectée est un gymnaste : renvoie directement sa fiche détaillée.
|
||
"""
|
||
season = Season()
|
||
|
||
if request.user.is_superuser or request.user.groups.filter(name="trainer").exists():
|
||
season_information_list = SeasonInformation.objects.filter(
|
||
season=season.label
|
||
).select_related("gymnast")
|
||
context = {"season_information_list": season_information_list}
|
||
return render(request, "gymnasts/list.html", context)
|
||
|
||
gymnast = Gymnast.objects.get(user=request.user) # a mettre dans un TRY
|
||
return gymnast_details(request, gymnast.id)
|
||
|
||
|
||
@login_required
|
||
@require_http_methods(["GET"])
|
||
def gymnast_details(request, gymnast_id, tab=None):
|
||
"""
|
||
Récupère toutes les informations d'un gymnaste si la personne connectée est un "trainer".
|
||
Si la personne connectée est un gymnaste : renvoie sa fiche détaillée.
|
||
|
||
Args:
|
||
gymnast_id (int) identifiant du gymnast
|
||
tab (str) <string> de l'onglet désiré
|
||
"""
|
||
|
||
if request.user.is_superuser or (
|
||
hasattr(request.user, 'gymnast')
|
||
and request.user.gymnast.id == gymnast_id
|
||
) or (
|
||
request.session.has_key("available_gymnast")
|
||
and gymnast_id in request.session["available_gymnast"]
|
||
):
|
||
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
|
||
else:
|
||
return gymnast_listing(request)
|
||
|
||
last_season_information = gymnast.season_informations.order_by("-season").first()
|
||
|
||
gymnast_nb_known_skills = gymnast.known_skills.distinct(
|
||
"skill"
|
||
).count() # devrait disparaitre
|
||
|
||
nb_skill = Skill.objects.all().count()
|
||
nb_known_skill = (
|
||
LearnedSkill.objects.filter(gymnast=gymnast_id).distinct("skill").count()
|
||
)
|
||
|
||
if nb_skill != 0:
|
||
percentage_known_skill = (nb_known_skill / nb_skill) * 100
|
||
else:
|
||
percentage_known_skill = 0
|
||
|
||
# base_queryset = Chrono.objects.filter(gymnast=gymnast_id).order_by("-date")
|
||
chronos_list = Chrono.objects.filter(gymnast=gymnast_id).order_by("-date")[:10]
|
||
straightjump_score = (
|
||
Chrono.objects.filter(gymnast=gymnast_id)
|
||
.filter(chrono_type=0)
|
||
.order_by("-date")
|
||
)
|
||
best_straightjump = (
|
||
Chrono.objects.filter(gymnast=gymnast_id)
|
||
.filter(chrono_type=0)
|
||
.order_by("-score")[:1]
|
||
)
|
||
best_routine = (
|
||
Chrono.objects.filter(gymnast=gymnast_id)
|
||
.filter(chrono_type=1)
|
||
.order_by("-score")[:1]
|
||
)
|
||
|
||
context = {
|
||
"gymnast": gymnast,
|
||
"last_season_information": last_season_information,
|
||
"gymnast_nb_known_skills": gymnast_nb_known_skills,
|
||
"chronos_list": chronos_list,
|
||
"straightjump_score": straightjump_score,
|
||
"best_routine": best_routine,
|
||
"best_straightjump": best_straightjump,
|
||
"nb_skill": nb_skill,
|
||
"nb_known_skill": nb_known_skill,
|
||
"percentage_known_skill": percentage_known_skill,
|
||
"tab": tab,
|
||
}
|
||
#TODO: utiliser les {{ perms }}
|
||
context["user_is_trainer"] = request.user.groups.filter(name="trainer").exists()
|
||
return render(request, "gymnasts/details.html", context)
|
||
|
||
|
||
@login_required
|
||
@require_http_methods(["GET"])
|
||
def gymnast_display_season_informations(request, gymnast_id):
|
||
"""
|
||
Renvoie la liste des seasons informations pour un gymnast.
|
||
|
||
Args:
|
||
gymnast_id (int) identifiant du gymnast
|
||
"""
|
||
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
|
||
trainers_list = gymnast.trainers.all().order_by("first_name", "last_name")
|
||
season_information_list = gymnast.season_informations.all()
|
||
context = {
|
||
"gymnast": gymnast,
|
||
"trainers_list": trainers_list,
|
||
"season_information_list": season_information_list,
|
||
}
|
||
return render(request, "gymnasts/tabs/tab_season_informations.html", context)
|
||
|
||
|
||
@login_required
|
||
@require_http_methods(["GET"])
|
||
def gymnast_display_events_and_notes(request, gymnast_id):
|
||
"""
|
||
Renvoie deux listes d'évènements : ceux à venir et ceux passés.
|
||
|
||
Args:
|
||
gymnast_id (int) identifiant du gymnast
|
||
"""
|
||
today = pendulum.now().date()
|
||
next_event_list = Event.objects.filter(gymnasts=gymnast_id, date_begin__gte=today)
|
||
previous_event_list = Event.objects.filter(
|
||
gymnasts=gymnast_id, date_begin__lte=today
|
||
)
|
||
|
||
base_queryset = Note.objects.filter(gymnast=gymnast_id)
|
||
if not request.user.groups.filter(name="trainer").exists():
|
||
notes_list = base_queryset.filter(status=1)
|
||
else:
|
||
notes_list = base_queryset
|
||
notes_list = notes_list.order_by("-created_at")
|
||
|
||
last_notes_list = notes_list[:6]
|
||
latest_published_note = (
|
||
base_queryset.filter(status=1).order_by("-created_at").first()
|
||
)
|
||
|
||
context = {
|
||
"next_event_list": next_event_list,
|
||
"previous_event_list": previous_event_list,
|
||
"last_notes_list": last_notes_list,
|
||
"latest_published_note": latest_published_note,
|
||
"gymnast_id": gymnast_id,
|
||
}
|
||
return render(request, "gymnasts/tabs/tab_notes_events.html", context)
|
||
|
||
|
||
@login_required
|
||
@require_http_methods(["GET"])
|
||
def gymnast_display_physiological(request, gymnast_id):
|
||
"""
|
||
Renvoie les listes des tailles/poids, état d'esprit et blessures.
|
||
|
||
Args:
|
||
gymnast_id (int) identifiant du gymnast
|
||
"""
|
||
number_of_injuries = Injury.objects.filter(gymnast=gymnast_id).count()
|
||
start_date = pendulum.now().date().subtract(months=2)
|
||
injury_list = Injury.objects.filter(
|
||
gymnast=gymnast_id, date__gte=start_date
|
||
).order_by("date")
|
||
wellbeing_list = WellBeing.objects.filter(
|
||
gymnast=gymnast_id, date__gte=start_date
|
||
).order_by("date")
|
||
height_weight_list = HeightWeight.objects.filter(
|
||
gymnast=gymnast_id, date__gte=start_date
|
||
)
|
||
|
||
if height_weight_list.count() <= 3:
|
||
height_weight_list = HeightWeight.objects.filter(gymnast=gymnast_id).order_by("-date")[:5][::-1]
|
||
|
||
context = {
|
||
"injury_list": injury_list,
|
||
"number_of_injuries": number_of_injuries,
|
||
"wellbeing_list": wellbeing_list,
|
||
"height_weight_list": height_weight_list,
|
||
"gymnast_id": gymnast_id,
|
||
}
|
||
return render(
|
||
request, "gymnasts/tabs/tab_height_weight_wellbeing_injury.html", context
|
||
)
|
||
|
||
|
||
@login_required
|
||
@require_http_methods(["GET"])
|
||
def gymnast_display_scores_chrono(request, gymnast_id):
|
||
"""
|
||
Selectionne tous les scores réalisés par le gymnaste.
|
||
Afin de ne pas avoir plusieurs valeurs pour une même date, nous faisons une
|
||
moyenne par date.
|
||
|
||
Args:
|
||
gymnast_id (int) identifiant du gymnast
|
||
|
||
TODO: limiter les intensités aux X derniers records/semaines/mois ?
|
||
"""
|
||
start_date = pendulum.now().date().subtract(months=6)
|
||
intensity_list = Intensity.objects.filter(gymnast=gymnast_id).order_by("date")
|
||
chrono_list = Chrono.objects.filter(
|
||
gymnast=gymnast_id, date__gte=start_date
|
||
).order_by("date")
|
||
base_queryset = chrono_list.values("date").annotate(score_avg=Avg("tof"))
|
||
|
||
context = {
|
||
"intensity_list": intensity_list,
|
||
"chrono_list": chrono_list,
|
||
"chrono_10c": base_queryset.filter(chrono_type=0),
|
||
"chrono_r1": base_queryset.filter(chrono_type=1),
|
||
"chrono_r2": base_queryset.filter(chrono_type=2),
|
||
"chrono_rf": base_queryset.filter(chrono_type=3),
|
||
"gymnast_id": gymnast_id,
|
||
}
|
||
return render(request, "gymnasts/tabs/tab_intensity_chronos_programs.html", context)
|
||
|
||
|
||
@login_required
|
||
@require_http_methods(["GET"])
|
||
def gymnast_display_routine_statistics(request, gymnast_id):
|
||
"""
|
||
Tag affichant les statistiques et informations de séries d'un gymnaste.
|
||
|
||
Args:
|
||
gymnast_id (int) identifiant de la classe Gymnast
|
||
"""
|
||
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
|
||
ghr_list = gymnast.has_routine.prefetch_related("routine").filter(
|
||
Q(date_end__gte=date.today()) | Q(date_end__isnull=True)
|
||
)
|
||
|
||
has_routine_1 = ghr_list.filter(routine_type=1)
|
||
has_routine_2 = ghr_list.filter(routine_type=2)
|
||
has_routine_3 = ghr_list.filter(routine_type=3)
|
||
has_routine_4 = ghr_list.filter(routine_type=4)
|
||
has_routine_5 = ghr_list.filter(routine_type=5)
|
||
|
||
routine_one_done_list = NumberOfRoutineDone.objects.filter(
|
||
gymnast=gymnast_id, routine_type=1
|
||
).order_by("date")
|
||
routine_two_done_list = NumberOfRoutineDone.objects.filter(
|
||
gymnast=gymnast_id, routine_type=2
|
||
).order_by("date")
|
||
score_list = Point.objects.filter(gymnast=gymnast_id).order_by("-event__date_begin")
|
||
|
||
context = {
|
||
"ghr_list": ghr_list,
|
||
"has_routine_1": has_routine_1,
|
||
"has_routine_2": has_routine_2,
|
||
"has_routine_3": has_routine_3,
|
||
"has_routine_4": has_routine_4,
|
||
"has_routine_5": has_routine_5,
|
||
"routine_one_done_list": routine_one_done_list,
|
||
"routine_two_done_list": routine_two_done_list,
|
||
"score_list": score_list,
|
||
"score_routine1_list": score_list.filter(routine_type=1),
|
||
"score_routine2_list": score_list.filter(routine_type=2),
|
||
"score_routine3_list": score_list.filter(routine_type=3),
|
||
"gymnast_id": gymnast_id,
|
||
}
|
||
return render(request, "gymnasts/tabs/tab_routines_scores.html", context)
|
||
|
||
|
||
@login_required
|
||
@require_http_methods(["GET", "POST"])
|
||
def link_routine_to_gymnast(request, gymnast_id=None):
|
||
"""Lie une série à un gymnaste
|
||
|
||
Args:
|
||
gymnast_id (int) identifiant de la classe Gymnast
|
||
"""
|
||
|
||
if gymnast_id:
|
||
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
|
||
data = {
|
||
"gymnast": gymnast_id,
|
||
"gymnast_related": str(gymnast),
|
||
"date_end": None,
|
||
}
|
||
else:
|
||
gymnast = None
|
||
data = {"date_end": None}
|
||
|
||
if request.method == "POST":
|
||
form = GymnastHasRoutineForm(request.POST)
|
||
|
||
if form.is_valid():
|
||
form.save()
|
||
if not gymnast:
|
||
gymnast = get_object_or_404(Gymnast, pk=form.cleaned_data["gymnast"])
|
||
|
||
receiver = []
|
||
functionality = ContentType.objects.get(model="gymnasthasroutine")
|
||
for notification in gymnast.notifications.filter(
|
||
functionality=functionality
|
||
):
|
||
receiver.append(notification.user.email)
|
||
|
||
send_mail(
|
||
"Nouvelle série",
|
||
"Une nouvelle série vous a été associée.",
|
||
settings.EMAIL_HOST_USER,
|
||
[gymnast.user.email, gymnast.email_trainer].append(receiver),
|
||
fail_silently=False,
|
||
html_message="""<p>Bonjour,</p>
|
||
<p>Une nouvelle série vous a été associée.</p><br />
|
||
<p>Excellente journée</p><p>Jarvis</p>""",
|
||
)
|
||
|
||
return HttpResponseRedirect(
|
||
reverse("gymnast_details_tab", args=(gymnast_id, "routine"))
|
||
)
|
||
|
||
else:
|
||
form = GymnastHasRoutineForm(instance=gymnast, initial=data)
|
||
|
||
context = {"form": form, "gymnast_id": gymnast_id}
|
||
return render(request, "gymnasts/link_to_routine.html", context)
|
||
|
||
|
||
@login_required
|
||
@require_http_methods(["GET", "POST"])
|
||
def gymnast_create_or_update(request, gymnast_id=None):
|
||
"""
|
||
Formulaire de création et modification d'un gymnaste.
|
||
|
||
Args:
|
||
gymnast_id (int) Identifiant de la classe Gymnast
|
||
"""
|
||
|
||
if gymnast_id and (
|
||
request.user.is_superuser
|
||
or (
|
||
request.session.has_key("available_gymnast")
|
||
and gymnast_id in request.session["available_gymnast"]
|
||
)
|
||
):
|
||
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
|
||
else:
|
||
gymnast = None
|
||
|
||
if request.method == "POST":
|
||
gymnast_form = GymnastForm(request.POST, instance=gymnast)
|
||
|
||
if gymnast_form.is_valid():
|
||
gymnast = gymnast_form.save()
|
||
form_data = request.POST.dict()
|
||
user_data = {}
|
||
user_data["first_name"] = form_data["first_name"]
|
||
user_data["last_name"] = form_data["last_name"]
|
||
user_data["username"] = (
|
||
form_data["first_name"].lower() + "_" + form_data["last_name"].lower()
|
||
)
|
||
user_data["email"] = form_data["email"].lower()
|
||
user_data["is_active"] = True
|
||
user_form = UserForm(user_data, instance=gymnast.user)
|
||
|
||
if user_form.is_valid():
|
||
user = user_form.save()
|
||
|
||
gymnast_group, _ = Group.objects.get_or_create(name="gymnast")
|
||
user.groups.add(gymnast_group)
|
||
gymnast.user = user
|
||
gymnast.save()
|
||
|
||
# if not user.has_usable_password():
|
||
# user.set_password(gymnast.last_name.lower() + _ + str(gymnast.birthdate)[-2:])
|
||
|
||
return HttpResponseRedirect(reverse("gymnast_details", args=(gymnast.pk,)))
|
||
|
||
return render(request, "gymnasts/create.html", {"form": gymnast_form})
|
||
|
||
form = GymnastForm(instance=gymnast)
|
||
context = {"form": form, "gymnast_id": gymnast_id}
|
||
return render(request, "gymnasts/create.html", context)
|
||
|
||
|
||
@login_required
|
||
@require_http_methods(["GET", "POST"])
|
||
def gymnast_trainers_listing(request, gymnast_id):
|
||
""" Liste tous les user """
|
||
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
|
||
user_list = User.objects.filter(is_active=True, groups__name="trainer").order_by('first_name', 'last_name')
|
||
rows = gymnast.trainers.values("id")
|
||
already_linked = [row["id"] for row in rows]
|
||
context = {
|
||
"gymnast": gymnast,
|
||
"already_linked": already_linked,
|
||
"trainers_list": user_list,
|
||
}
|
||
return render(request, "gymnasts/link_trainer_to_gymnast.html", context)
|
||
|
||
|
||
@login_required
|
||
@require_http_methods(["POST"])
|
||
def link_trainer_to_gymnast(request, gymnast_id):
|
||
""" Lie un entraîneur à un gymnaste. """
|
||
trainer_id = request.POST.get("trainer_id", None)
|
||
|
||
if gymnast_id and trainer_id:
|
||
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
|
||
gymnast.trainers.add(get_object_or_404(User, pk=trainer_id))
|
||
return HttpResponse(status=200)
|
||
|
||
if gymnast_id:
|
||
print("Error : can not link Gymnast and Trainer. Missing trainer id.")
|
||
else:
|
||
print("Error : can not link Gymnast and Trainer. Missing gymnast id.")
|
||
return HttpResponse(status=500)
|
||
|
||
|
||
@login_required
|
||
@require_http_methods(["POST"])
|
||
def unlink_trainer_to_gymnast(request, gymnast_id):
|
||
""" Délie un entraîneur d'un gymanste. """
|
||
trainer_id = request.POST.get("trainer_id", None)
|
||
|
||
if gymnast_id and trainer_id:
|
||
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
|
||
gymnast.trainers.remove(get_object_or_404(User, pk=trainer_id))
|
||
return HttpResponse(status=200)
|
||
|
||
if gymnast_id:
|
||
print("Error : can not link Gymnast and Trainer. Missing trainer id.")
|
||
else:
|
||
print("Error : can not link Gymnast and Trainer. Missing gymnast id.")
|
||
return HttpResponse(status=500)
|
||
|
||
|
||
@login_required
|
||
@require_http_methods(["GET"])
|
||
def gymnast_display_skill(request, gymnast_id):
|
||
"""Tag affichant les statistiques de skill 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, ...)
|
||
|
||
Il y a 4 étapes dans l'apprentissage :
|
||
- skill confusion syndrom "No" - 0
|
||
- avec aide (tapis, manip, …) "with help" - 1
|
||
- sans aide () "without help" - 2
|
||
- enchaîné (un saut avec et après) "chained" - 3
|
||
- maitrisé (dans n'importe quelle condition) "masterised" - 4
|
||
|
||
Args:
|
||
gymnast_id (int) Identifiant de la classe Gymnast
|
||
"""
|
||
|
||
context = {}
|
||
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
|
||
gymnast_nb_known_skills = gymnast.known_skills.distinct("skill").count()
|
||
context = gymnast.get_informations_from_type("level")
|
||
context.update(gymnast.get_informations_from_type("rank"))
|
||
|
||
planned_skill = (
|
||
Plan.objects.filter(gymnast=gymnast.id, educative__in=Skill.objects.all())
|
||
.select_related("educative", "educative__skill")
|
||
.order_by("-date", "educative__long_label")
|
||
)
|
||
|
||
context["planned_skill"] = planned_skill
|
||
|
||
if gymnast.gender:
|
||
context["skill_by_age"] = Skill.objects.filter(
|
||
age_girl_masterised__lte=gymnast.age
|
||
).exclude(known_by__gymnast=gymnast.id)
|
||
else:
|
||
context["skill_by_age"] = Skill.objects.filter(
|
||
age_boy_masterised__lte=gymnast.age
|
||
).exclude(known_by__gymnast=gymnast.id)
|
||
|
||
learned_skills = (
|
||
LearnedSkill.objects.filter(gymnast=gymnast_id)
|
||
.select_related("skill")
|
||
.order_by("skill_id", "-date")
|
||
.distinct("skill_id")
|
||
)
|
||
|
||
# skill_learned_by_phase = [[]] * 5
|
||
# print(skill_learned_by_phase)
|
||
confused_skill = []
|
||
skill_whith_help = []
|
||
skill_without_help = []
|
||
skill_chained = []
|
||
skill_masterised = []
|
||
for learned_skill in learned_skills:
|
||
# print('Add skill for ' + str(learned_skill.learning_step) + ' phase')
|
||
# skill_learned_by_phase[learned_skill.learning_step].append(learned_skill.skill)
|
||
if learned_skill.learning_step == 0:
|
||
confused_skill.append(learned_skill.skill)
|
||
elif learned_skill.learning_step == 1:
|
||
skill_whith_help.append(learned_skill.skill)
|
||
elif learned_skill.learning_step == 2:
|
||
skill_without_help.append(learned_skill.skill)
|
||
elif learned_skill.learning_step == 3:
|
||
skill_chained.append(learned_skill.skill)
|
||
else:
|
||
skill_masterised.append(learned_skill.skill)
|
||
|
||
# for i in range(0,4):
|
||
# print(skill_learned_by_phase[i])
|
||
|
||
# context["confused_skill"] = skill_learned_by_phase[0]
|
||
# context["skill_whith_help"] = skill_learned_by_phase[1]
|
||
# context["skill_without_help"] = skill_learned_by_phase[2]
|
||
# context["skill_chained"] = skill_learned_by_phase[3]
|
||
# context["skill_masterised"] = skill_learned_by_phase[4]
|
||
context["confused_skill"] = confused_skill
|
||
context["skill_whith_help"] = skill_whith_help
|
||
context["skill_without_help"] = skill_without_help
|
||
context["skill_chained"] = skill_chained
|
||
context["skill_masterised"] = skill_masterised
|
||
|
||
context["gymnast"] = gymnast
|
||
context["gymnast_nb_known_skills"] = gymnast_nb_known_skills
|
||
context["user_is_trainer"] = request.user.groups.filter(
|
||
name="trainer"
|
||
).exists() # TODO: utiliser les {{ perms }}
|
||
return render(request, "gymnasts/tabs/tab_skills.html", context)
|