2023-03-21 21:14:38 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
##############################################################################
|
|
|
|
#
|
|
|
|
# ScoDoc
|
|
|
|
#
|
2023-12-31 23:04:06 +01:00
|
|
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
2023-03-21 21:14:38 +01:00
|
|
|
#
|
|
|
|
# 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
|
|
|
|
#
|
|
|
|
##############################################################################
|
|
|
|
|
|
|
|
"""
|
2024-08-11 21:39:43 +02:00
|
|
|
Vues "modernes" des formsemestres
|
2023-03-21 21:14:38 +01:00
|
|
|
Emmanuel Viennet, 2023
|
|
|
|
"""
|
|
|
|
|
2024-08-13 16:47:55 +02:00
|
|
|
import datetime
|
2024-08-14 15:39:57 +02:00
|
|
|
import io
|
2024-08-13 16:47:55 +02:00
|
|
|
|
2023-03-22 21:39:55 +01:00
|
|
|
from flask import flash, redirect, render_template, url_for
|
2024-08-11 21:39:43 +02:00
|
|
|
from flask import current_app, g, request
|
2024-08-14 15:39:57 +02:00
|
|
|
import PIL
|
2023-03-21 21:14:38 +01:00
|
|
|
|
2023-11-12 19:58:55 +01:00
|
|
|
from app import db, log
|
2023-03-21 21:14:38 +01:00
|
|
|
from app.decorators import (
|
|
|
|
scodoc,
|
|
|
|
permission_required,
|
|
|
|
)
|
2024-10-10 00:41:20 +02:00
|
|
|
from app.formations import formation_io, formation_versions
|
2024-08-11 21:39:43 +02:00
|
|
|
from app.forms.formsemestre import (
|
|
|
|
change_formation,
|
|
|
|
edit_modimpls_codes_apo,
|
|
|
|
edit_description,
|
|
|
|
)
|
|
|
|
from app.models import (
|
|
|
|
Formation,
|
|
|
|
FormSemestre,
|
|
|
|
FormSemestreDescription,
|
2024-08-13 16:47:55 +02:00
|
|
|
FORMSEMESTRE_DISPOSITIFS,
|
2024-08-11 21:39:43 +02:00
|
|
|
ScoDocSiteConfig,
|
|
|
|
)
|
2023-12-24 16:09:07 +01:00
|
|
|
from app.scodoc import (
|
|
|
|
sco_edt_cal,
|
|
|
|
sco_groups_view,
|
|
|
|
)
|
2024-01-06 14:51:48 +01:00
|
|
|
from app.scodoc.sco_exceptions import ScoValueError
|
2023-03-21 21:14:38 +01:00
|
|
|
from app.scodoc.sco_permissions import Permission
|
2023-12-24 16:09:07 +01:00
|
|
|
from app.scodoc import sco_utils as scu
|
2023-03-21 21:14:38 +01:00
|
|
|
from app.views import notes_bp as bp
|
|
|
|
from app.views import ScoData
|
|
|
|
|
|
|
|
|
|
|
|
@bp.route(
|
|
|
|
"/formsemestre_change_formation/<int:formsemestre_id>", methods=["GET", "POST"]
|
|
|
|
)
|
|
|
|
@scodoc
|
2023-09-29 21:17:31 +02:00
|
|
|
@permission_required(Permission.EditFormSemestre)
|
2023-03-21 21:14:38 +01:00
|
|
|
def formsemestre_change_formation(formsemestre_id: int):
|
|
|
|
"""Propose de changer un formsemestre de formation.
|
|
|
|
Cette opération est bien sûr impossible... sauf si les deux formations sont identiques.
|
|
|
|
Par exemple, on vient de créer une formation, et on a oublié d'y associé un formsemestre
|
|
|
|
existant.
|
|
|
|
"""
|
|
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
2024-10-10 00:41:20 +02:00
|
|
|
formation_dict = formation_io.formation_export_dict(
|
2023-03-21 21:14:38 +01:00
|
|
|
formsemestre.formation, export_external_ues=True, ue_reference_style="acronyme"
|
|
|
|
)
|
|
|
|
formations = [
|
|
|
|
formation
|
|
|
|
for formation in Formation.query.filter_by(
|
|
|
|
dept_id=formsemestre.dept_id, acronyme=formsemestre.formation.acronyme
|
|
|
|
)
|
|
|
|
if formation.id != formsemestre.formation.id
|
2024-10-10 00:41:20 +02:00
|
|
|
and formation_versions.formations_are_equals(
|
2023-03-21 21:14:38 +01:00
|
|
|
formation, formation2_dict=formation_dict
|
|
|
|
)
|
|
|
|
]
|
|
|
|
form = change_formation.gen_formsemestre_change_formation_form(formations)
|
|
|
|
if request.method == "POST" and form.validate:
|
|
|
|
if not form.cancel.data:
|
|
|
|
new_formation_id = form.radio_but.data
|
|
|
|
if new_formation_id is None: # pas de choix radio
|
|
|
|
flash("Pas de formation sélectionnée !")
|
|
|
|
return render_template(
|
|
|
|
"formsemestre/change_formation.j2",
|
|
|
|
form=form,
|
|
|
|
formations=formations,
|
|
|
|
formsemestre=formsemestre,
|
|
|
|
sco=ScoData(formsemestre=formsemestre),
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
new_formation: Formation = Formation.query.filter_by(
|
|
|
|
dept_id=g.scodoc_dept_id, formation_id=new_formation_id
|
|
|
|
).first_or_404()
|
2024-10-10 00:41:20 +02:00
|
|
|
formation_versions.formsemestre_change_formation(
|
2023-03-21 21:14:38 +01:00
|
|
|
formsemestre, new_formation
|
|
|
|
)
|
|
|
|
flash("Formation du semestre modifiée")
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"notes.formsemestre_status",
|
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
# GET
|
|
|
|
return render_template(
|
|
|
|
"formsemestre/change_formation.j2",
|
|
|
|
form=form,
|
|
|
|
formations=formations,
|
|
|
|
formsemestre=formsemestre,
|
|
|
|
sco=ScoData(formsemestre=formsemestre),
|
|
|
|
)
|
2023-11-11 18:13:18 +01:00
|
|
|
|
|
|
|
|
2023-11-12 19:58:55 +01:00
|
|
|
@bp.route(
|
|
|
|
"/formsemestre_edit_modimpls_codes/<int:formsemestre_id>", methods=["GET", "POST"]
|
|
|
|
)
|
|
|
|
@scodoc
|
|
|
|
@permission_required(Permission.EditFormSemestre)
|
|
|
|
def formsemestre_edit_modimpls_codes(formsemestre_id: int):
|
|
|
|
"""Edition des codes Apogée et EDT"""
|
|
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
|
|
form = edit_modimpls_codes_apo.EditModimplsCodesForm(formsemestre)
|
|
|
|
|
|
|
|
if request.method == "POST" and form.validate:
|
|
|
|
if not form.cancel.data:
|
|
|
|
# record codes
|
|
|
|
for modimpl in formsemestre.modimpls_sorted:
|
|
|
|
field_apo = getattr(form, f"modimpl_apo_{modimpl.id}")
|
|
|
|
field_edt = getattr(form, f"modimpl_edt_{modimpl.id}")
|
|
|
|
if field_apo and field_edt:
|
|
|
|
modimpl.code_apogee = field_apo.data.strip() or None
|
|
|
|
modimpl.edt_id = field_edt.data.strip() or None
|
|
|
|
log(f"setting codes for {modimpl}: apo={field_apo} edt={field_edt}")
|
|
|
|
db.session.add(modimpl)
|
|
|
|
db.session.commit()
|
|
|
|
flash("Codes enregistrés")
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"notes.formsemestre_status",
|
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
# GET
|
|
|
|
for modimpl in formsemestre.modimpls_sorted:
|
|
|
|
field_apo = getattr(form, f"modimpl_apo_{modimpl.id}")
|
|
|
|
field_edt = getattr(form, f"modimpl_edt_{modimpl.id}")
|
|
|
|
field_apo.data = modimpl.code_apogee or ""
|
|
|
|
field_edt.data = modimpl.edt_id or ""
|
|
|
|
return render_template(
|
|
|
|
"formsemestre/edit_modimpls_codes.j2",
|
|
|
|
form=form,
|
|
|
|
formsemestre=formsemestre,
|
|
|
|
sco=ScoData(formsemestre=formsemestre),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-11-11 18:13:18 +01:00
|
|
|
@bp.route("/formsemestre/edt/<int:formsemestre_id>")
|
|
|
|
@scodoc
|
|
|
|
@permission_required(Permission.ScoView)
|
|
|
|
def formsemestre_edt(formsemestre_id: int):
|
|
|
|
"""Expérimental: affiche emploi du temps du semestre"""
|
2024-01-06 14:51:48 +01:00
|
|
|
|
2024-01-05 14:10:12 +01:00
|
|
|
current_date = request.args.get("current_date")
|
2023-12-24 16:09:07 +01:00
|
|
|
show_modules_titles = scu.to_bool(request.args.get("show_modules_titles", False))
|
2024-01-06 14:51:48 +01:00
|
|
|
view = request.args.get("view", "week")
|
|
|
|
views_names = {"day": "Jour", "month": "Mois", "week": "Semaine"}
|
|
|
|
if view not in views_names:
|
|
|
|
raise ScoValueError("valeur invalide pour le paramètre view")
|
2023-11-11 18:13:18 +01:00
|
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
2023-11-13 11:30:26 +01:00
|
|
|
cfg = ScoDocSiteConfig.query.filter_by(name="assi_morning_time").first()
|
|
|
|
hour_start = cfg.value.split(":")[0].lstrip(" 0") if cfg else "7"
|
|
|
|
cfg = ScoDocSiteConfig.query.filter_by(name="assi_afternoon_time").first()
|
|
|
|
hour_end = cfg.value.split(":")[0].lstrip(" 0") if cfg else "18"
|
2023-11-16 23:34:47 +01:00
|
|
|
group_ids = request.args.getlist("group_ids", int)
|
|
|
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
|
|
|
group_ids=group_ids,
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
empty_list_select_all=False,
|
|
|
|
)
|
2023-11-11 18:13:18 +01:00
|
|
|
return render_template(
|
|
|
|
"formsemestre/edt.j2",
|
2024-01-05 14:10:12 +01:00
|
|
|
current_date=current_date,
|
2023-11-11 18:13:18 +01:00
|
|
|
formsemestre=formsemestre,
|
2023-11-13 11:30:26 +01:00
|
|
|
hour_start=hour_start,
|
|
|
|
hour_end=hour_end,
|
2023-11-16 23:34:47 +01:00
|
|
|
form_groups_choice=sco_groups_view.form_groups_choice(
|
|
|
|
groups_infos,
|
|
|
|
submit_on_change=True,
|
|
|
|
default_deselect_others=False,
|
|
|
|
with_deselect_butt=True,
|
|
|
|
),
|
|
|
|
groups_query_args=groups_infos.groups_query_args,
|
2023-11-11 18:13:18 +01:00
|
|
|
sco=ScoData(formsemestre=formsemestre),
|
2023-12-24 16:09:07 +01:00
|
|
|
show_modules_titles=show_modules_titles,
|
2024-01-06 14:51:48 +01:00
|
|
|
title=f"EDT S{formsemestre.semestre_id} {formsemestre.titre_formation()}",
|
|
|
|
view=view,
|
|
|
|
views_names=views_names,
|
2023-12-24 16:09:07 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@bp.route("/formsemestre/edt_help_config/<int:formsemestre_id>")
|
|
|
|
@scodoc
|
|
|
|
@permission_required(Permission.ScoView)
|
|
|
|
def formsemestre_edt_help_config(formsemestre_id: int):
|
|
|
|
"""Page d'aide à la configuration de l'extraction emplois du temps
|
|
|
|
Affiche les identifiants extraits de l'ics et ceux de ScoDoc.
|
|
|
|
"""
|
|
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
|
|
edt2group = sco_edt_cal.formsemestre_retreive_groups_from_edt_id(formsemestre)
|
|
|
|
events_sco, edt_groups_ids = sco_edt_cal.load_and_convert_ics(formsemestre)
|
|
|
|
return render_template(
|
|
|
|
"formsemestre/edt_help_config.j2",
|
|
|
|
formsemestre=formsemestre,
|
|
|
|
edt2group=edt2group,
|
|
|
|
edt_groups_ids=edt_groups_ids,
|
|
|
|
events_sco=events_sco,
|
|
|
|
sco=ScoData(formsemestre=formsemestre),
|
2023-12-28 23:05:19 +01:00
|
|
|
ScoDocSiteConfig=ScoDocSiteConfig,
|
|
|
|
title="Aide configuration EDT",
|
2023-11-11 18:13:18 +01:00
|
|
|
)
|
2024-08-11 21:39:43 +02:00
|
|
|
|
|
|
|
|
|
|
|
@bp.route(
|
|
|
|
"/formsemestre_description/<int:formsemestre_id>/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)
|
2024-08-13 16:47:55 +02:00
|
|
|
ok = True
|
2024-08-11 21:39:43 +02:00
|
|
|
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,
|
|
|
|
)
|
|
|
|
)
|
2024-08-13 16:47:55 +02:00
|
|
|
# Vérification valeur dispositif
|
|
|
|
if form.dispositif.data not in FORMSEMESTRE_DISPOSITIFS:
|
|
|
|
flash("Dispositif inconnu", "danger")
|
|
|
|
ok = False
|
2024-08-11 21:39:43 +02:00
|
|
|
|
2024-08-13 16:47:55 +02:00
|
|
|
# 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
|
2024-08-11 21:39:43 +02:00
|
|
|
)
|
2024-08-13 16:47:55 +02:00
|
|
|
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
|
2024-08-11 21:39:43 +02:00
|
|
|
)
|
2024-08-13 16:47:55 +02:00
|
|
|
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",
|
|
|
|
)
|
2024-08-14 15:39:57 +02:00
|
|
|
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",
|
|
|
|
)
|
2024-08-13 16:47:55 +02:00
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"notes.edit_formsemestre_description",
|
|
|
|
formsemestre_id=formsemestre.id,
|
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
setattr(formsemestre_description, field, image_data)
|
2024-08-11 21:39:43 +02:00
|
|
|
|
2024-08-13 16:47:55 +02:00
|
|
|
db.session.commit()
|
|
|
|
flash("Description enregistrée", "success")
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"notes.formsemestre_status",
|
|
|
|
formsemestre_id=formsemestre.id,
|
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
)
|
2024-08-11 21:39:43 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
return render_template(
|
|
|
|
"formsemestre/edit_description.j2",
|
|
|
|
form=form,
|
|
|
|
formsemestre=formsemestre,
|
|
|
|
formsemestre_description=formsemestre_description,
|
|
|
|
sco=ScoData(formsemestre=formsemestre),
|
2024-08-13 16:47:55 +02:00
|
|
|
title="Modif. description semestre",
|
2024-08-11 21:39:43 +02:00
|
|
|
)
|