Formation: |
+ f"""
+ Formation : |
| Parcours: |
+ Parcours : |
{', '.join(parcours.code for parcours in sem_parcours)} |
"""
)
+ if formsemestre.capacite_accueil is not None:
+ H.append(
+ f"""
+ Capacité d'accueil : |
+ {formsemestre.capacite_accueil} |
+
+ """
+ )
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre)
H.append(
- 'Évaluations: | %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides'
+ """ | Évaluations : |
+ %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides"""
% evals
)
if evals["last_modif"]:
diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py
index d4817ee2..541589bb 100644
--- a/app/scodoc/sco_groups_view.py
+++ b/app/scodoc/sco_groups_view.py
@@ -40,7 +40,7 @@ from flask import url_for, g, render_template, request
from flask_login import current_user
from app import db
-from app.models import FormSemestre, Identite
+from app.models import FormSemestre, Identite, ScolarEvent
import app.scodoc.sco_utils as scu
from app.scodoc import html_sco_header
from app.scodoc import sco_assiduites as scass
@@ -70,6 +70,7 @@ def groups_lists(
group_ids=(),
fmt="html",
with_codes=0,
+ with_date_inscription=0,
etat=None,
with_paiement=0,
with_archives=0,
@@ -102,6 +103,7 @@ def groups_lists(
groups_infos=groups_infos,
fmt=fmt,
with_codes=with_codes,
+ with_date_inscription=with_date_inscription,
etat=etat,
with_paiement=with_paiement,
with_archives=with_archives,
@@ -121,6 +123,7 @@ def groups_lists(
groups_infos=groups_infos,
fmt=fmt,
with_codes=with_codes,
+ with_date_inscription=with_date_inscription,
etat=etat,
with_paiement=with_paiement,
with_archives=with_archives,
@@ -507,6 +510,7 @@ class DisplayedGroupsInfos:
def groups_table(
groups_infos: DisplayedGroupsInfos = None,
with_codes=0,
+ with_date_inscription=0,
etat=None,
fmt="html",
with_paiement=0, # si vrai, ajoute colonnes infos paiement droits et finalisation inscription (lent car interrogation portail)
@@ -522,15 +526,16 @@ def groups_table(
can_view_etud_data = int(current_user.has_permission(Permission.ViewEtudData))
with_codes = int(with_codes)
+ with_date_inscription = int(with_date_inscription)
with_paiement = int(with_paiement) and can_view_etud_data
with_archives = int(with_archives) and can_view_etud_data
with_annotations = int(with_annotations) and can_view_etud_data
with_bourse = int(with_bourse) and can_view_etud_data
- base_url_np = groups_infos.base_url + f"&with_codes={with_codes}"
base_url = (
- base_url_np
- + f"""&with_paiement={with_paiement}&with_archives={
+ groups_infos.base_url
+ + f"""&with_codes={with_codes}&with_date_inscription={
+ with_date_inscription}&with_paiement={with_paiement}&with_archives={
with_archives}&with_annotations={with_annotations
}&with_bourse={with_bourse}"""
)
@@ -546,6 +551,7 @@ def groups_table(
"etudid": "etudid",
"code_nip": "code_nip",
"code_ine": "code_ine",
+ "date_inscription": "Date inscription",
"datefinalisationinscription_str": "Finalisation inscr.",
"paiementinscription_str": "Paiement",
"etudarchive": "Fichiers",
@@ -579,9 +585,11 @@ def groups_table(
if with_codes:
columns_ids += ["etape", "etudid", "code_nip", "code_ine"]
+ if with_date_inscription:
+ columns_ids += ["date_inscription"]
if with_paiement:
columns_ids += ["datefinalisationinscription_str", "paiementinscription_str"]
- if with_paiement: # or with_codes:
+ if with_paiement:
sco_portal_apogee.check_paiement_etuds(groups_infos.members)
if with_archives:
from app.scodoc import sco_archives_etud
@@ -597,6 +605,16 @@ def groups_table(
moodle_groupenames = set()
# ajoute liens
for etud_info in groups_infos.members:
+ if with_date_inscription:
+ event = ScolarEvent.query.filter_by(
+ etudid=etud_info["etudid"],
+ event_type="INSCRIPTION",
+ formsemestre_id=groups_infos.formsemestre_id,
+ ).first()
+ if event:
+ etud_info["date_inscription"] = event.event_date.strftime(scu.DATE_FMT)
+ etud_info["_date_inscription_xls"] = event.event_date
+ etud_info["_date_inscription_order"] = event.event_date.isoformat
if etud_info["email"]:
etud_info["_email_target"] = "mailto:" + etud_info["email"]
else:
@@ -612,8 +630,8 @@ def groups_table(
etud_info["_nom_disp_order"] = etud_sort_key(etud_info)
etud_info["_prenom_target"] = fiche_url
- etud_info["_nom_disp_td_attrs"] = 'id="%s" class="etudinfo"' % (
- etud_info["etudid"]
+ etud_info["_nom_disp_td_attrs"] = (
+ f"""id="{etud_info['etudid']}" class="etudinfo" """
)
etud_info["bourse_str"] = "oui" if etud_info["boursier"] else "non"
if etud_info["etat"] == "D":
@@ -720,6 +738,7 @@ def groups_table(
if groups_infos.members:
options = {
"with_codes": "Affiche codes",
+ "with_date_inscription": "Date inscription",
}
if can_view_etud_data:
options.update(
@@ -824,6 +843,7 @@ def groups_table(
groups_infos.members,
partitions=groups_infos.partitions,
with_codes=with_codes,
+ with_date_inscription=with_date_inscription,
with_paiement=with_paiement,
server_name=request.url_root,
)
diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py
index b7083eea..72c9d705 100644
--- a/app/scodoc/sco_preferences.py
+++ b/app/scodoc/sco_preferences.py
@@ -369,10 +369,23 @@ class BasePreferences:
"emails_notifications",
{
"initvalue": "",
- "title": "e-mails à qui notifier les opérations",
+ "title": "e-mail(s) à qui notifier les opérations",
"size": 70,
- "explanation": """adresses séparées par des virgules; notifie les opérations
- (saisies de notes, etc).
+ "explanation": """optionnel; adresses séparées par des virgules;
+ notifie les opérations (saisies de notes, etc).
+ """,
+ "category": "general",
+ "only_global": False, # peut être spécifique à un semestre
+ },
+ ),
+ (
+ "emails_notifications_inscriptions",
+ {
+ "initvalue": "",
+ "title": "e-mail(s) à qui notifier les inscriptions d'étudiants",
+ "size": 70,
+ "explanation": """optionnel; adresses séparées par des virgules;
+ notifie les inscriptions/désincriptions de chaque individu.
""",
"category": "general",
"only_global": False, # peut être spécifique à un semestre
@@ -2320,6 +2333,7 @@ class BasePreferences:
+
"""
descr["explanation"] = menu_global
diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index 3f5f923b..0e5a93fd 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -1843,6 +1843,15 @@ div.formsemestre_status {
/* EMO_WARNING, "⚠️" */
}
+table.formsemestre_status_head {
+ border-collapse: collapse;
+
+}
+
+table.formsemestre_status_head tr td:nth-child(2) {
+ padding-left: 1em;
+}
+
table.formsemestre_status {
border-collapse: collapse;
}
@@ -3310,6 +3319,12 @@ li.tf-msg {
padding-bottom: 5px;
}
+.pref-comment {
+ font-style: italic;
+ font-size: small;
+ color: var(--sco-color-explication);
+}
+
div.formsemestre-warning-box {
background-color: yellow;
border-radius: 4px;
diff --git a/app/static/js/groups_view.js b/app/static/js/groups_view.js
index 7f16a580..43eb694b 100644
--- a/app/static/js/groups_view.js
+++ b/app/static/js/groups_view.js
@@ -48,13 +48,12 @@ function change_list_options(selected_options) {
"with_archives",
"with_annotations",
"with_codes",
+ "with_date_inscription",
"with_bourse",
];
for (var i = 0; i < options.length; i++) {
- var option = options[i];
- if ($.inArray(option, selected_options) >= 0) {
- urlParams.set(option, "1");
- }
+ let option = options[i];
+ urlParams.set(option, selected_options.indexOf(option) >= 0 ? "1" : "0");
}
window.location = url.href;
}
@@ -62,23 +61,32 @@ function change_list_options(selected_options) {
// Menu choix groupe:
function toggle_visible_etuds() {
//
- $(".etud_elem").hide();
+ document.querySelectorAll('.etud_elem').forEach(element => {
+ element.style.display = 'none';
+ });
var qargs = "";
- $("#group_ids_sel option:selected").each(function (index, opt) {
+ var selectedOptions = document.querySelectorAll("#group_ids_sel option:checked");
+ var qargs = "";
+ selectedOptions.forEach(function (opt) {
var group_id = opt.value;
- $(".group-" + group_id).show();
+ var groupElements = document.querySelectorAll(".group-" + group_id);
+ groupElements.forEach(function (elem) {
+ elem.style.display = "block";
+ });
qargs += "&group_ids=" + group_id;
});
// Update url saisie tableur:
- var input_eval = $("#formnotes_evaluation_id");
+ let input_eval = document.querySelectorAll("#formnotes_evaluation_id");
if (input_eval.length > 0) {
- var evaluation_id = input_eval[0].value;
- $("#menu_saisie_tableur a").attr(
+ let evaluation_id = input_eval[0].value;
+ let menu_saisie_tableur_a = document.querySelector("#menu_saisie_tableur a");
+ menu_saisie_tableur_a.setAttribute(
"href",
"saisie_notes_tableur?evaluation_id=" + evaluation_id + qargs
);
// lien feuille excel:
- $("#lnk_feuille_saisie").attr(
+ let lnk_feuille_saisie = document.querySelector("#lnk_feuille_saisie");
+ lnk_feuille_saisie.setAttribute(
"href",
"feuille_saisie_notes?evaluation_id=" + evaluation_id + qargs
);
diff --git a/app/templates/formsemestre/edit_description.j2 b/app/templates/formsemestre/edit_description.j2
new file mode 100644
index 00000000..aadfe8ee
--- /dev/null
+++ b/app/templates/formsemestre/edit_description.j2
@@ -0,0 +1,130 @@
+{% extends "sco_page.j2" %}
+{% import 'wtf.j2' as wtf %}
+
+{% block styles %}
+{{super()}}
+
+{% endblock %}
+
+{% macro render_string_field(field, size=64) %}
+
+ {{ field.label }} :
+ {{ field(size=size)|safe }}
+ {% for error in field.errors %}
+ {{ error }}
+ {% endfor %}
+ {% if field.description %}
+ {{ field.description }}
+ {% endif %}
+
+{% endmacro %}
+
+{% macro render_textarea_field(field, cols=80, rows=12) %}
+
+ {{ field.label }} :
+ {{ field(cols=cols, rows=rows)|safe }}
+ {% if field.description %}
+ {{ field.description }}
+ {% endif %}
+
+{% endmacro %}
+
+{% block app_content %}
+
+
+ Édition de la description du semestre
+
+
+ Les informations saisies ici ne sont pas utilisées par ScoDoc mais
+ mises à disposition des applications tierces comme AutoSco.
+
+ Tous les champs sont optionnels.
+
+
+
+
+
+
+{% endblock %}
diff --git a/app/views/notes_formsemestre.py b/app/views/notes_formsemestre.py
index 4868d2ba..e3e25c90 100644
--- a/app/views/notes_formsemestre.py
+++ b/app/views/notes_formsemestre.py
@@ -25,20 +25,34 @@
##############################################################################
"""
-Vues "modernes" des formsemestre
+Vues "modernes" des formsemestres
Emmanuel Viennet, 2023
"""
+import datetime
+import io
+
from flask import flash, redirect, render_template, url_for
-from flask import g, request
+from flask import current_app, g, request
+import PIL
from app import db, log
from app.decorators import (
scodoc,
permission_required,
)
-from app.forms.formsemestre import change_formation, edit_modimpls_codes_apo
-from app.models import Formation, FormSemestre, ScoDocSiteConfig
+from app.forms.formsemestre import (
+ change_formation,
+ edit_modimpls_codes_apo,
+ edit_description,
+)
+from app.models import (
+ Formation,
+ FormSemestre,
+ FormSemestreDescription,
+ FORMSEMESTRE_DISPOSITIFS,
+ ScoDocSiteConfig,
+)
from app.scodoc import (
sco_edt_cal,
sco_formations,
@@ -223,3 +237,121 @@ def formsemestre_edt_help_config(formsemestre_id: int):
ScoDocSiteConfig=ScoDocSiteConfig,
title="Aide configuration EDT",
)
+
+
+@bp.route(
+ "/formsemestre_description//edit", methods=["GET", "POST"]
+)
+@scodoc
+@permission_required(Permission.EditFormSemestre)
+def edit_formsemestre_description(formsemestre_id: int):
+ "Edition de la description d'un formsemestre"
+ formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
+ if not formsemestre.description:
+ formsemestre.description = FormSemestreDescription()
+ db.session.add(formsemestre)
+ db.session.commit()
+ formsemestre_description = formsemestre.description
+ form = edit_description.FormSemestreDescriptionForm(obj=formsemestre_description)
+ ok = True
+ if form.validate_on_submit():
+ if form.cancel.data: # cancel button
+ return redirect(
+ url_for(
+ "notes.formsemestre_editwithmodules",
+ scodoc_dept=g.scodoc_dept,
+ formsemestre_id=formsemestre.id,
+ )
+ )
+ # Vérification valeur dispositif
+ if form.dispositif.data not in FORMSEMESTRE_DISPOSITIFS:
+ flash("Dispositif inconnu", "danger")
+ ok = False
+
+ # Vérification dates inscriptions
+ if form.date_debut_inscriptions.data:
+ try:
+ date_debut_inscriptions_dt = datetime.datetime.strptime(
+ form.date_debut_inscriptions.data, scu.DATE_FMT
+ )
+ except ValueError:
+ flash("Date de début des inscriptions invalide", "danger")
+ form.set_error("date début invalide", form.date_debut_inscriptions)
+ ok = False
+ else:
+ date_debut_inscriptions_dt = None
+ if form.date_fin_inscriptions.data:
+ try:
+ date_fin_inscriptions_dt = datetime.datetime.strptime(
+ form.date_fin_inscriptions.data, scu.DATE_FMT
+ )
+ except ValueError:
+ flash("Date de fin des inscriptions invalide", "danger")
+ form.set_error("date fin invalide", form.date_fin_inscriptions)
+ ok = False
+ else:
+ date_fin_inscriptions_dt = None
+ if ok:
+ # dates converties
+ form.date_debut_inscriptions.data = date_debut_inscriptions_dt
+ form.date_fin_inscriptions.data = date_fin_inscriptions_dt
+ # Affecte tous les champs sauf les images:
+ form_image = form.image
+ del form.image
+ form_photo_ens = form.photo_ens
+ del form.photo_ens
+ form.populate_obj(formsemestre_description)
+ # Affecte les images:
+ for field, form_field in (
+ ("image", form_image),
+ ("photo_ens", form_photo_ens),
+ ):
+ if form_field.data:
+ image_data = form_field.data.read()
+ max_length = current_app.config.get("MAX_CONTENT_LENGTH")
+ if max_length and len(image_data) > max_length:
+ flash(
+ f"Image trop grande ({field}), max {max_length} octets",
+ "danger",
+ )
+ return redirect(
+ url_for(
+ "notes.edit_formsemestre_description",
+ formsemestre_id=formsemestre.id,
+ scodoc_dept=g.scodoc_dept,
+ )
+ )
+ try:
+ _ = PIL.Image.open(io.BytesIO(image_data))
+ except PIL.UnidentifiedImageError:
+ flash(
+ f"Image invalide ({field}), doit être une image",
+ "danger",
+ )
+ return redirect(
+ url_for(
+ "notes.edit_formsemestre_description",
+ formsemestre_id=formsemestre.id,
+ scodoc_dept=g.scodoc_dept,
+ )
+ )
+ setattr(formsemestre_description, field, image_data)
+
+ db.session.commit()
+ flash("Description enregistrée", "success")
+ return redirect(
+ url_for(
+ "notes.formsemestre_status",
+ formsemestre_id=formsemestre.id,
+ scodoc_dept=g.scodoc_dept,
+ )
+ )
+
+ return render_template(
+ "formsemestre/edit_description.j2",
+ form=form,
+ formsemestre=formsemestre,
+ formsemestre_description=formsemestre_description,
+ sco=ScoData(formsemestre=formsemestre),
+ title="Modif. description semestre",
+ )
diff --git a/app/views/scolar.py b/app/views/scolar.py
index e6c840cd..455388aa 100644
--- a/app/views/scolar.py
+++ b/app/views/scolar.py
@@ -471,18 +471,24 @@ def groups_lists(
fmt="html",
# Options pour listes:
with_codes=0,
+ with_date_inscription=0,
etat=None,
- with_paiement=0, # si vrai, ajoute colonnes infos paiement droits et finalisation inscription (lent car interrogation portail)
- with_archives=0, # ajoute colonne avec noms fichiers archivés
+ with_paiement=0,
+ with_archives=0,
with_annotations=0,
with_bourse=0,
formsemestre_id=None,
):
- "Listes des étudiants des groupes"
+ """Listes des étudiants des groupes.
+ Si with_paiement, ajoute colonnes infos paiement droits et finalisation
+ inscription (lent car interrogation portail).
+ Si with_archives, ajoute colonne avec noms fichiers archivés.
+ """
return sco_groups_view.groups_lists(
group_ids=group_ids,
fmt=fmt,
with_codes=with_codes,
+ with_date_inscription=with_date_inscription,
etat=etat,
with_paiement=with_paiement,
with_archives=with_archives,
diff --git a/migrations/versions/2640b7686de6_formsemestre_description.py b/migrations/versions/2640b7686de6_formsemestre_description.py
new file mode 100644
index 00000000..c2533093
--- /dev/null
+++ b/migrations/versions/2640b7686de6_formsemestre_description.py
@@ -0,0 +1,51 @@
+"""FormSemestreDescription et capacité d'accueil
+
+Revision ID: 2640b7686de6
+Revises: f6cb3d4e44ec
+Create Date: 2024-08-11 15:44:32.560054
+
+"""
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = "2640b7686de6"
+down_revision = "f6cb3d4e44ec"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ op.create_table(
+ "notes_formsemestre_description",
+ sa.Column("id", sa.Integer(), nullable=False),
+ sa.Column("description", sa.Text(), server_default="", nullable=False),
+ sa.Column("horaire", sa.Text(), server_default="", nullable=False),
+ sa.Column("date_debut_inscriptions", sa.DateTime(timezone=True), nullable=True),
+ sa.Column("date_fin_inscriptions", sa.DateTime(timezone=True), nullable=True),
+ sa.Column("wip", sa.Boolean(), server_default="false", nullable=False),
+ sa.Column("image", sa.LargeBinary(), nullable=True),
+ sa.Column("campus", sa.Text(), server_default="", nullable=False),
+ sa.Column("salle", sa.Text(), server_default="", nullable=False),
+ sa.Column("dispositif", sa.Integer(), server_default="0", nullable=False),
+ sa.Column("modalites_mcc", sa.Text(), server_default="", nullable=False),
+ sa.Column("photo_ens", sa.LargeBinary(), nullable=True),
+ sa.Column("public", sa.Text(), server_default="", nullable=False),
+ sa.Column("prerequis", sa.Text(), server_default="", nullable=False),
+ sa.Column("responsable", sa.Text(), server_default="", nullable=False),
+ sa.Column("formsemestre_id", sa.Integer(), nullable=False),
+ sa.ForeignKeyConstraint(
+ ["formsemestre_id"], ["notes_formsemestre.id"], ondelete="CASCADE"
+ ),
+ sa.PrimaryKeyConstraint("id"),
+ )
+ with op.batch_alter_table("notes_formsemestre", schema=None) as batch_op:
+ batch_op.add_column(sa.Column("capacite_accueil", sa.Integer(), nullable=True))
+
+
+def downgrade():
+ with op.batch_alter_table("notes_formsemestre", schema=None) as batch_op:
+ batch_op.drop_column("capacite_accueil")
+ op.drop_table("notes_formsemestre_description")
diff --git a/tests/api/setup_test_api.py b/tests/api/setup_test_api.py
index 2ce97eb8..126162e2 100644
--- a/tests/api/setup_test_api.py
+++ b/tests/api/setup_test_api.py
@@ -122,9 +122,14 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None, raw=False):
if reply.headers.get("Content-Type", None) == "application/json":
return reply.json() # decode la reponse JSON
if reply.headers.get("Content-Type", None) in [
- "image/jpg",
- "image/png",
"application/pdf",
+ "application/vnd.ms-excel",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ "image/gif",
+ "image/jpeg",
+ "image/png",
+ "image/webp",
]:
retval = {
"Content-Type": reply.headers.get("Content-Type", None),
@@ -132,7 +137,7 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None, raw=False):
}
return retval
raise APIError(
- "Unknown returned content {r.headers.get('Content-Type', None} !\n",
+ f"Unknown returned content {reply.headers.get('Content-Type', None)} !\n",
status_code=reply.status_code,
)
diff --git a/tests/api/test_api_formsemestre.py b/tests/api/test_api_formsemestre.py
index 23d55ccc..1395a6a3 100644
--- a/tests/api/test_api_formsemestre.py
+++ b/tests/api/test_api_formsemestre.py
@@ -16,6 +16,7 @@ Utilisation :
Lancer :
pytest tests/api/test_api_formsemestre.py
"""
+import base64
import json
import requests
from types import NoneType
@@ -28,8 +29,10 @@ from tests.api.setup_test_api import (
API_URL,
CHECK_CERTIFICATE,
GET,
+ POST,
api_headers,
api_admin_headers,
+ set_headers,
)
from tests.api.tools_test_api import (
@@ -780,3 +783,64 @@ def _compare_formsemestre_resultat(res: list[dict], ref: list[dict]):
if "nbabs" in k:
continue
assert res_d[k] == ref_d[k], f"values for key {k} differ."
+
+
+def test_formsemestre_description(api_admin_headers):
+ """
+ Test accès et modification de la description
+ """
+ set_headers(api_admin_headers)
+ formsemestre_id = 1
+ r = GET(f"/formsemestre/{formsemestre_id}")
+ assert "description" not in r
+ r = POST(
+ f"/formsemestre/{formsemestre_id}/description/edit",
+ data={
+ "description": "une description",
+ "horaire": "un horaire",
+ "salle": "une salle",
+ "dispositif": 1,
+ "wip": True,
+ },
+ )
+ assert r["description"] == "une description"
+ assert r["horaire"] == "un horaire"
+ assert r["salle"] == "une salle"
+ assert r["dispositif"] == 1
+ assert r["wip"] is True
+ r = GET(f"/formsemestre/{formsemestre_id}/description")
+ assert r["description"] == "une description"
+ assert r["horaire"] == "un horaire"
+ assert r["salle"] == "une salle"
+ assert r["dispositif"] == 1
+ assert r["wip"] is True
+ # La réponse ne contient pas les images, servies à part:
+ assert "image" not in r
+ assert "photo_ens" not in r
+ r = POST(
+ f"/formsemestre/{formsemestre_id}/description/edit",
+ data={
+ "description": "",
+ "horaire": "",
+ "salle": "",
+ "dispositif": 0,
+ "wip": False,
+ },
+ )
+ assert r["description"] == ""
+ assert r["horaire"] == ""
+ assert r["salle"] == ""
+ assert r["dispositif"] == 0
+ assert r["wip"] is False
+ # Upload image
+ with open("tests/ressources/images/papillon.jpg", "rb") as f:
+ img = f.read()
+ img_base64 = base64.b64encode(img).decode("utf-8")
+ r = POST(
+ f"/formsemestre/{formsemestre_id}/description/edit", data={"image": img_base64}
+ )
+ assert r["wip"] is False
+ r = GET(f"/formsemestre/{formsemestre_id}/description/image", raw=True)
+ assert r.status_code == 200
+ assert r.headers.get("Content-Type") == "image/jpeg"
+ assert r.content == img
diff --git a/tests/api/test_api_permissions.py b/tests/api/test_api_permissions.py
index ae3dad92..f94ce12e 100755
--- a/tests/api/test_api_permissions.py
+++ b/tests/api/test_api_permissions.py
@@ -100,7 +100,7 @@ def test_permissions(api_headers):
verify=CHECK_CERTIFICATE,
timeout=scu.SCO_TEST_API_TIMEOUT,
)
- assert r.status_code == 200
+ assert r.status_code // 100 == 2 # 2xx success
# Même chose sans le jeton:
for rule in api_rules:
diff --git a/tests/unit/test_formsemestre.py b/tests/unit/test_formsemestre.py
index 928aae83..783c1232 100644
--- a/tests/unit/test_formsemestre.py
+++ b/tests/unit/test_formsemestre.py
@@ -3,12 +3,12 @@
""" Test création/accès/clonage formsemestre
"""
-from flask import Response
+from flask import g, Response
import pytest
-from tests.unit import yaml_setup, call_view
import app
-from app.models import Formation, FormSemestre
+from app import db
+from app.models import Formation, FormSemestre, FormSemestreDescription
from app.scodoc import (
sco_archives_formsemestre,
sco_cost_formation,
@@ -35,6 +35,7 @@ from app.scodoc import (
from app.scodoc import sco_utils as scu
from app.views import notes, scolar
from config import TestConfig
+from tests.unit import yaml_setup, call_view
DEPT = TestConfig.DEPT_TEST
@@ -205,3 +206,39 @@ def test_formsemestre_misc_views(test_client):
ans = sco_debouche.report_debouche_date(start_year=2000)
ans = sco_cost_formation.formsemestre_estim_cost(formsemestre.id)
# pas de test des indicateurs de suivi BUT
+
+
+def test_formsemestre_description(test_client):
+ """Test FormSemestreDescription"""
+ app.set_sco_dept(DEPT)
+ #
+ nb_descriptions = FormSemestreDescription.query.count()
+ # Création d'un semestre
+
+ formsemestre = FormSemestre(
+ dept_id=g.scodoc_dept_id,
+ titre="test description",
+ date_debut="2024-08-01",
+ date_fin="2024-08-31",
+ )
+ db.session.add(formsemestre)
+ db.session.commit()
+ assert formsemestre.description is None
+ # Association d'une description
+ formsemestre.description = FormSemestreDescription.create_from_dict(
+ {
+ "description": "Description",
+ "responsable": "Responsable",
+ "campus": "Sorbonne",
+ "salle": "L214",
+ "horaire": "23h à l'aube",
+ }
+ )
+ db.session.add(formsemestre)
+ db.session.commit()
+ assert formsemestre.description.formsemestre.id == formsemestre.id
+ assert FormSemestreDescription.query.count() == nb_descriptions + 1
+ # Suppression / cascade
+ db.session.delete(formsemestre)
+ db.session.commit()
+ assert FormSemestreDescription.query.count() == nb_descriptions
| |