Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
7 changed files with 284 additions and 206 deletions
Showing only changes of commit 38032a8c09 - Show all commits

View File

@ -249,9 +249,10 @@ class ScolarNews(db.Model):
news_list = cls.last_news(n=n) news_list = cls.last_news(n=n)
if not news_list: if not news_list:
return "" return ""
dept_news_url = url_for("scolar.dept_news", scodoc_dept=g.scodoc_dept)
H = [ H = [
f"""<div class="scobox news"><div class="scobox-title"><a href="{ f"""<div class="scobox news"><div class="scobox-title"><a href="{
url_for("scolar.dept_news", scodoc_dept=g.scodoc_dept) dept_news_url
}">Dernières opérations</a> }">Dernières opérations</a>
</div><ul class="newslist">""" </div><ul class="newslist">"""
] ]
@ -261,17 +262,22 @@ class ScolarNews(db.Model):
f"""<li class="newslist"><span class="newsdate">{news.formatted_date()}</span><span f"""<li class="newslist"><span class="newsdate">{news.formatted_date()}</span><span
class="newstext">{news}</span></li>""" class="newstext">{news}</span></li>"""
) )
H.append(
f"""<li class="newslist">
<span class="newstext"><a href="{dept_news_url}" class="stdlink">...</a>
</span>
</li>"""
)
H.append("</ul>") H.append("</ul></div>")
# Informations générales # Informations générales
H.append( H.append(
f"""<div> f"""<div>
Pour en savoir plus sur ScoDoc voir le site Pour en savoir plus sur ScoDoc voir
<a class="stdlink" href="{scu.SCO_ANNONCES_WEBSITE}">scodoc.org</a>. <a class="stdlink" href="{scu.SCO_ANNONCES_WEBSITE}">scodoc.org</a>
</div> </div>
""" """
) )
H.append("</div>")
return "\n".join(H) return "\n".join(H)

View File

@ -247,7 +247,7 @@ class FormSemestre(models.ScoDocModel):
def to_dict_api(self): def to_dict_api(self):
""" """
Un dict avec les informations sur le semestre destiné à l'api Un dict avec les informations sur le semestre destinées à l'api
""" """
d = dict(self.__dict__) d = dict(self.__dict__)
d.pop("_sa_instance_state", None) d.pop("_sa_instance_state", None)

View File

