diff --git a/app/but/jury_but_view.py b/app/but/jury_but_view.py
new file mode 100644
index 000000000..35e0ddaaa
--- /dev/null
+++ b/app/but/jury_but_view.py
@@ -0,0 +1,173 @@
+##############################################################################
+# ScoDoc
+# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
+# See LICENSE
+##############################################################################
+
+"""Jury BUT: affichage/formulaire
+"""
+from flask import g, url_for
+from app.models.etudiants import Identite
+
+from app.scodoc import sco_utils as scu
+from app.but.jury_but import DecisionsProposeesAnnee, DecisionsProposeesUE
+from app.models import FormSemestre, FormSemestreInscription, UniteEns
+from app.scodoc.sco_exceptions import ScoValueError
+
+
+def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
+ """Affichage des décisions annuelles BUT
+ Si pas read_only, menus sélection codes jury.
+ """
+ H = []
+ if deca.code_valide and not read_only:
+ erase_span = f"""effacer décisions"""
+ else:
+ erase_span = ""
+
+ H.append(
+ f"""
+
+ Décision de jury pour l'année : {
+ _gen_but_select("code_annee", deca.codes, deca.code_valide,
+ disabled=True, klass="manual")
+ }
+ ({'non ' if deca.code_valide is None else ''}enregistrée)
+ {erase_span}
+
+
{deca.explanation}
+
"""
+ )
+
+ H.append(
+ f"""
+ Niveaux de compétences et unités d'enseignement :
+
+
+
S{1}
+
S{2}
+
RCUE
+ """
+ )
+ for niveau in deca.niveaux_competences:
+ H.append(
+ f"""
+
{niveau.competence.titre}
+
"""
+ )
+ dec_rcue = deca.decisions_rcue_by_niveau.get(niveau.id)
+ if dec_rcue is None:
+ break
+ # Semestre impair
+ H.append(
+ _gen_but_niveau_ue(
+ dec_rcue.rcue.ue_1,
+ dec_rcue.rcue.moy_ue_1,
+ deca.decisions_ues[dec_rcue.rcue.ue_1.id],
+ disabled=read_only,
+ )
+ )
+ # Semestre pair
+ H.append(
+ _gen_but_niveau_ue(
+ dec_rcue.rcue.ue_2,
+ dec_rcue.rcue.moy_ue_2,
+ deca.decisions_ues[dec_rcue.rcue.ue_2.id],
+ disabled=read_only,
+ )
+ )
+ # RCUE
+ H.append(
+ f"""
+
{scu.fmt_note(dec_rcue.rcue.moy_rcue)}
+
{
+ _gen_but_select("code_rcue_"+str(niveau.id),
+ dec_rcue.codes,
+ dec_rcue.code_valide,
+ disabled=True, klass="manual"
+ )
+ }
+
"""
+ )
+ H.append("
") # but_annee
+ return "\n".join(H)
+
+
+def _gen_but_select(
+ name: str,
+ codes: list[str],
+ code_valide: str,
+ disabled: bool = False,
+ klass: str = "",
+) -> str:
+ "Le menu html select avec les codes"
+ h = "\n".join(
+ [
+ f""""""
+ for code in codes
+ ]
+ )
+ return f"""
+ """
+
+
+def _gen_but_niveau_ue(
+ ue: UniteEns, moy_ue: float, dec_ue: DecisionsProposeesUE, disabled=False
+):
+ return f"""
+
{ue.acronyme}
+
{scu.fmt_note(moy_ue)}
+
{
+ _gen_but_select("code_ue_"+str(ue.id),
+ dec_ue.codes,
+ dec_ue.code_valide, disabled=disabled
+ )
+ }
+
"""
+
+
+#
+def infos_fiche_etud_html(etudid: int) -> str:
+ """Section html pour fiche etudiant
+ provisoire pour BUT 2022
+ """
+ etud: Identite = Identite.query.get_or_404(etudid)
+ inscriptions = (
+ FormSemestreInscription.query.join(FormSemestreInscription.formsemestre)
+ .filter(
+ FormSemestreInscription.etudid == etud.id,
+ )
+ .order_by(FormSemestre.date_debut)
+ )
+ formsemestres_but = [
+ i.formsemestre for i in inscriptions if i.formsemestre.formation.is_apc()
+ ]
+ if len(formsemestres_but) == 0:
+ return ""
+
+ # temporaire quick & dirty: affiche le dernier
+ try:
+ deca = DecisionsProposeesAnnee(etud, formsemestres_but[-1])
+ except ScoValueError:
+ pass
+ if len(deca.rcues_annee) > 0:
+ return f"""
+ {show_etud(deca, read_only=True)}
+
+ """
+
+ return ""