# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Emmanuel Viennet emmanuel.viennet@viennet.net # ############################################################################## """ScoDoc ficheEtud Fiche description d'un étudiant et de son parcours """ from flask import abort, url_for, g, render_template, request from flask_login import current_user import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app import log from app.but import cursus_but, jury_but_view from app.models.etudiants import Identite, make_etud_args from app.models.formsemestre import FormSemestre from app.scodoc import html_sco_header from app.scodoc import htmlutils from app.scodoc import sco_archives_etud from app.scodoc import sco_bac from app.scodoc import codes_cursus from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_status from app.scodoc import sco_groups from app.scodoc import sco_cursus from app.scodoc import sco_permissions_check from app.scodoc import sco_photos from app.scodoc import sco_users from app.scodoc import sco_report from app.scodoc import sco_etud from app.scodoc.sco_bulletins import etud_descr_situation_semestre from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_formsemestre_validation import formsemestre_recap_parcours_table from app.scodoc.sco_permissions import Permission def _menu_scolarite(authuser, sem: dict, etudid: int): """HTML pour menu "scolarite" pour un etudiant dans un semestre. Le contenu du menu depend des droits de l'utilisateur et de l'état de l'étudiant. """ locked = not sem["etat"] if locked: lockicon = scu.icontag("lock32_img", title="verrouillé", border="0") return lockicon # no menu if not authuser.has_permission( Permission.ScoEtudInscrit ) and not authuser.has_permission(Permission.ScoEtudChangeGroups): return "" # no menu ins = sem["ins"] args = {"etudid": etudid, "formsemestre_id": ins["formsemestre_id"]} if ins["etat"] != "D": dem_title = "Démission" dem_url = "scolar.form_dem" else: dem_title = "Annuler la démission" dem_url = "scolar.do_cancel_dem" # Note: seul un etudiant inscrit (I) peut devenir défaillant. if ins["etat"] != codes_cursus.DEF: def_title = "Déclarer défaillance" def_url = "scolar.form_def" elif ins["etat"] == codes_cursus.DEF: def_title = "Annuler la défaillance" def_url = "scolar.do_cancel_def" def_enabled = ( (ins["etat"] != "D") and authuser.has_permission(Permission.ScoEtudInscrit) and not locked ) items = [ { "title": dem_title, "endpoint": dem_url, "args": args, "enabled": authuser.has_permission(Permission.ScoEtudInscrit) and not locked, }, { "title": "Validation du semestre (jury)", "endpoint": "notes.formsemestre_validation_etud_form", "args": args, "enabled": authuser.has_permission(Permission.ScoEtudInscrit) and not locked, }, { "title": def_title, "endpoint": def_url, "args": args, "enabled": def_enabled, }, { "title": "Inscrire à un module optionnel (ou au sport)", "endpoint": "notes.formsemestre_inscription_option", "args": args, "enabled": authuser.has_permission(Permission.ScoEtudInscrit) and not locked, }, { "title": "Désinscrire (en cas d'erreur)", "endpoint": "notes.formsemestre_desinscription", "args": args, "enabled": authuser.has_permission(Permission.ScoEtudInscrit) and not locked, }, { "title": "Inscrire à un autre semestre", "endpoint": "notes.formsemestre_inscription_with_modules_form", "args": {"etudid": etudid}, "enabled": authuser.has_permission(Permission.ScoEtudInscrit), }, { "title": "Enregistrer un semestre effectué ailleurs", "endpoint": "notes.formsemestre_ext_create_form", "args": args, "enabled": authuser.has_permission(Permission.ScoImplement), }, { "title": "Affecter les notes manquantes", "endpoint": "notes.formsemestre_note_etuds_sans_notes", "args": args, "enabled": authuser.has_permission(Permission.ScoEditAllNotes), }, ] return htmlutils.make_menu( "Scolarité", items, css_class="direction_etud", alone=True ) def ficheEtud(etudid=None): "fiche d'informations sur un etudiant" authuser = current_user cnx = ndb.GetDBConnexion() if etudid: try: # pour les bookmarks avec d'anciens ids... etudid = int(etudid) except ValueError: raise ScoValueError("id invalide !") from ValueError # la sidebar est differente s'il y a ou pas un etudid # voir html_sidebar.sidebar() g.etudid = etudid args = make_etud_args(etudid=etudid) etuds = sco_etud.etudident_list(cnx, args) if not etuds: log(f"ficheEtud: etudid={etudid!r} request.args={request.args!r}") raise ScoValueError("Étudiant inexistant !") etud_ = etuds[0] # transition: etud_ à éliminer et remplacer par etud etudid = etud_["etudid"] etud = Identite.query.get(etudid) sco_etud.fill_etuds_info([etud_]) # info = etud_ info["ScoURL"] = scu.ScoURL() info["authuser"] = authuser info["info_naissance"] = info["date_naissance"] if info["lieu_naissance"]: info["info_naissance"] += " à " + info["lieu_naissance"] if info["dept_naissance"]: info["info_naissance"] += f" ({info['dept_naissance']})" info["etudfoto"] = sco_photos.etud_photo_html(etud_) if ( (not info["domicile"]) and (not info["codepostaldomicile"]) and (not info["villedomicile"]) ): info["domicile"] = "<em>inconnue</em>" if info["paysdomicile"]: pays = sco_etud.format_pays(info["paysdomicile"]) if pays: info["paysdomicile"] = "(%s)" % pays else: info["paysdomicile"] = "" if info["telephone"] or info["telephonemobile"]: info["telephones"] = "<br>%s %s" % ( info["telephonestr"], info["telephonemobilestr"], ) else: info["telephones"] = "" # e-mail: if info["email_default"]: info["emaillink"] = ", ".join( [ '<a class="stdlink" href="mailto:%s">%s</a>' % (m, m) for m in [etud_["email"], etud_["emailperso"]] if m ] ) else: info["emaillink"] = "<em>(pas d'adresse e-mail)</em>" # Champ dépendant des permissions: if authuser.has_permission(Permission.ScoEtudChangeAdr): info[ "modifadresse" ] = f"""<a class="stdlink" href="{ url_for("scolar.form_change_coordonnees", scodoc_dept=g.scodoc_dept, etudid=etudid) }">modifier adresse</a>""" else: info["modifadresse"] = "" # Groupes: sco_groups.etud_add_group_infos( info, info["cursem"]["formsemestre_id"] if info["cursem"] else None, only_to_show=True, ) # Parcours de l'étudiant if info["sems"]: info["last_formsemestre_id"] = info["sems"][0]["formsemestre_id"] else: info["last_formsemestre_id"] = "" sem_info = {} for sem in info["sems"]: if sem["ins"]["etat"] != scu.INSCRIT: descr, _ = etud_descr_situation_semestre( etudid, sem["formsemestre_id"], info["ne"], show_date_inscr=False, ) grlink = f"""<span class="fontred">{descr["situation"]}</span>""" else: e = {"etudid": etudid} sco_groups.etud_add_group_infos( e, sem["formsemestre_id"], only_to_show=True, ) grlinks = [] for partition in e["partitions"].values(): if partition["partition_name"]: gr_name = partition["group_name"] else: gr_name = "tous" grlinks.append( f"""<a class="discretelink" href="{ url_for('scolar.groups_view', scodoc_dept=g.scodoc_dept, group_ids=partition['group_id']) }" title="Liste du groupe {gr_name}">{gr_name}</a> """ ) grlink = ", ".join(grlinks) # infos ajoutées au semestre dans le parcours (groupe, menu) menu = _menu_scolarite(authuser, sem, etudid) if menu: sem_info[sem["formsemestre_id"]] = ( "<table><tr><td>" + grlink + "</td><td>" + menu + "</td></tr></table>" ) else: sem_info[sem["formsemestre_id"]] = grlink if info["sems"]: Se = sco_cursus.get_situation_etud_cursus(etud_, info["last_formsemestre_id"]) info["liste_inscriptions"] = formsemestre_recap_parcours_table( Se, etudid, with_links=False, sem_info=sem_info, with_all_columns=False, a_url="Notes/", ) info[ "link_bul_pdf" ] = f"""<span class="link_bul_pdf"><a class="stdlink" href="{ url_for("notes.etud_bulletins_pdf", scodoc_dept=g.scodoc_dept, etudid=etudid) }">tous les bulletins</a></span>""" if authuser.has_permission(Permission.ScoEtudInscrit): info[ "link_inscrire_ailleurs" ] = f"""<span class="link_bul_pdf"><a class="stdlink" href="{ url_for("notes.formsemestre_inscription_with_modules_form", scodoc_dept=g.scodoc_dept, etudid=etudid) }">inscrire à un autre semestre</a></span>""" else: info["link_inscrire_ailleurs"] = "" else: # non inscrit l = [f"""<p><b>Étudiant{info["ne"]} non inscrit{info["ne"]}"""] if authuser.has_permission(Permission.ScoEtudInscrit): l.append( f"""<a href="{ url_for("notes.formsemestre_inscription_with_modules_form", scodoc_dept=g.scodoc_dept, etudid=etudid) }">inscrire</a></li>""" ) l.append("</b></b>") info["liste_inscriptions"] = "\n".join(l) info["link_bul_pdf"] = "" info["link_inscrire_ailleurs"] = "" # Liste des annotations alist = [] annos = sco_etud.etud_annotations_list(cnx, args={"etudid": etudid}) for a in annos: if not sco_permissions_check.can_suppress_annotation(a["id"]): a["dellink"] = "" else: a["dellink"] = ( '<td class="annodel"><a href="doSuppressAnnotation?etudid=%s&annotation_id=%s">%s</a></td>' % ( etudid, a["id"], scu.icontag( "delete_img", border="0", alt="suppress", title="Supprimer cette annotation", ), ) ) author = sco_users.user_info(a["author"]) alist.append( f"""<tr><td><span class="annodate">Le {a['date']} par {author['prenomnom']} : </span><span class="annoc">{a['comment']}</span></td>{a['dellink']}</tr> """ ) info["liste_annotations"] = "\n".join(alist) # fiche admission has_adm_notes = ( info["math"] or info["physique"] or info["anglais"] or info["francais"] ) has_bac_info = ( info["bac"] or info["specialite"] or info["annee_bac"] or info["rapporteur"] or info["commentaire"] or info["classement"] or info["type_admission"] ) if has_bac_info or has_adm_notes: adm_tmpl = """<!-- Donnees admission --> <div class="fichetitre">Informations admission</div> """ if has_adm_notes: adm_tmpl += """ <table> <tr><th>Bac</th><th>Année</th><th>Rg</th> <th>Math</th><th>Physique</th><th>Anglais</th><th>Français</th></tr> <tr> <td>%(bac)s (%(specialite)s)</td> <td>%(annee_bac)s </td> <td>%(classement)s</td> <td>%(math)s</td><td>%(physique)s</td><td>%(anglais)s</td><td>%(francais)s</td> </tr> </table> """ adm_tmpl += """ <div>Bac %(bac)s (%(specialite)s) obtenu en %(annee_bac)s </div> <div class="ilycee">%(ilycee)s</div>""" if info["type_admission"] or info["classement"]: adm_tmpl += """<div class="vadmission">""" if info["type_admission"]: adm_tmpl += """<span>Voie d'admission: <span class="etud_type_admission">%(type_admission)s</span></span> """ if info["classement"]: adm_tmpl += """<span>Rang admission: <span class="etud_type_admission">%(classement)s</span></span>""" if info["type_admission"] or info["classement"]: adm_tmpl += "</div>" if info["rap"]: adm_tmpl += """<div class="note_rapporteur">%(rap)s</div>""" adm_tmpl += """</div>""" else: adm_tmpl = "" # pas de boite "info admission" info["adm_data"] = adm_tmpl % info # Fichiers archivés: info["fichiers_archive_htm"] = ( '<div class="fichetitre">Fichiers associés</div>' + sco_archives_etud.etud_list_archives_html(etudid) ) # Devenir de l'étudiant: has_debouche = True if sco_permissions_check.can_edit_suivi(): suivi_readonly = "0" link_add_suivi = """<li class="adddebouche"> <a id="adddebouchelink" class="stdlink" href="#">ajouter une ligne</a> </li>""" else: suivi_readonly = "1" link_add_suivi = "" if has_debouche: info[ "debouche_html" ] = """<div id="fichedebouche" data-readonly="%s" data-etudid="%s"> <span class="debouche_tit">Devenir:</span> <div><form> <ul class="listdebouches"> %s </ul> </form></div> </div>""" % ( suivi_readonly, info["etudid"], link_add_suivi, ) else: info["debouche_html"] = "" # pas de boite "devenir" # if info["liste_annotations"]: info["tit_anno"] = '<div class="fichetitre">Annotations</div>' else: info["tit_anno"] = "" # Inscriptions info[ "inscriptions_mkup" ] = f"""<div class="ficheinscriptions" id="ficheinscriptions"> <div class="fichetitre">Parcours</div>{info["liste_inscriptions"]} {info["link_bul_pdf"]} {info["link_inscrire_ailleurs"]} </div>""" # if info["groupes"].strip(): info[ "groupes_row" ] = f"""<tr> <td class="fichetitre2">Groupes :</td><td>{info['groupes']}</td> </tr>""" else: info["groupes_row"] = "" info["menus_etud"] = menus_etud(etudid) if info["boursier"]: info["bourse_span"] = """<span class="boursier">boursier</span>""" else: info["bourse_span"] = "" # raccordement provisoire pour juillet 2022, avant refonte complète de cette fiche... # info["but_infos_mkup"] = jury_but_view.infos_fiche_etud_html(etudid) # XXX dev info["but_cursus_mkup"] = "" if info["sems"]: last_sem = FormSemestre.query.get_or_404(info["sems"][0]["formsemestre_id"]) if last_sem.formation.is_apc(): but_cursus = cursus_but.EtudCursusBUT(etud, last_sem.formation) info["but_cursus_mkup"] = render_template( "but/cursus_etud.j2", cursus=but_cursus, scu=scu, ) tmpl = """<div class="menus_etud">%(menus_etud)s</div> <div class="ficheEtud" id="ficheEtud"><table> <tr><td> <h2>%(nomprenom)s (%(inscription)s)</h2> <span>%(emaillink)s</span> </td><td class="photocell"> <a href="etud_photo_orig_page?etudid=%(etudid)s">%(etudfoto)s</a> </td></tr></table> <div class="fichesituation"> <div class="fichetablesitu"> <table> <tr><td class="fichetitre2">Situation :</td><td>%(situation)s %(bourse_span)s</td></tr> %(groupes_row)s <tr><td class="fichetitre2">Né%(ne)s le :</td><td>%(info_naissance)s</td></tr> </table> <!-- Adresse --> <div class="ficheadresse" id="ficheadresse"> <table><tr> <td class="fichetitre2">Adresse :</td><td> %(domicile)s %(codepostaldomicile)s %(villedomicile)s %(paysdomicile)s %(modifadresse)s %(telephones)s </td></tr></table> </div> </div> </div> %(inscriptions_mkup)s %(but_cursus_mkup)s <div class="ficheadmission"> %(adm_data)s %(fichiers_archive_htm)s </div> %(debouche_html)s <div class="ficheannotations"> %(tit_anno)s <table id="etudannotations">%(liste_annotations)s</table> <form action="doAddAnnotation" method="GET" class="noprint"> <input type="hidden" name="etudid" value="%(etudid)s"> <b>Ajouter une annotation sur %(nomprenom)s: </b> <table><tr> <tr><td><textarea name="comment" rows="4" cols="50" value=""></textarea> <br><font size=-1> <i>Ces annotations sont lisibles par tous les enseignants et le secrétariat.</i> <br> <i>L'annotation commençant par "PE:" est un avis de poursuite d'études.</i> </font> </td></tr> <tr><td> <input type="hidden" name="author" width=12 value="%(authuser)s"> <input type="submit" value="Ajouter annotation"></td></tr> </table> </form> </div> <div class="code_nip">code NIP: %(code_nip)s</div> </div> """ header = html_sco_header.sco_header( page_title="Fiche étudiant %(prenom)s %(nom)s" % info, cssstyles=[ "libjs/jQuery-tagEditor/jquery.tag-editor.css", "css/jury_but.css", "css/cursus_but.css", ], javascripts=[ "libjs/jinplace-1.2.1.min.js", "js/ue_list.js", "libjs/jQuery-tagEditor/jquery.tag-editor.min.js", "libjs/jQuery-tagEditor/jquery.caret.min.js", "js/recap_parcours.js", "js/etud_debouche.js", ], ) return header + tmpl % info + html_sco_header.sco_footer() def menus_etud(etudid): """Menu etudiant (operations sur l'etudiant)""" authuser = current_user etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] menuEtud = [ { "title": etud["nomprenom"], "endpoint": "scolar.ficheEtud", "args": {"etudid": etud["etudid"]}, "enabled": True, "helpmsg": "Fiche étudiant", }, { "title": "Changer la photo", "endpoint": "scolar.form_change_photo", "args": {"etudid": etud["etudid"]}, "enabled": authuser.has_permission(Permission.ScoEtudChangeAdr), }, { "title": "Changer les données identité/admission", "endpoint": "scolar.etudident_edit_form", "args": {"etudid": etud["etudid"]}, "enabled": authuser.has_permission(Permission.ScoEtudInscrit), }, { "title": "Supprimer cet étudiant...", "endpoint": "scolar.etudident_delete", "args": {"etudid": etud["etudid"]}, "enabled": authuser.has_permission(Permission.ScoEtudInscrit), }, { "title": "Voir le journal...", "endpoint": "scolar.showEtudLog", "args": {"etudid": etud["etudid"]}, "enabled": True, }, ] return htmlutils.make_menu("Étudiant", menuEtud, alone=True) def etud_info_html(etudid, with_photo="1", debug=False): """An HTML div with basic information and links about this etud. Used for popups information windows. """ formsemestre_id = sco_formsemestre_status.retreive_formsemestre_from_request() with_photo = int(with_photo) etuds = sco_etud.get_etud_info(filled=True) if etuds: etud = etuds[0] else: abort(404, "etudiant inconnu") photo_html = sco_photos.etud_photo_html(etud, title="fiche de " + etud["nom"]) # experimental: may be too slow to be here code_cursus, _ = sco_report.get_code_cursus_etud(etud, prefix="S", separator=", ") bac = sco_bac.Baccalaureat(etud["bac"], etud["specialite"]) bac_abbrev = bac.abbrev() H = f"""<div class="etud_info_div"> <div class="eid_left"> <div class="eid_nom"><div>{etud["nomprenom"]}</div></div> <div class="eid_info eid_bac">Bac: <span class="eid_bac">{bac_abbrev}</span></div> <div class="eid_info eid_parcours">{code_cursus}</div> """ # Informations sur l'etudiant dans le semestre courant: sem = None if formsemestre_id: # un semestre est spécifié par la page sem = sco_formsemestre.get_formsemestre(formsemestre_id) elif etud["cursem"]: # le semestre "en cours" pour l'étudiant sem = etud["cursem"] if sem: groups = sco_groups.get_etud_groups(etudid, formsemestre_id) grc = sco_groups.listgroups_abbrev(groups) H += f"""<div class="eid_info">En <b>S{sem["semestre_id"]}</b>: {grc}</div>""" H += "</div>" # fin partie gauche (eid_left) if with_photo: H += '<span class="eid_right">' + photo_html + "</span>" H += "</div>" if debug: return ( html_sco_header.standard_html_header() + H + html_sco_header.standard_html_footer() ) else: return H