import datetime from flask import g, request, render_template from flask import abort, url_for from flask_login import current_user from app import db from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.decorators import ( scodoc, permission_required, ) from app.models import ( FormSemestre, Identite, ScoDocSiteConfig, Assiduite, Departement, FormSemestreInscription, ) from app.views import assiduites_bp as bp from app.views import ScoData # --------------- from app.scodoc.sco_permissions import Permission from app.scodoc import html_sco_header from app.scodoc import safehtml from app.scodoc import sco_moduleimpl from app.scodoc import sco_preferences from app.scodoc import sco_groups_view from app.scodoc import sco_etud from app.scodoc import sco_find_etud from app.scodoc import sco_assiduites as scass from app.scodoc import sco_utils as scu from app.scodoc.sco_exceptions import ScoValueError from app.tables.visu_assiduites import TableAssi, etuds_sorted_from_ids CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS # --- UTILS --- class HTMLElement: """""" class HTMLElement: """Représentation d'un HTMLElement version Python""" def __init__(self, tag: str, *attr, **kattr) -> None: self.tag: str = tag self.children: list[HTMLElement] = [] self.self_close: bool = kattr.get("self_close", False) self.text_content: str = kattr.get("text_content", "") self.key_attributes: dict[str, any] = kattr self.attributes: list[str] = list(attr) def add(self, *child: HTMLElement) -> None: """add child element to self""" for kid in child: self.children.append(kid) def remove(self, child: HTMLElement) -> None: """Remove child element from self""" if child in self.children: self.children.remove(child) def __str__(self) -> str: attr: list[str] = self.attributes for att, val in self.key_attributes.items(): if att in ("self_close", "text_content"): continue if att != "cls": attr.append(f'{att}="{val}"') else: attr.append(f'class="{val}"') if not self.self_close: head: str = f"<{self.tag} {' '.join(attr)}>{self.text_content}" body: str = "\n".join(map(str, self.children)) foot: str = f"" return head + body + foot return f"<{self.tag} {' '.join(attr)}/>" def __add__(self, other: str): return str(self) + other def __radd__(self, other: str): return other + str(self) class HTMLStringElement(HTMLElement): """Utilisation d'une chaine de caracètres pour représenter un element""" def __init__(self, text: str) -> None: self.text: str = text HTMLElement.__init__(self, "textnode") def __str__(self) -> str: return self.text class HTMLBuilder: def __init__(self, *content: HTMLElement or str) -> None: self.content: list[HTMLElement or str] = list(content) def add(self, *element: HTMLElement or str): self.content.extend(element) def remove(self, element: HTMLElement or str): if element in self.content: self.content.remove(element) def __str__(self) -> str: return "\n".join(map(str, self.content)) def build(self) -> str: return self.__str__() # -------------------------------------------------------------------- # # Assiduités (/ScoDoc//Scolarite/Assiduites/...) # # -------------------------------------------------------------------- @bp.route("/") @bp.route("/index_html") @scodoc @permission_required(Permission.ScoAbsChange) def index_html(): """Gestionnaire assiduités, page principale""" H = [ html_sco_header.sco_header( page_title="Saisie des assiduités", javascripts=[ "js/assiduites.js", "libjs/moment.new.min.js", "libjs/moment-timezone.js", ], cssstyles=[ "css/assiduites.css", ], ), """

Traitement des assiduités

Pour saisir des assiduités ou consulter les états, il est recommandé par passer par le semestre concerné (saisie par jour ou saisie différée).

""", ] H.append( """

Pour signaler, annuler ou justifier une assiduité pour un seul étudiant, choisissez d'abord le concerné:

""" ) H.append(sco_find_etud.form_search_etud()) # if current_user.has_permission( # Permission.ScoAbsChange # ) and sco_preferences.get_preference("handle_billets_abs"): # H.append( # f""" #

Billets d'absence

