from datetime import timedelta from weasyprint import HTML, CSS import pendulum from django.db.models import Max from django.conf import settings from django.db.models import Q, F, OuterRef, Subquery from django.shortcuts import render, get_object_or_404 from django.utils import timezone from django.contrib.auth import authenticate, login as auth_login, logout as auth_logout 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.template.loader import render_to_string from django.urls import reverse from django.contrib.auth import get_user_model from jarvis.objective.models import Routine from jarvis.profiles.models import Profile from jarvis.followup.models import ( Skill, Point, Chrono, WellBeing, Intensity, HeightWeight, LearnedSkill ) from jarvis.location.models import Place, Club from jarvis.people.models import Gymnast from jarvis.people.views import gymnast_details from jarvis.planning.models import Event from jarvis.tools.models import from_date_to_week_number from jarvis.tools.clean_name import clean_name from jarvis.tools.models import Season from .models import Citation User = get_user_model() def login(request): """Fonction d'authentifictation.""" if request.method == "POST": username = request.POST["login"] password = request.POST["password"] user = authenticate(username=username, password=password) if user is not None: if user.is_active: auth_login(request, user) try: profile = Profile.objects.get(user=user) request.session["profileid"] = profile.id request.session["template"] = profile.template_color request.session["sidebar"] = profile.sidebar_color request.session["is_sidebar_minified"] = profile.is_sidebar_minified except Profile.DoesNotExist: pass request.session["available_gymnast"] = [x.gymnast.id for x in user.can_view.all()] return HttpResponseRedirect(reverse("home")) context = {"message": "Account disabled."} else: context = {"message": "Wrong login/password."} else: context = {} return render(request, "login.html", context) @login_required @require_http_methods(["GET"]) def logout(request): """ Fonction de déconnexion """ auth_logout(request) return HttpResponseRedirect(reverse("login")) def next_birthdays(request, number_of_birthday): """ Renvoie la liste des `number_of_birthday` prochains anniversaires. """ birthday_list = sorted( Gymnast.objects.all(), key=lambda t: t.next_birthday_in_days )[:number_of_birthday] return birthday_list def gymnast_have_birthday(request): """ Renvoie la liste des gymnastes qui ont leur anniversaire aujourd'hui. """ today = pendulum.now().date() return Gymnast.objects.filter(birthdate__day=today.day, birthdate__month=today.month).values_list("id") # @lru_cache() # def get_last_updated_gymnasts(expiration_date): # ... # TODO: liste de mots pour souhaiter la bienvenue # TODO: véririer si c'est l'anniversaire de la personne qui est connectée. # TODO: check if gymnast have point # --------------------------- # 1. récupérer tous les évènements passés # 2. pour chaque event, vérifier que tous les gymnastes renseignés # dans les participants ont des points associés. # S'il n'y a pas de point, faire une alerte à l'utilisateur qui se connecte. # TODO: Check if gymnast have update # ----------------------------- # lister tous les gymnastes qui n'ont pas eu d'update depuis... 2 semaines ? # peut-être le paramètre (en jour) devrait être stocké en DB. # S'il n'y a pas d'update, faire une alerte à l'utilisateur qui se connecte. @login_required @require_http_methods(["GET"]) def home(request): """ Génère la page d'accueil du site basée sur la saison (si celle-ci est connue) """ today = timezone.now() # pendulum.now().date() _, week_number = from_date_to_week_number(today) context = { "week_number": week_number, "quote": Citation.objects.order_by("?").first(), "event_list": Event.objects.filter(date_begin__gte=today).order_by("date_begin")[:10], "nb_active_gymnast": Gymnast.objects.filter(is_active=True).count(), "nb_trainer": User.objects.filter(is_active=True, groups__name="trainer").count(), "nb_event": Event.objects.all().count(), "nb_skill": Skill.objects.all().count(), "nb_routine": Routine.objects.all().count(), "nb_score": Point.objects.all().count(), "nb_club": Club.objects.all().count(), "percentage_week": (week_number / 52) * 100, "birthday_list": next_birthdays(request, 10), } is_trainer = request.user.groups.filter(name="trainer").exists() if is_trainer: last_updated_gymnasts = set() last_updated_gymnasts.update( Gymnast.objects.filter(wellbeings__created_at__gt=request.user.last_login) ) last_updated_gymnasts.update( Gymnast.objects.filter(height_weight__date__gt=request.user.last_login) ) last_updated_gymnasts.update( Gymnast.objects.filter(intensities__date__gt=request.user.last_login) ) last_updated_gymnasts.update( Gymnast.objects.filter(points__created_at__gt=request.user.last_login) ) last_updated_gymnasts.update( Gymnast.objects.filter(chronos__created_at__gt=request.user.last_login) ) last_updated_gymnasts.update( Gymnast.objects.filter(injuries__created_at__gt=request.user.last_login) ) last_updated_gymnasts.update( Gymnast.objects.filter(known_skills__created_at__gt=request.user.last_login) ) season = Season() # Paramètre dans le fichier .env ? limit_date = today - timedelta(days=7) waiting_update_gymnast = ( Gymnast.objects.filter(season_informations__season=season) .exclude( Q(is_active=False) | Q(wellbeings__created_at__gte=limit_date) | Q(points__created_at__gte=limit_date) | Q(chronos__created_at__gte=limit_date) | Q(injuries__created_at__gte=limit_date) | Q(known_skills__created_at__gte=limit_date) ) .distinct() ) context["is_trainer"] = is_trainer context["last_updated_gymnasts"] = last_updated_gymnasts context["waiting_update_gymnast"] = waiting_update_gymnast else: todate = pendulum.now().date() birthday_list = gymnast_have_birthday(request) gymnast = get_object_or_404(Gymnast, pk=request.user.gymnast.id) context["gymnast"] = gymnast # if birthday_list is not None and gymnast.id in birthday_list: # print("C'est l'anniversaire du gymnaste !") # else: # print("Pas son annif…") wellbeing_last_record_date = WellBeing.objects.filter(gymnast=gymnast).values_list("date").order_by("-date").first() wellbeing_state = None if wellbeing_last_record_date: if todate.diff(wellbeing_last_record_date[0]).in_days() > 21: wellbeing_state = "danger bold" elif todate.diff(wellbeing_last_record_date[0]).in_days() > 14: wellbeing_state = "danger" elif todate.diff(wellbeing_last_record_date[0]).in_days() > 7: wellbeing_state = "warning" height_weight_last_record_date = HeightWeight.objects.filter(gymnast=gymnast).values_list("date").order_by("-date").first() height_weight_state = None if height_weight_last_record_date: if todate.diff(height_weight_last_record_date[0]).in_days() > 21: height_weight_state = "danger bold" elif todate.diff(height_weight_last_record_date[0]).in_days() > 14: height_weight_state = "danger" elif todate.diff(height_weight_last_record_date[0]).in_days() > 7: height_weight_state = "warning" intensities_last_record_date = Intensity.objects.filter(gymnast=gymnast).values_list("date").order_by("-date").first() intensity_state = None if intensities_last_record_date: if todate.diff(intensities_last_record_date[0]).in_days() > 21: intensity_state = "danger bold" elif todate.diff(intensities_last_record_date[0]).in_days() > 14: intensity_state = "danger" elif todate.diff(intensities_last_record_date[0]).in_days() > 7: intensity_state = "warning" # points_last_update = Gymnast.objects.all().order_by("-date")[:1] chrono_last_record_date = Chrono.objects.filter(gymnast=gymnast).values_list("date").order_by("-date").first() chrono_state = None if chrono_last_record_date: if todate.diff(chrono_last_record_date[0]).in_days() > 21: chrono_state = "danger bold" elif todate.diff(chrono_last_record_date[0]).in_days() > 14: chrono_state = "danger" elif todate.diff(chrono_last_record_date[0]).in_days() > 7: chrono_state = "warning" known_skills_last_record_date = LearnedSkill.objects.filter(gymnast=gymnast).values_list("date").order_by("-date").first() known_skills_state = None if known_skills_last_record_date: if todate.diff(known_skills_last_record_date[0]).in_days() > 21: known_skills_state = "danger bold" elif todate.diff(known_skills_last_record_date[0]).in_days() > 14: known_skills_state = "danger" elif todate.diff(known_skills_last_record_date[0]).in_days() > 7: known_skills_state = "warning" context["wellbeing_last_record_date"] = wellbeing_last_record_date context["wellbeing_state"] = wellbeing_state context["height_weight_last_record_date"] = height_weight_last_record_date context["height_weight_state"] = height_weight_state context["intensities_last_record_date"] = intensities_last_record_date context["intensity_state"] = intensity_state # "points_last_update": points_last_update, context["chrono_last_record_date"] = chrono_last_record_date context["chrono_state"] = chrono_state context["known_skills_last_record_date"] = known_skills_last_record_date context["known_skills_state"] = known_skills_state return render(request, "dashboard/dashboard.html", context) @login_required @require_http_methods(["GET"]) def search(request): """ Recherche globale au travers de toutes les applications. """ pattern = request.GET.get("pattern", None) if pattern: name = clean_name(pattern) gymnast_list = Gymnast.objects.filter( Q(cleaned_last_name__icontains=name) | Q(cleaned_first_name__icontains=name) ) if gymnast_list.count() == 1: gymnast = gymnast_list.first() return gymnast_details(request, gymnast.id) else: skill_list = Skill.objects.filter( Q(long_label__icontains=pattern) | Q(short_label__icontains=pattern) | Q(notation__icontains=pattern) ) event_list = Event.objects.filter( Q(name__icontains=pattern) | Q(place__name__icontains=pattern) ) place_list = Place.objects.filter( Q(name__icontains=pattern) | Q(city__icontains=pattern) ) club_list = Club.objects.filter( Q(name__icontains=pattern) | Q(place__name__icontains=pattern) | Q(place__city__icontains=pattern) ) context = { "gymnast_list": gymnast_list, "skill_list": skill_list, "event_list": event_list, "place_list": place_list, "club_list": club_list, "pattern": pattern, } else: context = {} return render(request, "search/results.html", context) @login_required @require_http_methods(["GET"]) def report_listing(request): """Liste des rapports disponibles""" return render(request, "reports/list.html", {}) @login_required @require_http_methods(["GET"]) def generate_best_straightjump_listing(request): """Va chercher le meilleur chrono pour chaque gymnaste et sa date de réalisation pour en générer un fichier PDF.""" date_begin = pendulum.now().date() season, week_number = from_date_to_week_number(date_begin) records_list = ( Gymnast.objects.values("last_name", "first_name") .annotate( score=Max("chronos__score"), date=Subquery( Chrono.objects.filter(gymnast=OuterRef("pk")) .order_by(F("score").desc(nulls_last=True)) .values("date")[:1] ), ) .order_by("-score") ) context = { "SITE_TITLE": settings.SITE_TITLE, "CLUB_NAME": settings.CLUB_NAME, "ADDRESS": settings.ADDRESS, "CITY": settings.CITY, "ZIP": settings.ZIP, "HEAD_COACH": settings.HEAD_COACH, "MOBILE_PHONE": settings.MOBILE_PHONE, "HEAD_COACH_EMAIL": settings.HEAD_COACH_EMAIL, "week_number": week_number, "today": date_begin, "records_list": records_list, } # return render(request, "reports/records_10.html", context) response = HttpResponse(content_type="application/pdf") response["Content-Disposition"] = "attachment; filename=report-top_straightjump.pdf" html = render_to_string("reports/records_10.html", context) # font_config = FontConfiguration() HTML(string=html, base_url=request.build_absolute_uri()).write_pdf( response, stylesheets=[ CSS(settings.STATICFILES_DIRS[0] + "/css/a4_paper.css"), CSS(settings.STATICFILES_DIRS[0] + "/css/black-dashboard_report.css"), CSS(settings.STATICFILES_DIRS[0] + "/css/font_awesome_all_5.15.3.css"), ], ) # , font_config=font_config) return response @login_required @require_http_methods(["GET"]) def help_view(request): """ Appel à la vue d'aide. """ return render(request, "help/help.html", {})