Jarvis/jarvis/followup/views_chrono.py

487 lines
15 KiB
Python
Raw Normal View History

from datetime import date, datetime
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.db.models import Min, Avg, Max, Sum
from django.urls import reverse
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.mail import send_mail
import pendulum
from jarvis.core.models import Email
from jarvis.people.models import Gymnast
from jarvis.tools.date_week_transition import from_date_to_week_number
from jarvis.tools.models import Season
from .models import (
Chrono,
ChronoDetails,
)
from .forms import ChronoForm
from .models import (
SCORE_TYPE_CHOICE,
CHRONO_TYPE_CHOICE,
)
from .email_vars import MAIL_HEADER, MAIL_FOOTER
User = get_user_model()
@login_required
@require_http_methods(["GET"])
def jump_chrono_details(request, chrono_id):
"""Récupère toutes les informations détaillées d'un chrono. La fonction en profite pour
recalculer le total et s'assure que cela correspond à la valeur stockée dans le model
Chrono.
Args:
chrono_id (int) identifiant chrono
QTF : Est-ce que je ne devrais pas faire un prefetch_related sur mon objet chrono pour
optimiser mon affichage ?
chrono = Chrono.object.get(pk=chrono_id).prefetch_related('chrono_details') ?
"""
chrono = get_object_or_404(Chrono, pk=chrono_id)
if not request.user.is_superuser and (
request.session.has_key("available_gymnast")
and chrono.gymnast.id not in request.session["available_gymnast"]
):
return chrono_listing(request)
sum_value = chrono.details.all().aggregate(total=Sum("value"))
if chrono.score != sum_value["total"]:
chrono.score = sum_value["total"]
if chrono.score_type == 0:
chrono.tof = Chrono.compute_tof(sum_value["total"])
chrono.save()
mean_value = chrono.details.all().aggregate(mean=Avg("value"))["mean"]
tmp_min_value = chrono.details.all().aggregate(min=Min("value"))["min"]
tmp_max_value = chrono.details.all().aggregate(max=Max("value"))["max"]
chart_min_value = mean_value - (tmp_min_value / 20)
chart_max_value = mean_value - (tmp_max_value / 20)
context = {
"chrono": chrono,
"mean_value": mean_value,
"chart_min_value": chart_min_value,
"chart_max_value": chart_max_value,
}
return render(request, "chronos/details.html", context)
@login_required
@require_http_methods(["GET"])
def average_jump_chrono_details_for_gymnast(
request, gymnast_id, routine_type=1, season=None, week_number=1
):
"""Récupère tout les chronos entre deux date pour un gymnaste et un type de série
Args:
gymnast_id (int) Identifiant d'un gymnaste
routine_type (int) Type de série (cf. jarvis/followup/models.py > ROUTINE_CHOICE)
season (string) Saison sous forme "xxxx-xxxy"
week_number (int) numéro de semaine (1, , 52)
"""
if season is None:
today = pendulum.now().date()
season, week_number = from_date_to_week_number(today)
else:
season = Season(season).label
week_number = min(week_number, 52)
week_number = max(week_number, 1)
return average_jump_chrono_details_for_season_and_week(
request,
gymnast_id,
routine_type,
season,
week_number,
)
@require_http_methods(["POST"])
def remove_jump_chrono_value(request):
"""
Recoit trois informations permettant de supprimer le chrono d'un saut à un chrono.
"""
chrono_id = request.POST.get("chrono_id", None)
order = request.POST.get("order", None)
chrono = get_object_or_404(Chrono, pk=chrono_id)
try:
ChronoDetails.objects.filter(chrono=chrono, order=order).delete()
except Exception:
return HttpResponse(409)
return HttpResponse(200)
@require_http_methods(["POST"])
def add_jump_chrono_value(request):
"""
Recoit trois informations permettant d'ajouter le chrono d'un saut à un chrono.
"""
chrono_id = request.POST.get("chrono_id", None)
order = request.POST.get("order", None)
value = request.POST.get("value", None)
chrono = get_object_or_404(Chrono, pk=chrono_id)
row, created = ChronoDetails.objects.get_or_create(
chrono=chrono, order=order, value=value
)
if created:
return HttpResponse(200, (row, created)) # devrait être un 201
return HttpResponse(400, (row, created))
@login_required
@require_http_methods(["GET"])
def jump_chrono_values_create_or_update(request, chrono_id):
"""
Ajoute des scores de saut à un chrono.
Args:
chrono_id (int) identifiant chrono
"""
chrono = get_object_or_404(Chrono, pk=chrono_id)
jump_list = chrono.details.all()
number_of_jump = jump_list.count()
context = {
"chrono": chrono,
"jump_list": jump_list,
"number_of_jump": number_of_jump,
"score_type": chrono.score_type,
}
return render(request, "chronos/add_details.html", context)
@login_required
@require_http_methods(["GET"])
def average_jump_chrono_details_between_two_date(
request, gymnast_id, routine_type=1, date_begin=None, date_end=None
):
"""Récupère tout les chronos entre deux date pour un gymnaste et un type de série
Args:
gymnast_id (int) Identifiant d'un gymnaste
routine_type (int) type de série (cf. jarvis/followup/models.py > ROUTINE_CHOICE)
date_begin (date) date de début
date_end (date) date de fin
QTF : le cast en date devrait être dans un try mais comment gérer correctement l'erreur - si
erreur il y a ?
"""
if date_end:
try:
date_end = datetime.strptime(date_end, "%Y-%m-%d").date()
except (ValueError, TypeError):
date_end = pendulum.now().date()
else:
date_end = pendulum.now().date()
if date_begin:
try:
date_begin = datetime.strptime(date_begin, "%Y-%m-%d").date()
except (ValueError, TypeError):
date_begin = datetime(date_end.year, 9, 1)
else:
date_begin = datetime(date_end.year, 9, 1)
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
stat_values = (
ChronoDetails.objects.filter(
chrono__gymnast=gymnast_id,
chrono__chrono_type=routine_type,
chrono__date__gte=date_begin,
chrono__date__lte=date_end,
)
.values("order")
.annotate(
avg_score=Avg("value"), max_score=Max("value"), min_score=Min("value")
)
.order_by("order")
)
chrono_list = Chrono.objects.filter(
gymnast=gymnast_id,
date__gte=date_begin,
date__lte=date_end,
chrono_type=routine_type,
)
context = {
"gymnast": gymnast,
"date_begin": date_begin,
"date_end": date_end,
"chrono_list": chrono_list,
"stat_values": stat_values,
}
return render(request, "chronos/list_details.html", context)
@require_http_methods(["GET"])
def get_chrono_detail_distinct_season(request, gymnast_id):
"""Récupère toutes les saisons pour lesquelles le gymnaste a des chronos détaillés.
Args:
gymnast_id (int) Identifiant d'un gymnaste
"""
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
season_list = list(
gymnast.chronos.values_list("season", flat=True)
.distinct("season")
.order_by("season")
)
return JsonResponse(season_list, safe=False)
@require_http_methods(["GET"])
def get_chrono_detail_distinct_weeknumber_for_season(request, gymnast_id, season):
"""Récupère toutes les week_number pour lesquelles le gymnaste a des chronos détaillés au cours
d'une saison.
Args:
gymnast_id (int) Identifiant d'un gymnaste
season (string) Season
"""
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
weeknumber_list = list(
gymnast.chronos.values_list("week_number", flat=True)
.filter(season=season)
.distinct("week_number")
.order_by("week_number")
)
return JsonResponse(weeknumber_list, safe=False)
@require_http_methods(["GET"])
def get_average_jump_chrono_details_for_season_and_week(
request, gymnast_id, routine_type, season, week_number
):
"""Récupère tout les chronos moyen par saut pour une saison & semaine d'un gymnaste
Args:
gymnast_id (int) Identifiant d'un gymnaste
routine_type (int) Type de série (cf. jarvis/followup/models.py > ROUTINE_CHOICE)
season (string) Season
week_number (int) Numero de la semaine
"""
stat_values = list(
ChronoDetails.objects.filter(
chrono__gymnast=gymnast_id,
chrono__chrono_type=routine_type,
chrono__season=season,
chrono__week_number=week_number,
)
.values("order")
.annotate(avg_score=Avg("value"))
.order_by("order")
)
return JsonResponse(stat_values, safe=False)
@login_required
@require_http_methods(["GET"])
def average_jump_chrono_details_for_season_and_week(
request, gymnast_id, routine_type, season, week_number
):
"""Récupère tout les chronos entre deux date pour un gymnaste et un type de série
Args:
gymnast_id (int) Identifiant d'un gymnaste
routine_type (int) Type de série (cf. jarvis/followup/models.py > ROUTINE_CHOICE)
season (string) Season
week_number (int) Numero de la semaine
"""
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
stat_values = (
ChronoDetails.objects.filter(
chrono__gymnast=gymnast_id,
chrono__chrono_type=routine_type,
chrono__season=season,
chrono__week_number=week_number,
)
.values("order")
.annotate(
avg_score=Avg("value"), max_score=Max("value"), min_score=Min("value")
)
.order_by("order")
)
# print(stat_values)
distinct_season_list = (
gymnast.chronos.values_list("season", flat=True)
.distinct("season")
.order_by("season")
)
distinct_week_number_list = (
gymnast.chronos.values_list("week_number", flat=True)
.filter(season=season)
.distinct("week_number")
.order_by("week_number")
)
distinct_routine_type_list = (
gymnast.chronos.values_list("chrono_type", flat=True)
.distinct("chrono_type")
.order_by("chrono_type")
)
chrono_list = Chrono.objects.filter(
gymnast=gymnast_id,
season=season,
week_number=week_number,
chrono_type=routine_type,
)
# print(chrono_list)
context = {
"gymnast": gymnast,
"selected_season": season,
"selected_week_number": week_number,
"selected_routine_type": routine_type,
"chrono_list": chrono_list,
"stat_values": stat_values,
"distinct_season_list": distinct_season_list,
"distinct_week_number_list": distinct_week_number_list,
"distinct_routine_type_list": distinct_routine_type_list,
}
return render(request, "chronos/list_details.html", context)
@login_required
@require_http_methods(["GET"])
def chrono_listing(request, gymnast_id=None):
"""
Récupère les chronos des gymnastes autorisé(e)s.
Args:
gymnast_id (int) identifiant d'un gymnaste
"""
gymnast = None
if gymnast_id and (
request.user.is_superuser
or (
request.session.has_key("available_gymnast")
and gymnast_id in request.session["available_gymnast"]
)
):
gymnast = Gymnast.objects.get(pk=gymnast_id)
2024-04-11 12:18:14 +02:00
chrono_list = Chrono.objects.filter(gymnast=gymnast_id).order_by("date")
base_queryset = chrono_list.values("date").annotate(score_avg=Avg("tof"))
context = {
"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),
}
else:
if request.user.is_superuser:
chrono_list = Chrono.objects.all()
else:
chrono_list = Chrono.objects.filter(
gymnast__in=request.session["available_gymnast"]
)
2024-04-11 12:18:14 +02:00
context["chrono_list"] = chrono_list
context["gymnast"] = gymnast
return render(request, "chronos/list.html", context)
@login_required
@require_http_methods(["GET", "POST"])
def chrono_create_or_update(request, chrono_id=None, gymnast_id=None):
"""Création ou modification d'un chrono
Args:
chrono_id (int) identifiant d'un chrono
gymnast_id (int) identifiant d'un gymnaste
"""
if chrono_id:
chrono = get_object_or_404(Chrono, pk=chrono_id)
if not request.user.is_superuser and (
request.session.has_key("available_gymnast")
and chrono.gymnast.id not in request.session["available_gymnast"]
):
return chrono_listing(request)
data = {
"gymnast": chrono.gymnast.id,
"gymnast_related": str(chrono.gymnast),
}
else:
chrono = None
data = None
if gymnast_id is not None:
gymnast = get_object_or_404(Gymnast, pk=gymnast_id)
data = {"gymnast": gymnast_id, "gymnast_related": gymnast}
if request.method == "POST":
form = ChronoForm(request.POST, instance=chrono)
if form.is_valid():
new_chrono = form.save(commit=False)
if new_chrono.score_type == 1:
new_chrono.tof = new_chrono.score
else:
new_chrono.tof = Chrono.compute_tof(new_chrono.score)
new_chrono.save()
# notification
receivers = []
functionality = ContentType.objects.get(model="chrono")
for notification in new_chrono.gymnast.notifications.filter(
functionality=functionality
):
receivers.append(notification.user.email)
title = f"{new_chrono.gymnast} : Nouveau chrono"
body = f"""<p>Bonjour,</p><p>Nouveau chrono pour {new_chrono.gymnast} : {SCORE_TYPE_CHOICE[new_chrono.score_type][1]} {CHRONO_TYPE_CHOICE[new_chrono.chrono_type][1]} - {new_chrono.score}.</p>"""
Email.objects.create(
receivers=receivers,
title=title,
body=body,
)
send_mail(
title,
f"{new_chrono.gymnast} a enregistrer un nouveau chrono ({date})",
settings.EMAIL_HOST_USER,
receivers,
fail_silently=False,
html_message=body + MAIL_FOOTER,
)
return HttpResponseRedirect(
2024-04-11 16:29:15 +02:00
reverse("chrono_list_for_gymnast", args=(new_chrono.gymnast.id,))
)
return render(request, "chronos/create.html", {"form": form})
form = ChronoForm(instance=chrono, initial=data)
context = {"form": form, "chrono_id": chrono_id}
return render(request, "chronos/create.html", context)