@ -3,7 +3,7 @@
############################################################################## ##############################################################################
# #
# Gestion scolarite IUT # ScoDoc
# #
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
# #
@ -28,93 +28,119 @@
"""Page accueil département (liste des semestres, etc) """Page accueil département (liste des semestres, etc)
""" """
from flask import g from sqlalchemy import desc
from flask import url_for from flask import g, url_for, render_template
from flask_login import current_user from flask_login import current_user
from flask_sqlalchemy.query import Query
import app import app
from app import log from app import log
from app.models import ScolarNews from app.models import FormSemestre, ScolarNews
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc import html_sco_header
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_modalites from app.scodoc import sco_modalites
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_users from app.scodoc import sco_users
from app.views import ScoData
def index_html(showcodes=0, showsemtable=0): def index_html(showcodes=0, showsemtable=0):
"Page accueil département (liste des semestres)" "Page accueil département (liste des semestres)"
showcodes = int(showcodes) showcodes = int(showcodes)
showsemtable = int(showsemtable) showsemtable = int(showsemtable)
H = []
# News: # Liste tous les formsemestres du dept, le plus récent d'abord
H.append(ScolarNews.scolar_news_summary_html()) current_formsemestres = (
FormSemestre.query.filter_by(dept_id=g.scodoc_dept_id, etat=True)
.filter(FormSemestre.modalite != "EXT")
.order_by(desc(FormSemestre.date_debut))
)
locked_formsemestres = (
FormSemestre.query.filter_by(dept_id=g.scodoc_dept_id, etat=False)
.filter(FormSemestre.modalite != "EXT")
.order_by(desc(FormSemestre.date_debut))
)
formsemestres = (
FormSemestre.query.filter_by(dept_id=g.scodoc_dept_id)
.filter(FormSemestre.modalite != "EXT")
.order_by(desc(FormSemestre.date_debut))
)
if showsemtable: # table de tous les formsemestres
html_table_formsemestres = _sem_table_gt(
formsemestres, showcodes=showcodes
).html()
else:
html_table_formsemestres = None
# Avertissement de mise à jour: return render_template(
H.append("""<div id="update_warning" class="scobox update_warning"></div>""") "scolar/index.j2",
current_user=current_user,
dept_name=sco_preferences.get_preference("DeptName"),
formsemestres=formsemestres,
html_current_formsemestres=_show_current_formsemestres(
current_formsemestres, showcodes
),
html_table_formsemestres=html_table_formsemestres,
locked_formsemestres=locked_formsemestres,
nb_locked=locked_formsemestres.count(),
nb_user_accounts=sco_users.get_users_count(dept=g.scodoc_dept),
page_title=f"ScoDoc {g.scodoc_dept}",
Permission=Permission,
scolar_news_summary=ScolarNews.scolar_news_summary_html(),
showsemtable=showsemtable,
sco=ScoData(),
)
# Liste de toutes les sessions:
sems = sco_formsemestre.do_formsemestre_list() def _convert_formsemestres_to_dicts(
cursems = [] # semestres "courants" formsemestres: Query, showcodes: bool
othersems = [] # autres (verrouillés) ) -> list[dict]:
""" """
# icon image: # icon image:
groupicon = scu.icontag("groupicon_img", title="Inscrits", border="0") groupicon = scu.icontag("groupicon_img", title="Inscrits", border="0")
emptygroupicon = scu.icontag( emptygroupicon = scu.icontag(
"emptygroupicon_img", title="Pas d'inscrits", border="0" "emptygroupicon_img", title="Pas d'inscrits", border="0"
) )
lockicon = scu.icontag("lock32_img", title="verrouillé", border="0") lockicon = scu.icontag("lock32_img", title="verrouillé", border="0")
# Sélection sur l'etat du semestre # génère liste de dict
for sem in sems: sems = []
if sem["etat"] and sem["modalite"] != "EXT": for formsemestre in formsemestres:
sem["lockimg"] = "" nb_inscrits = len(formsemestre.inscriptions)
cursems.append(sem) sem = {
else: "anneescolaire": formsemestre.annee_scolaire(),
sem["lockimg"] = lockicon "bul_hide_xml": formsemestre.bul_hide_xml,
othersems.append(sem) "dateord": formsemestre.date_debut,
# Responsable de formation: "elt_annee_apo": formsemestre.elt_annee_apo,
sco_formsemestre.sem_set_responsable_name(sem) "elt_sem_apo": formsemestre.elt_sem_apo,
"etapes_apo_str": formsemestre.etapes_apo_str(),
"formsemestre_id": formsemestre.id,
"groupicon": groupicon if nb_inscrits > 0 else emptygroupicon,
"lockimg": lockicon,
"modalite": formsemestre.modalite,
"mois_debut": formsemestre.mois_debut(),
"mois_fin": formsemestre.mois_fin(),
"nb_inscrits": nb_inscrits,
"responsable_name": formsemestre.responsables_str(),
"semestre_id": formsemestre.semestre_id,
"session_id": formsemestre.session_id(),
"titre_num": formsemestre.titre_num(),
"tmpcode": (
f"<td><tt>{sem['formsemestre_id']}</tt></td>" if showcodes else ""
),
}
sems.append(sem)
return sems
if showcodes:
sem["tmpcode"] = f"<td><tt>{sem['formsemestre_id']}</tt></td>"
else:
sem["tmpcode"] = ""
# Nombre d'inscrits:
args = {"formsemestre_id": sem["formsemestre_id"]}
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(args=args)
nb = len(ins) # nb etudiants
sem["nb_inscrits"] = nb
if nb > 0:
sem["groupicon"] = groupicon
else:
sem["groupicon"] = emptygroupicon
# S'il n'y a pas d'utilisateurs dans la base, affiche message def _show_current_formsemestres(formsemestres: Query, showcodes: bool) -> str:
if not sco_users.get_users_count(dept=g.scodoc_dept): """html div avec les formsemestres courants de la page d'accueil"""
H.append(
"""<h2>Aucun utilisateur défini !</h2><p>Pour définir des utilisateurs
<a href="Users">passez par la page Utilisateurs</a>.
<br>
Définissez au moins un utilisateur avec le rôle AdminXXX
(le responsable du département XXX).
</p>
"""
)
H.append("""<div class="scobox">""") H = []
# Liste des formsemestres "courants" if formsemestres.count():
if cursems: H.append("""<div class="scobox-title">Sessions en cours</div>""")
H.append( H.append(_sem_table(_convert_formsemestres_to_dicts(formsemestres, showcodes)))
f"""
<div class="scobox-title">Sessions en cours</div>
{ _sem_table(cursems) }
"""
)
else: else:
# aucun semestre courant: affiche aide # aucun semestre courant: affiche aide
H.append( H.append(
@ -127,125 +153,16 @@ def index_html(showcodes=0, showsemtable=0):
"<em>Mettre en place un nouveau semestre de formation...</em>" "<em>Mettre en place un nouveau semestre de formation...</em>"
</p>""" </p>"""
) )
return "\n".join(H)
if showsemtable:
H.append(
f"""
<div class="scobox-title">Semestres de {sco_preferences.get_preference("DeptName")}</div>
"""
)
H.append(_sem_table_gt(sems, showcodes=showcodes).html())
H.append("</table>")
else:
H.append(
f"""
<p><a class="stdlink" href="{url_for('scolar.index_html',
scodoc_dept=g.scodoc_dept, showsemtable=1)
}">Voir table des semestres (dont {len(othersems)}
verrouillé{'s' if len(othersems) else ''})</a>
</p>"""
)
H.append(
f"""
<form action="{url_for('notes.view_formsemestre_by_etape', scodoc_dept=g.scodoc_dept)}">
Chercher étape courante:
<input name="etape_apo" type="text" size="8" spellcheck="false"></input>
</form>
</div>
"""
)
#
H.append(
"""
<div class="scobox">
<div class="scobox-title">Gestion des étudiants</div>
<ul>
"""
)
if current_user.has_permission(Permission.EtudInscrit):
H.append(
f"""
<li><a class="stdlink" href="{
url_for("scolar.etudident_create_form", scodoc_dept=g.scodoc_dept)
}">créer <em>un</em> nouvel étudiant</a>
</li>
<li><a class="stdlink" href="{
url_for("scolar.form_students_import_excel", scodoc_dept=g.scodoc_dept)
}">importer de nouveaux étudiants</a>
(<em>ne pas utiliser</em> sauf cas particulier&nbsp;: utilisez plutôt le lien dans
le tableau de bord semestre si vous souhaitez inscrire les
étudiants importés à un semestre)
</li>
"""
)
H.append(
f"""
<li><a class="stdlink" href="{
url_for("scolar.export_etudiants_courants", scodoc_dept=g.scodoc_dept)
}">exporter tableau des étudiants des semestres en cours</a>
</li>
"""
)
if current_user.has_permission(
Permission.EtudInscrit
) and sco_preferences.get_preference("portal_url"):
H.append(
f"""
<li><a class="stdlink" href="{
url_for("scolar.formsemestre_import_etud_admission",
scodoc_dept=g.scodoc_dept, tous_courants=1)
}">resynchroniser les données étudiants des semestres en cours depuis le portail</a>
</li>
"""
)
H.append("</ul></div>")
#
if current_user.has_permission(Permission.EditApogee):
H.append(
f"""
<div class="scobox">
<div class="scobox-title">Exports Apogée</div>
<ul>
<li><a class="stdlink" href="{
url_for('notes.semset_page', scodoc_dept=g.scodoc_dept)
}">Années scolaires / exports Apogée</a>
</li>
</ul>
</div>
"""
)
#
H.append(
"""
<div class="scobox">
<div class="scobox-title">Assistance</div>
<ul>
<li><a class="stdlink" href="https://scodoc.org/Contact" target="_blank"
rel="noopener noreferrer">Contact (Discord)</a>
</li>
<li><a class="stdlink" href="sco_dump_and_send_db">Envoyer données</a>
</li>
</ul>
</div>
"""
)
#
return (
html_sco_header.sco_header(
page_title=f"ScoDoc {g.scodoc_dept}", javascripts=["js/scolar_index.js"]
)
+ "\n".join(H)
+ html_sco_header.sco_footer()
)
def _sem_table(sems): def _sem_table(sems: list[dict]) -> str:
"""Affiche liste des semestres, utilisée pour semestres en cours""" """Affiche liste des semestres, utilisée pour semestres en cours"""
tmpl = """<tr class="%(trclass)s">%(tmpcode)s tmpl = f"""<tr class="%(trclass)s">%(tmpcode)s
<td class="semicon">%(lockimg)s <a href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td> <td class="semicon">%(lockimg)s <a href="{
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept)}?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td>
<td class="datesem">%(mois_debut)s <a title="%(session_id)s">-</a> %(mois_fin)s</td> <td class="datesem">%(mois_debut)s <a title="%(session_id)s">-</a> %(mois_fin)s</td>
<td><a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a> <td class="titresem"><a class="stdlink" href="{url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept)}?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
<span class="respsem">(%(responsable_name)s)</span> <span class="respsem">(%(responsable_name)s)</span>
</td> </td>
</tr> </tr>
@ -267,19 +184,26 @@ def _sem_table(sems):
cur_idx = sem["semestre_id"] cur_idx = sem["semestre_id"]
else: else:
sem["trclass"] = "" sem["trclass"] = ""
sem["notes_url"] = scu.NotesURL()
H.append(tmpl % sem) H.append(tmpl % sem)
H.append("</table>") H.append("</table>")
return "\n".join(H) return "\n".join(H)
def _sem_table_gt(sems, showcodes=False): def _sem_table_gt(formsemestres: Query, showcodes=False) -> GenTable:
"""Nouvelle version de la table des semestres """Table des semestres
Utilise une datatables. Utilise une datatables.
""" """
_style_sems(sems) sems = _style_sems(_convert_formsemestres_to_dicts(formsemestres, showcodes))
sems.sort(
key=lambda s: (
-s["anneescolaire"],
s["semestre_id"] if s["semestre_id"] > 0 else -s["semestre_id"] * 1000,
s["modalite"],
)
)
columns_ids = ( columns_ids = (
"lockimg", "lockimg",
"published",
"semestre_id_n", "semestre_id_n",
"modalite", "modalite",
#'mois_debut', #'mois_debut',
@ -327,22 +251,35 @@ def _sem_table_gt(sems, showcodes=False):
return tab return tab
def _style_sems(sems): def _style_sems(sems: list[dict]) -> list[dict]:
"""ajoute quelques attributs de présentation pour la table""" """ajoute quelques attributs de présentation pour la table"""
for sem in sems: for sem in sems:
sem["notes_url"] = scu.NotesURL() status_url = url_for(
sem["_groupicon_target"] = ( "notes.formsemestre_status",
"%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s" scodoc_dept=g.scodoc_dept,
% sem formsemestre_id=sem["formsemestre_id"],
) )
sem["_groupicon_target"] = status_url
sem["_formsemestre_id_class"] = "blacktt" sem["_formsemestre_id_class"] = "blacktt"
sem["dash_mois_fin"] = '<a title="%(session_id)s"></a> %(anneescolaire)s' % sem sem["dash_mois_fin"] = '<a title="%(session_id)s"></a> %(anneescolaire)s' % sem
sem["_dash_mois_fin_class"] = "datesem" sem["_dash_mois_fin_class"] = "datesem"
sem["titre_resp"] = ( sem["titre_resp"] = (
"""<a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a> f"""<a class="stdlink" href="{status_url}">{sem['titre_num']}</a> <span class="respsem">({sem['responsable_name']})</span>"""
<span class="respsem">(%(responsable_name)s)</span>"""
% sem
) )
sem["published"] = (
scu.icontag(
"hide_img",
border="0",
title="Bulletins NON publiés sur la passerelle étudiants",
)
if sem["bul_hide_xml"]
else scu.icontag(
"eye_img",
border="0",
title="Bulletins publiés sur la passerelle étudiants",
)
)
sem["_css_row_class"] = "css_S%d css_M%s" % ( sem["_css_row_class"] = "css_S%d css_M%s" % (
sem["semestre_id"], sem["semestre_id"],
sem["modalite"], sem["modalite"],
@ -363,6 +300,7 @@ def _style_sems(sems):
sem["_elt_sem_apo_td_attrs"] = ( sem["_elt_sem_apo_td_attrs"] = (
f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """ f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """
) )
return sems
def delete_dept(dept_id: int) -> str: def delete_dept(dept_id: int) -> str:

View File

@ -494,7 +494,7 @@ def table_formsemestres(
): ):
"""Une table presentant des semestres""" """Une table presentant des semestres"""
for sem in sems: for sem in sems:
sem_set_responsable_name(sem) sem_set_responsable_name(sem) # TODO utiliser formsemestre.responsables_str()
sem["_titre_num_target"] = url_for( sem["_titre_num_target"] = url_for(
"notes.formsemestre_status", "notes.formsemestre_status",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,

View File

@ -50,8 +50,8 @@ def list_formsemestres_modalites(sems):
return modalites return modalites
def group_sems_by_modalite(sems): def group_sems_by_modalite(sems: list[dict]):
"""Given the list of fromsemestre, group them by modalite, """Given the list of formsemestre, group them by modalite,
sorted in each one by semestre id and date sorted in each one by semestre id and date
""" """
sems_by_mod = collections.defaultdict(list) sems_by_mod = collections.defaultdict(list)

View File

@ -40,6 +40,10 @@ h3 {
font-weight: bold; font-weight: bold;
} }
details > summary:first-of-type {
display: list-item!important;
}
div.container { div.container {
margin-bottom: 24px; margin-bottom: 24px;
} }
@ -592,10 +596,6 @@ table.listesems tr.firstsem td {
padding-top: 0.8em; padding-top: 0.8em;
} }
td.datesem {
font-size: 80%;
white-space: nowrap;
}
h2.listesems { h2.listesems {
padding-top: 10px; padding-top: 10px;
@ -683,27 +683,33 @@ table.semlist tbody tr td.modalite {
} }
div#gtrcontent table.semlist tbody tr.css_S-1 td { div#gtrcontent table.semlist tbody tr.css_S-1 td {
background-color: rgb(251, 250, 216); background-color: rgb(211, 213, 255);
} }
div#gtrcontent table.semlist tbody tr.css_S1 td { div#gtrcontent table.semlist tbody tr.css_S1 td {
background-color: rgb(92%, 95%, 94%); background-color:#e9efef;
} }
div#gtrcontent table.semlist tbody tr.css_S2 td { div#gtrcontent table.semlist tbody tr.css_S2 td {
background-color: rgb(214, 223, 236); background-color: #d4ebd7;
} }
div#gtrcontent table.semlist tbody tr.css_S3 td { div#gtrcontent table.semlist tbody tr.css_S3 td {
background-color: rgb(167, 216, 201); background-color: #bedebe;
} }
div#gtrcontent table.semlist tbody tr.css_S4 td { div#gtrcontent table.semlist tbody tr.css_S4 td {
background-color: rgb(131, 225, 140); background-color: #afd7ad;
}
div#gtrcontent table.semlist tbody tr.css_S5 td {
background-color: #a0cd9a;
}
div#gtrcontent table.semlist tbody tr.css_S6 td {
background-color: #7dcf78;
} }
div#gtrcontent table.semlist tbody tr.css_MEXT td { div#gtrcontent table.semlist tbody tr.css_MEXT td {
color: #0b6e08; color: #fefcdf;
} }
/* ----- Liste des news ----- */ /* ----- Liste des news ----- */
@ -2019,7 +2025,8 @@ ul.ue_inscr_list li.etud {
.sem-groups-partition .stdlink, .sem-groups-partition .stdlink:visited { .sem-groups-partition .stdlink, .sem-groups-partition .stdlink:visited {
color: black; color: black;
text-decoration-style: dashed; text-decoration-style: dotted;
text-underline-offset: 3px;
} }
.sem-groups-list .stdlink, .sem-groups-list .stdlink:visited { .sem-groups-list .stdlink, .sem-groups-list .stdlink:visited {
color:rgb(0, 0, 192); color:rgb(0, 0, 192);

View File

@ -0,0 +1,127 @@
{# page accueil département #}
{% extends "sco_page.j2" %}
{% block app_content %}
<style>
td.datesem {
font-size: 80%;
white-space: nowrap;
padding-left: 8px;
}
td.titresem {
padding-left: 6px;
}
</style>
{# News #}
{{scolar_news_summary|safe}}
{# Avertissement de mise à jour: #}
<div id="update_warning" class="scobox update_warning"></div>
{% if nb_user_accounts == 0 %}
<h2>Aucun utilisateur défini !</h2>
<p>Pour définir des utilisateurs <a href="{{
url_for('users.index_html', scodoc_dept=g.scodoc_dept)
}}">passez par la page Utilisateurs</a>.<br>
Définissez au moins un utilisateur avec le rôle <tt>AdminXXX</tt>
(le responsable du département <tt>XXX</tt>).
</p>
{% endif %}
{# Les semestres courants (cad non verrouillés) #}
<div class="scobox">
{{html_current_formsemestres|safe}}
</div>
{# Table de tous les semestres #}
{% if html_table_formsemestres %}
<details open>
<summary>
Semestres de {{dept_name}}
</summary>
{{ html_table_formsemestres|safe }}
</details>
{% else %}
<p><a class="stdlink" href="{{
url_for('scolar.index_html', scodoc_dept=g.scodoc_dept, showsemtable=1)
}}">Voir table des {{formsemestres.count()}} semestres
{% if nb_locked %}
(dont {{nb_locked}} verrouillé{{'s' if nb_locked > 1 else ''}})
{%endif%}
</a>
</p>
{% endif %}
</div>
{# Recherche d'un semestre par code Apogée #}
<form action="{url_for('notes.view_formsemestre_by_etape', scodoc_dept=g.scodoc_dept)}">
Chercher étape courante:
<input name="etape_apo" type="text" size="8" spellcheck="false"></input>
</form>
{# Gestion des étudiants #}
<div class="scobox">
<div class="scobox-title">Gestion des étudiants</div>
<ul>
{% if current_user.has_permission(Permission.EtudInscrit) %}
<li><a class="stdlink" href="{{
url_for('scolar.etudident_create_form', scodoc_dept=g.scodoc_dept)
}}">créer <em>un</em> nouvel étudiant</a>
</li>
<li><a class="stdlink" href="{{
url_for('scolar.form_students_import_excel', scodoc_dept=g.scodoc_dept)
}}">importer de nouveaux étudiants</a>
(<em>ne pas utiliser</em> sauf cas particulier&nbsp;: utilisez plutôt le lien dans
le tableau de bord semestre si vous souhaitez inscrire les
étudiants importés à un semestre)
</li>
{% endif %}
<li><a class="stdlink" href="{{
url_for('scolar.export_etudiants_courants', scodoc_dept=g.scodoc_dept)
}}">exporter tableau des étudiants des semestres en cours</a>
</li>
{% if current_user.has_permission(Permission.EtudInscrit) and sco.prefs["portal_url"] %}
<li><a class="stdlink" href="{{
url_for('scolar.formsemestre_import_etud_admission', scodoc_dept=g.scodoc_dept, tous_courants=1)
}}">resynchroniser les données étudiants des semestres en cours depuis le portail</a>
</li>
{% endif %}
</ul>
</div>
{# Apogée #}
{% if current_user.has_permission(Permission.EditApogee) %}
<div class="scobox">
<div class="scobox-title">Exports Apogée</div>
<ul>
<li><a class="stdlink" href="{{
url_for('notes.semset_page', scodoc_dept=g.scodoc_dept)
}}">Années scolaires / exports Apogée</a>
</li>
</ul>
</div>
{% endif %}
{# Assistance #}
<div class="scobox">
<div class="scobox-title">Assistance</div>
<ul>
<li>
<a class="stdlink" href="https://scodoc.org/Contact" target="_blank"
rel="noopener noreferrer">Contact (Discord)</a>
</li>
<li>
<a class="stdlink" href="sco_dump_and_send_db">Envoyer données</a>
</li>
</ul>
</div>
{% endblock app_content %}
{% block scripts %}
{{ super() }}
<script src="{{scu.STATIC_DIR}}/js/scolar_index.js"></script>
{% endblock scripts %}