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
6 changed files with 376 additions and 5 deletions
Showing only changes of commit 53e16176df - Show all commits

View File

@ -0,0 +1,87 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""Génération bulletin BUT synthétique en une page
On génère du HTML. Il sera si possible traduit en PDF par weasyprint.
Le HTML est lui même généré à partir d'un template Jinja.
## Données
Ces données sont des objets passés au template.
- `etud: Identite` : l'étudiant
- `formsemestre: FormSemestre` : le formsemestre d'où est émis ce bulletin
- `bulletins_sem: BulletinBUT` les données bulletins pour tous les étudiants
- `bul: dict` : le bulletin (dict, même structure que le json publié)
- `cursus: EtudCursusBUT`: infos sur le cursus BUT (niveaux validés etc)
- `decision_ues: dict`: `{ acronyme_ue : { 'code' : 'ADM' }}` accès aux décisions
de jury d'UE
- `ects_total` : nombre d'ECTS validées dans ce cursus
- `ue_validation_by_niveau : dict` : les validations d'UE de chaque niveau du cursus
"""
import datetime
import time
from flask import render_template, url_for
from flask import g, request
from app.but.bulletin_but import BulletinBUT
from app.but import cursus_but, validations_view
from app.decorators import (
scodoc,
permission_required,
)
from app.models import FormSemestre, FormSemestreInscription, Identite
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences
from app.scodoc.sco_logos import find_logo
from app.scodoc.sco_permissions import Permission
from app.views import notes_bp as bp
from app.views import ScoData
@bp.route("/bulletin_but/<int:formsemestre_id>/<int:etudid>")
@scodoc
@permission_required(Permission.ScoView)
def bulletin_but(formsemestre_id: int, etudid: int = None):
"""Page HTML affichant le bulletin BUT simplifié"""
etud: Identite = Identite.query.get_or_404(etudid)
formsemestre: FormSemestre = (
FormSemestre.query.filter_by(id=formsemestre_id)
.join(FormSemestreInscription)
.filter_by(etudid=etudid)
.first_or_404()
)
bulletins_sem = BulletinBUT(formsemestre)
bul = bulletins_sem.bulletin_etud(etud, formsemestre) # dict
decision_ues = {x["acronyme"]: x for x in bul["semestre"]["decision_ue"]}
cursus = cursus_but.EtudCursusBUT(etud, formsemestre.formation)
refcomp = formsemestre.formation.referentiel_competence
if refcomp is None:
raise ScoNoReferentielCompetences(formation=formsemestre.formation)
ue_validation_by_niveau = validations_view.get_ue_validation_by_niveau(
refcomp, etud
)
ects_total = sum((v.ects() for v in ue_validation_by_niveau.values()))
logo = find_logo(logoname="header", dept_id=g.scodoc_dept_id)
return render_template(
"but/bulletin_court_page.j2",
bul=bul,
bulletins_sem=bulletins_sem,
cursus=cursus,
datetime=datetime,
decision_ues=decision_ues,
ects_total=ects_total,
etud=etud,
formsemestre=formsemestre,
logo=logo,
sco=ScoData(formsemestre=formsemestre, etud=etud),
time=time,
ue_validation_by_niveau=ue_validation_by_niveau,
)

View File

@ -426,6 +426,7 @@ def dict_decision_jury(
etud: Identite, formsemestre: FormSemestre, with_decisions: bool = False etud: Identite, formsemestre: FormSemestre, with_decisions: bool = False
) -> dict: ) -> dict:
"""dict avec decision pour bulletins json """dict avec decision pour bulletins json
- autorisation_inscription
- decision : décision semestre - decision : décision semestre
- decision_ue : list des décisions UE - decision_ue : list des décisions UE
- situation - situation
@ -511,7 +512,10 @@ def dict_decision_jury(
d["autorisation_inscription"] = [] d["autorisation_inscription"] = []
for aut in decision["autorisations"]: for aut in decision["autorisations"]:
d["autorisation_inscription"].append( d["autorisation_inscription"].append(
dict(semestre_id=aut["semestre_id"]) dict(
semestre_id=aut["semestre_id"],
date=aut["date"].isoformat() if aut["date"] else None,
)
) )
else: else:
d["decision"] = dict(code="", etat="DEM") d["decision"] = dict(code="", etat="DEM")

View File

@ -0,0 +1,118 @@
div.but_bul_court {
width: 17cm;
display: grid;
grid-template-columns: 6cm 11cm;
font-size: 11pt;
}
#infos_etudiant {
grid-column: 1;
grid-row: 1;
border-radius: 3mm;
border: 1px solid black;
background-color: white;
padding: 5mm;
}
.nom {
font-weight: bold;
font-size: 14pt;
}
#logo {
grid-column: 2;
grid-row: 1;
justify-self: end;
}
#logo img {
text-align: right;
height: 3cm;
}
div.but_bul_court table {
border-collapse: collapse;
border: 2px solid black;
}
div.but_bul_court table th,
div.but_bul_court table td {
background-color: white;
border: 1px solid black; /* Thin black border between cells */
padding: 2px 4px 2px 4px; /* Padding inside the cells */
}
table td.col_ue {
width: 18mm;
}
#ues {
grid-row: 2;
grid-column: 1/3;
justify-self: end;
margin-top: 5mm;
margin-bottom: 5mm;
}
#ues tr.titre_table th {
background-color: rgb(183,235,255);
padding: 2mm;
}
tr.titres_ues td, tr.jury td {
font-weight: bold;
}
table.resultats_modules {
width: 100%;
}
#ressources {
grid-row: 3;
grid-column: 1/3;
margin-bottom: 5mm;
width: 100%;
}
#ressources tr.titres_ues td:first-child {
background-color: rgb(255, 192, 0);
}
#saes {
grid-row: 4;
grid-column: 1/3;
margin-bottom: 5mm;
width: 100%;
}
#saes tr.titres_ues td:first-child {
background-color: rgb(176, 255, 99);
}
#row_situation {
grid-row: 5;
grid-column: 1/3;
display: grid;
grid-template-columns: auto auto;
}
#cursus_etud, #situation {
grid-row: 1;
}
#situation {
background-color: white;
justify-self: end;
margin-left: 1cm;
border-radius: 3mm;
border: 1px solid black;
padding: 5mm;
}
#footer {
grid-row: 6;
grid-column: 1/3;
margin-top: 5mm;
font-size: 9pt;
font-style: italic;
}
.but_bul_court .cursus_but {
margin-left: 0px;
}

View File

@ -0,0 +1,162 @@
{% extends "sco_page.j2" %}
{% block styles %}
{{super()}}
<link href="{{scu.STATIC_DIR}}/css/jury_but.css" rel="stylesheet" type="text/css" />
<link href="{{scu.STATIC_DIR}}/css/cursus_but.css" rel="stylesheet" type="text/css" />
<link href="{{scu.STATIC_DIR}}/css/bulletin_court.css" rel="stylesheet" type="text/css" />
{% endblock %}
{% macro table_modules(mod_type, title) -%}
<table class="resultats_modules">
<thead>
<tr class="titre_table">
<th colspan="2"></th>
<th colspan="{{ bul.ues|length }}">Unités d'enseignement</th>
</tr>
<tr class="titres_ues">
<td colspan="2">{{title}}</td>
{% for ue in bul.ues %}
<td class="col_ue">{{ue}}</td>
{% endfor %}
</tr>
</thead>
<tbody>
{% for mod in bul[mod_type] %}
<tr>
<td>{{mod}}</td>
<td>{{bul[mod_type][mod].titre}}</td>
{% for ue in bul.ues %}
<td>{{
bul.ues[ue][mod_type][mod].moyenne
if mod in bul.ues[ue][mod_type] else ""
}}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{%- endmacro %}
{% block app_content %}
<div class="but_bul_court">
<div id="infos_etudiant">
<div class="nom">{{etud.nomprenom}}</div>
<div class="formation">BUT {{formsemestre.formation.referentiel_competence.specialite}}</div>
{% if formsemestre.etuds_inscriptions[etud.id].parcour %}
<div class="parcours">Parcours {{formsemestre.etuds_inscriptions[etud.id].parcour.code}}</div>
{% endif %}
<div class="annee_scolaire">Année {{formsemestre.annee_scolaire_str()}}</div>
<div class="semestre">Semestre {{formsemestre.semestre_id}}</div>
</div>
<div id="logo">
{% if logo %}
{{logo.html()|safe}}
{% endif %}
</div>
<div id="ues">
<table>
<thead>
<tr class="titre_table">
<th colspan="{{ 1 + bul.ues|length }}">Unités d'enseignement du semestre {{formsemestre.semestre_id}}</th>
</tr>
<tr class="titres_ues">
<td></td>
{% for ue in bul.ues %}
<td class="col_ue">{{ue}}</td>
{% endfor %}
</tr>
</thead>
<tbody>
<tr>
<td>Moyenne</td>
{% for ue in bul.ues %}
<td class="col_ue">{{bul.ues[ue].moyenne.value}}</td>
{% endfor %}
</tr>
<tr>
<td>Bonus</td>
{% for ue in bul.ues %}
<td class="col_ue">{{bul.ues[ue].bonus if bul.ues[ue].bonus != "00.00" else ""}}</td>
{% endfor %}
</tr>
<tr>
<td>Malus</td>
{% for ue in bul.ues %}
<td class="col_ue">{{bul.ues[ue].malus if bul.ues[ue].malus != "00.00" else ""}}</td>
{% endfor %}
</tr>
<tr>
<td>Rang</td>
{% for ue in bul.ues %}
<td class="col_ue">{{bul.ues[ue].moyenne.rang}}</td>
{% endfor %}
</tr>
<tr>
<td>Effectif</td>
{% for ue in bul.ues %}
<td class="col_ue">{{bul.ues[ue].moyenne.total}}</td>
{% endfor %}
</tr>
<tr>
<td>ECTS</td>
{% for ue in bul.ues %}
<td class="col_ue">{{bul.ues[ue].moyenne.ects}}</td>
{% endfor %}
</tr>
<tr class="jury">
<td>Jury</td>
{% for ue in bul.ues %}
<td class="col_ue">{{decision_ues[ue].code}}</td>
{% endfor %}
</tr>
</tbody>
</table>
</div>
<div id="ressources">
{{ table_modules("ressources", "Ressources") }}
</div>
<div id="saes">
{{ table_modules("saes", "Situations d'Apprentissage et d'Évaluation (SAÉ)") }}
</div>
<div id="row_situation">
<div id="cursus_etud">
{% include "but/cursus_etud.j2" %}
</div>
<div id="situation">
<div>ECTS acquis : {{ects_total}}</div>
<div class="descr_jury">
{% if bul.semestre.decision_annee %}
Jury tenu le {{
datetime.datetime.fromisoformat(bul.semestre.decision_annee.date).strftime("%d/%m/%Y à %H:%M")
}},
année BUT {{bul.semestre.decision_annee.code}}.
{% endif %}
{% set virg = joiner(", ") %}
{% for aut in bul.semestre.autorisation_inscription -%}
{% if loop.first %}
Autorisé à s'inscrire en
{% endif %}
{{- virg() }}S{{aut.semestre_id -}}
{%- if loop.last -%}
.
{%- endif -%}
{%- endfor %}
</div>
</div>
</div>
<div id="footer">
Bulletin généré par ScoDoc le {{time.strftime("%d/%m/%Y à %Hh%M")}}. Explication des codes sur
<a href="https://scodoc.org/CodesJuryBUT">https://scodoc.org/CodesJuryBUT</a>
</div>
</div>
{% endblock %}

View File

@ -35,7 +35,7 @@ from operator import itemgetter
import time import time
import flask import flask
from flask import abort, flash, redirect, render_template, url_for from flask import flash, redirect, render_template, url_for
from flask import g, request from flask import g, request
from flask_login import current_user from flask_login import current_user
@ -44,6 +44,7 @@ from app import models
from app.auth.models import User from app.auth.models import User
from app.but import ( from app.but import (
apc_edit_ue, apc_edit_ue,
bulletin_but_court,
cursus_but, cursus_but,
jury_edit_manual, jury_edit_manual,
jury_but, jury_but,
@ -58,7 +59,6 @@ from app.comp import jury, res_sem
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import ( from app.models import (
Formation, Formation,
ScolarFormSemestreValidation,
ScolarAutorisationInscription, ScolarAutorisationInscription,
ScolarNews, ScolarNews,
Scolog, Scolog,

View File

@ -4,4 +4,4 @@ Architecture: amd64
Maintainer: Emmanuel Viennet <emmanuel@viennet.net> Maintainer: Emmanuel Viennet <emmanuel@viennet.net>
Description: ScoDoc 9 Description: ScoDoc 9
Un logiciel pour le suivi de la scolarité universitaire. Un logiciel pour le suivi de la scolarité universitaire.
Depends: adduser, curl, gcc, graphviz, graphviz-dev, libpq-dev, postfix|exim4, cracklib-runtime, libcrack2-dev, python3-dev, python3-venv, python3-pip, python3-wheel, nginx, postgresql, libpq-dev, redis, ufw Depends: adduser, curl, gcc, graphviz, graphviz-dev, libpq-dev, postfix|exim4, cracklib-runtime, libcrack2-dev, libpango-1.0-0, pango1.0-tools, python3-dev, python3-venv, python3-pip, python3-wheel, nginx, postgresql, libpq-dev, redis, ufw