# # """ # ) H.append( render_template( "assiduites/pages/bilan_dept.j2", dept_id=g.scodoc_dept_id, annee=scu.annee_scolaire(), ), ) H.append(html_sco_header.sco_footer()) return "\n".join(H) @bp.route("/SignaleAssiduiteEtud") @scodoc @permission_required(Permission.ScoAbsChange) def signal_assiduites_etud(): """ signal_assiduites_etud Saisie de l'assiduité d'un étudiant Args: etudid (int): l'identifiant de l'étudiant Returns: str: l'html généré """ etudid = request.args.get("etudid", -1) etud: Identite = Identite.query.get_or_404(etudid) if etud.dept_id != g.scodoc_dept_id: abort(404, "étudiant inexistant dans ce département") header: str = html_sco_header.sco_header( page_title="Saisie Assiduités", init_qtip=True, javascripts=[ "js/assiduites.js", "libjs/moment.new.min.js", "libjs/moment-timezone.js", ], cssstyles=[ "css/assiduites.css", ], ) # Gestion des horaires (journée, matin, soir) morning = get_time("assi_morning_time", "08:00:00") lunch = get_time("assi_lunch_time", "13:00:00") afternoon = get_time("assi_afternoon_time", "18:00:00") select = """ """ return HTMLBuilder( header, _mini_timeline(), render_template( "assiduites/pages/signal_assiduites_etud.j2", sco=ScoData(etud), date=datetime.date.today().isoformat(), morning=morning, lunch=lunch, timeline=_timeline(), afternoon=afternoon, nonworkdays=_non_work_days(), forcer_module=sco_preferences.get_preference( "forcer_module", dept_id=g.scodoc_dept_id ), moduleimpl_select=_dynamic_module_selector(), diff=_differee( etudiants=[sco_etud.get_etud_info(etudid=etud.etudid, filled=True)[0]], moduleimpl_select=select, ), ), ).build() @bp.route("/ListeAssiduitesEtud") @scodoc @permission_required(Permission.ScoView) def liste_assiduites_etud(): """ liste_assiduites_etud Affichage de toutes les assiduites et justificatifs d'un etudiant Args: etudid (int): l'identifiant de l'étudiant Returns: str: l'html généré """ etudid = request.args.get("etudid", -1) etud: Identite = Identite.query.get_or_404(etudid) if etud.dept_id != g.scodoc_dept_id: abort(404, "étudiant inexistant dans ce département") header: str = html_sco_header.sco_header( page_title="Liste des assiduités", init_qtip=True, javascripts=[ "js/assiduites.js", "libjs/moment.new.min.js", "libjs/moment-timezone.js", ], cssstyles=CSSSTYLES + [ "css/assiduites.css", ], ) return HTMLBuilder( header, render_template( "assiduites/pages/liste_assiduites.j2", sco=ScoData(etud), date=datetime.date.today().isoformat(), ), ).build() @bp.route("/BilanEtud") @scodoc @permission_required(Permission.ScoView) def bilan_etud(): """ bilan_etud Affichage de toutes les assiduites et justificatifs d'un etudiant Args: etudid (int): l'identifiant de l'étudiant Returns: str: l'html généré """ etudid = request.args.get("etudid", -1) etud: Identite = Identite.query.get_or_404(etudid) if etud.dept_id != g.scodoc_dept_id: abort(404, "étudiant inexistant dans ce département") header: str = html_sco_header.sco_header( page_title="Bilan de l'assiduité étudiante", init_qtip=True, javascripts=[ "js/assiduites.js", "libjs/moment.new.min.js", "libjs/moment-timezone.js", ], cssstyles=CSSSTYLES + [ "css/assiduites.css", ], ) date_debut: str = f"{scu.annee_scolaire()}-09-01" date_fin: str = f"{scu.annee_scolaire()+1}-06-30" assi_metric = scu.translate_assiduites_metric( sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id), ) return HTMLBuilder( header, render_template( "assiduites/pages/bilan_etud.j2", sco=ScoData(etud), date_debut=date_debut, date_fin=date_fin, assi_metric=assi_metric, assi_seuil=_get_seuil(), ), ).build() @bp.route("/AjoutJustificatifEtud") @scodoc @permission_required(Permission.ScoAbsChange) def ajout_justificatif_etud(): """ ajout_justificatif_etud : Affichage et création/modification des justificatifs de l'étudiant Args: etudid (int): l'identifiant de l'étudiant Returns: str: l'html généré """ etudid = request.args.get("etudid", -1) etud: Identite = Identite.query.get_or_404(etudid) if etud.dept_id != g.scodoc_dept_id: abort(404, "étudiant inexistant dans ce département") header: str = html_sco_header.sco_header( page_title="Justificatifs", init_qtip=True, javascripts=[ "js/assiduites.js", "libjs/moment.new.min.js", "libjs/moment-timezone.js", ], cssstyles=CSSSTYLES + [ "css/assiduites.css", ], ) return HTMLBuilder( header, render_template( "assiduites/pages/ajout_justificatif.j2", sco=ScoData(etud), ), ).build() @bp.route("/CalendrierAssiduitesEtud") @scodoc @permission_required(Permission.ScoView) def calendrier_etud(): """ calendrier_etud : Affichage d'un calendrier des assiduités de l'étudiant Args: etudid (int): l'identifiant de l'étudiant Returns: str: l'html généré """ etudid = request.args.get("etudid", -1) etud: Identite = Identite.query.get_or_404(etudid) if etud.dept_id != g.scodoc_dept_id: abort(404, "étudiant inexistant dans ce département") header: str = html_sco_header.sco_header( page_title="Calendrier des Assiduités", init_qtip=True, javascripts=[ "js/assiduites.js", "libjs/moment.new.min.js", "libjs/moment-timezone.js", ], cssstyles=CSSSTYLES + [ "css/assiduites.css", ], ) annees: list[int] = sorted( [ins.formsemestre.date_debut.year for ins in etud.formsemestre_inscriptions], reverse=True, ) annees_str: str = "[" for ann in annees: annees_str += f"{ann}," annees_str += "]" return HTMLBuilder( header, render_template( "assiduites/pages/calendrier.j2", sco=ScoData(etud), annee=scu.annee_scolaire(), nonworkdays=_non_work_days(), minitimeline=_mini_timeline(), annees=annees_str, ), ).build() @bp.route("/SignalAssiduiteGr") @scodoc @permission_required(Permission.ScoAbsChange) def signal_assiduites_group(): """ signal_assiduites_group Saisie des assiduités des groupes pour le jour donnée Returns: str: l'html généré """ formsemestre_id: int = request.args.get("formsemestre_id", -1) moduleimpl_id: int = request.args.get("moduleimpl_id") date: str = request.args.get("jour", datetime.date.today().isoformat()) group_ids: list[int] = request.args.get("group_ids", None) if group_ids is None: group_ids = [] else: group_ids = group_ids.split(",") map(str, group_ids) # Vérification du moduleimpl_id try: moduleimpl_id = int(moduleimpl_id) except (TypeError, ValueError): moduleimpl_id = None # Vérification du formsemestre_id try: formsemestre_id = int(formsemestre_id) except (TypeError, ValueError): formsemestre_id = None groups_infos = sco_groups_view.DisplayedGroupsInfos( group_ids, moduleimpl_id=moduleimpl_id, formsemestre_id=formsemestre_id ) if not groups_infos.members: return ( html_sco_header.sco_header(page_title="Saisie journalière des Assiduités") + "

Aucun étudiant !

" + html_sco_header.sco_footer() ) # --- URL DEFAULT --- base_url: str = f"SignalAssiduiteGr?date={date}&{groups_infos.groups_query_args}" # --- Filtrage par formsemestre --- formsemestre_id = groups_infos.formsemestre_id formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) if formsemestre.dept_id != g.scodoc_dept_id: abort(404, "groupes inexistants dans ce département") require_module = sco_preferences.get_preference( "abs_require_module", formsemestre_id ) etuds = [ sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0] for m in groups_infos.members ] # --- Vérification de la date --- real_date = scu.is_iso_formated(date, True).date() if real_date < formsemestre.date_debut: date = formsemestre.date_debut.isoformat() elif real_date > formsemestre.date_fin: date = formsemestre.date_fin.isoformat() # --- Restriction en fonction du moduleimpl_id --- if moduleimpl_id: mod_inscrits = { x["etudid"] for x in sco_moduleimpl.do_moduleimpl_inscription_list( moduleimpl_id=moduleimpl_id ) } etuds_inscrits_module = [e for e in etuds if e["etudid"] in mod_inscrits] if etuds_inscrits_module: etuds = etuds_inscrits_module else: # Si aucun etudiant n'est inscrit au module choisi... moduleimpl_id = None # --- Génération de l'HTML --- sem = formsemestre.to_dict() if groups_infos.tous_les_etuds_du_sem: gr_tit = "en" else: if len(groups_infos.group_ids) > 1: grp = "des groupes" else: grp = "du groupe" gr_tit = ( grp + ' ' + groups_infos.groups_titles + "" ) header: str = html_sco_header.sco_header( page_title="Saisie journalière des assiduités", init_qtip=True, javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS + [ # Voir fonctionnement JS "js/etud_info.js", "js/groups_view.js", "js/assiduites.js", "libjs/moment.new.min.js", "libjs/moment-timezone.js", ], cssstyles=CSSSTYLES + [ "css/assiduites.css", ], ) return HTMLBuilder( header, _mini_timeline(), render_template( "assiduites/pages/signal_assiduites_group.j2", gr_tit=gr_tit, sem=sem["titre_num"], date=date, formsemestre_id=formsemestre_id, grp=sco_groups_view.menu_groups_choice(groups_infos), moduleimpl_select=_module_selector(formsemestre, moduleimpl_id), timeline=_timeline(), nonworkdays=_non_work_days(), formsemestre_date_debut=str(formsemestre.date_debut), formsemestre_date_fin=str(formsemestre.date_fin), forcer_module=sco_preferences.get_preference( "forcer_module", formsemestre_id=formsemestre_id, dept_id=g.scodoc_dept_id, ), defdem=_get_etuds_dem_def(formsemestre), readonly="false", ), html_sco_header.sco_footer(), ).build() @bp.route("/VisuAssiduiteGr") @scodoc @permission_required(Permission.ScoView) def visu_assiduites_group(): """ signal_assiduites_group Saisie des assiduités des groupes pour le jour donnée Returns: str: l'html généré """ formsemestre_id: int = request.args.get("formsemestre_id", -1) moduleimpl_id: int = request.args.get("moduleimpl_id") date: str = request.args.get("jour", datetime.date.today().isoformat()) group_ids: list[int] = request.args.get("group_ids", None) if group_ids is None: group_ids = [] else: group_ids = group_ids.split(",") map(str, group_ids) # Vérification du moduleimpl_id try: moduleimpl_id = int(moduleimpl_id) except (TypeError, ValueError): moduleimpl_id = None # Vérification du formsemestre_id try: formsemestre_id = int(formsemestre_id) except (TypeError, ValueError): formsemestre_id = None groups_infos = sco_groups_view.DisplayedGroupsInfos( group_ids, moduleimpl_id=moduleimpl_id, formsemestre_id=formsemestre_id ) if not groups_infos.members: return ( html_sco_header.sco_header(page_title="Saisie journalière des Assiduités") + "

Aucun étudiant !

" + html_sco_header.sco_footer() ) # --- URL DEFAULT --- base_url: str = f"SignalAssiduiteGr?date={date}&{groups_infos.groups_query_args}" # --- Filtrage par formsemestre --- formsemestre_id = groups_infos.formsemestre_id formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) if formsemestre.dept_id != g.scodoc_dept_id: abort(404, "groupes inexistants dans ce département") require_module = sco_preferences.get_preference( "abs_require_module", formsemestre_id ) etuds = [ sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0] for m in groups_infos.members ] # --- Vérification de la date --- real_date = scu.is_iso_formated(date, True).date() if real_date < formsemestre.date_debut: date = formsemestre.date_debut.isoformat() elif real_date > formsemestre.date_fin: date = formsemestre.date_fin.isoformat() # --- Restriction en fonction du moduleimpl_id --- if moduleimpl_id: mod_inscrits = { x["etudid"] for x in sco_moduleimpl.do_moduleimpl_inscription_list( moduleimpl_id=moduleimpl_id ) } etuds_inscrits_module = [e for e in etuds if e["etudid"] in mod_inscrits] if etuds_inscrits_module: etuds = etuds_inscrits_module else: # Si aucun etudiant n'est inscrit au module choisi... moduleimpl_id = None # --- Génération de l'HTML --- sem = formsemestre.to_dict() if groups_infos.tous_les_etuds_du_sem: gr_tit = "en" else: if len(groups_infos.group_ids) > 1: grp = "des groupes" else: grp = "du groupe" gr_tit = ( grp + ' ' + groups_infos.groups_titles + "" ) header: str = html_sco_header.sco_header( page_title="Saisie journalière des assiduités", init_qtip=True, javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS + [ # Voir fonctionnement JS "js/etud_info.js", "js/abs_ajax.js", "js/groups_view.js", "js/assiduites.js", "libjs/moment.new.min.js", "libjs/moment-timezone.js", ], cssstyles=CSSSTYLES + [ "css/assiduites.css", ], ) return HTMLBuilder( header, _mini_timeline(), render_template( "assiduites/pages/signal_assiduites_group.j2", gr_tit=gr_tit, sem=sem["titre_num"], date=date, formsemestre_id=formsemestre_id, grp=sco_groups_view.menu_groups_choice(groups_infos), moduleimpl_select=_module_selector(formsemestre, moduleimpl_id), timeline=_timeline(), nonworkdays=_non_work_days(), formsemestre_date_debut=str(formsemestre.date_debut), formsemestre_date_fin=str(formsemestre.date_fin), forcer_module=sco_preferences.get_preference( "forcer_module", formsemestre_id=formsemestre_id, dept_id=g.scodoc_dept_id, ), defdem=_get_etuds_dem_def(formsemestre), readonly="true", ), html_sco_header.sco_footer(), ).build() @bp.route("/etat_abs_date") @scodoc @permission_required(Permission.ScoView) def etat_abs_date(): """date_debut, date_fin en ISO""" date_debut_str = request.args.get("date_debut") date_fin_str = request.args.get("date_fin") title = request.args.get("desc") group_ids: list[int] = request.args.get("group_ids", None) try: date_debut = datetime.datetime.fromisoformat(date_debut_str) except ValueError as exc: raise ScoValueError("date_debut invalide") from exc try: date_fin = datetime.datetime.fromisoformat(date_fin_str) except ValueError as exc: raise ScoValueError("date_fin invalide") from exc if group_ids is None: group_ids = [] else: group_ids = group_ids.split(",") map(str, group_ids) groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids) etuds = [ sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0] for m in groups_infos.members ] assiduites: Assiduite = Assiduite.query.filter( Assiduite.etudid.in_([e["etudid"] for e in etuds]) ) assiduites = scass.filter_by_date( assiduites, Assiduite, date_debut, date_fin, False ) etudiants: list[dict] = [] for etud in etuds: assi = assiduites.filter_by(etudid=etud["etudid"]).first() etat = "" if assi is not None and assi.etat != 0: etat = scu.EtatAssiduite.inverse().get(assi.etat).name etudiant = { "nom": f"""{etud["nomprenom"]}""", "etat": etat, } etudiants.append(etudiant) etudiants = list(sorted(etudiants, key=lambda x: x["nom"])) header: str = html_sco_header.sco_header( page_title=safehtml.html_to_safe_html(title), init_qtip=True, ) return HTMLBuilder( header, render_template( "assiduites/pages/etat_absence_date.j2", etudiants=etudiants, group_title=groups_infos.groups_titles, date_debut=date_debut, date_fin=date_fin, ), html_sco_header.sco_footer(), ).build() @bp.route("/VisualisationAssiduitesGroupe") @scodoc @permission_required(Permission.ScoView) def visu_assi_group(): dates = { "debut": request.args.get("date_debut"), "fin": request.args.get("date_fin"), } fmt = request.args.get("format", "html") group_ids: list[int] = request.args.get("group_ids", None) if group_ids is None: group_ids = [] else: group_ids = group_ids.split(",") map(str, group_ids) groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids) formsemestre = db.session.get(FormSemestre, groups_infos.formsemestre_id) etuds = etuds_sorted_from_ids([m["etudid"] for m in groups_infos.members]) table: TableAssi = TableAssi( etuds=etuds, dates=list(dates.values()), formsemestre=formsemestre ) if fmt.startswith("xls"): return scu.send_file( table.excel(), filename=f"assiduite-{groups_infos.groups_filename}", mime=scu.XLSX_MIMETYPE, suffix=scu.XLSX_SUFFIX, ) if groups_infos.tous_les_etuds_du_sem: gr_tit = "" grp = "" else: if len(groups_infos.group_ids) > 1: grp = "des groupes" else: grp = "du groupe" gr_tit = ( grp + ' ' + groups_infos.groups_titles + "" ) return render_template( "assiduites/pages/visu_assi.j2", assi_metric=scu.translate_assiduites_metric( scu.translate_assiduites_metric( sco_preferences.get_preference( "assi_metrique", dept_id=g.scodoc_dept_id ), ), inverse=False, short=False, ), date_debut=dates["debut"], date_fin=dates["fin"], gr_tit=gr_tit, group_ids=request.args.get("group_ids", None), sco=ScoData(formsemestre=groups_infos.get_formsemestre()), tableau=table.html(), title=f"Assiduité {grp} {groups_infos.groups_titles}", ) @bp.route("/SignalAssiduiteDifferee") @scodoc @permission_required(Permission.ScoAbsChange) def signal_assiduites_diff(): group_ids: list[int] = request.args.get("group_ids", None) formsemestre_id: int = request.args.get("formsemestre_id", -1) date: str = request.args.get("jour", datetime.date.today().isoformat()) etudiants: list[dict] = [] titre = None # Vérification du formsemestre_id try: formsemestre_id = int(formsemestre_id) except (TypeError, ValueError): formsemestre_id = None formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) # --- Vérification de la date --- real_date = scu.is_iso_formated(date, True).date() if real_date < formsemestre.date_debut: date = formsemestre.date_debut.isoformat() elif real_date > formsemestre.date_fin: date = formsemestre.date_fin.isoformat() if group_ids is None: group_ids = [] else: group_ids = group_ids.split(",") map(str, group_ids) groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids) if not groups_infos.members: return ( html_sco_header.sco_header(page_title="Assiduités Différées") + "

Aucun étudiant !

" + html_sco_header.sco_footer() ) etudiants.extend( [ sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0] for m in groups_infos.members ] ) etudiants = list(sorted(etudiants, key=lambda x: x["nom"])) header: str = html_sco_header.sco_header( page_title="Assiduités Différées", init_qtip=True, cssstyles=[ "css/assiduites.css", ], javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS + [ "js/assiduites.js", "libjs/moment.new.min.js", "libjs/moment-timezone.js", ], ) sem = formsemestre.to_dict() if groups_infos.tous_les_etuds_du_sem: gr_tit = "en" else: if len(groups_infos.group_ids) > 1: grp = "des groupes" else: grp = "du groupe" gr_tit = ( grp + ' ' + groups_infos.groups_titles + "" ) return HTMLBuilder( header, render_template( "assiduites/pages/signal_assiduites_diff.j2", diff=_differee( etudiants=etudiants, moduleimpl_select=_module_selector(formsemestre), date=date, periode={ "deb": formsemestre.date_debut.isoformat(), "fin": formsemestre.date_fin.isoformat(), }, ), gr=gr_tit, sem=sem["titre_num"], defdem=_get_etuds_dem_def(formsemestre), ), html_sco_header.sco_footer(), ).build() def _differee( etudiants, moduleimpl_select, date=None, periode=None, formsemestre_id=None ): if date is None: date = datetime.date.today().isoformat() forcer_module = sco_preferences.get_preference( "forcer_module", formsemestre_id=formsemestre_id, dept_id=g.scodoc_dept_id, ) etat_def = sco_preferences.get_preference( "assi_etat_defaut", formsemestre_id=formsemestre_id, dept_id=g.scodoc_dept_id, ) return render_template( "assiduites/widgets/differee.j2", etudiants=etudiants, etat_def=etat_def, forcer_module=forcer_module, moduleimpl_select=moduleimpl_select, date=date, periode=periode, ) def _module_selector( formsemestre: FormSemestre, moduleimpl_id: int = None ) -> HTMLElement: """ _module_selector Génère un HTMLSelectElement à partir des moduleimpl du formsemestre Args: formsemestre (FormSemestre): Le formsemestre d'où les moduleimpls seront pris. Returns: str: La représentation str d'un HTMLSelectElement """ ntc: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) modimpls_list: list[dict] = [] ues = ntc.get_ues_stat_dict() for ue in ues: modimpls_list += ntc.get_modimpls_dict(ue_id=ue["ue_id"]) selected = moduleimpl_id is not None modules = [] for modimpl in modimpls_list: modname: str = ( (modimpl["module"]["code"] or "") + " " + (modimpl["module"]["abbrev"] or modimpl["module"]["titre"] or "") ) modules.append({"moduleimpl_id": modimpl["moduleimpl_id"], "name": modname}) return render_template( "assiduites/widgets/moduleimpl_selector.j2", selected=selected, modules=modules ) def _dynamic_module_selector(): return render_template("assiduites/widgets/moduleimpl_dynamic_selector.j2") def _timeline(formsemestre_id=None) -> HTMLElement: return render_template( "assiduites/widgets/timeline.j2", t_start=get_time("assi_morning_time", "08:00:00"), t_end=get_time("assi_afternoon_time", "18:00:00"), tick_time=ScoDocSiteConfig.get("assi_tick_time", 15), periode_defaut=sco_preferences.get_preference( "periode_defaut", formsemestre_id ), ) def _mini_timeline() -> HTMLElement: return render_template( "assiduites/widgets/minitimeline.j2", t_start=get_time("assi_morning_time", "08:00:00"), t_end=get_time("assi_afternoon_time", "18:00:00"), ) def _non_work_days(): non_travail = sco_preferences.get_preference("non_travail", None) non_travail = non_travail.replace(" ", "").split(",") return ",".join([f"'{i.lower()}'" for i in non_travail]) def _str_to_num(string: str): parts = [*map(float, string.split(":"))] hour = parts[0] minutes = round(parts[1] / 60 * 4) / 4 return hour + minutes def get_time(label: str, default: str): return _str_to_num(ScoDocSiteConfig.get(label, default)) def _get_seuil(): return sco_preferences.get_preference("assi_seuil", dept_id=g.scodoc_dept_id) def _get_etuds_dem_def(formsemestre): etuds_dem_def = [ (f.etudid, f.etat) for f in FormSemestreInscription.query.filter( FormSemestreInscription.formsemestre_id == formsemestre.id, FormSemestreInscription.etat != "I", ).all() ] template: str = '"£" : "$",' json_str: str = "{" for etud in etuds_dem_def: json_str += template.replace("£", str(etud[0])).replace("$", etud[1]) if json_str != "{": json_str = json_str[:-1] return json_str + "}"