# -*- coding: utf-8 -*- ############################################################################## # # ScoDoc # # Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved. # # 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 # ############################################################################## """ Vues "modernes" des formsemestres Emmanuel Viennet, 2023 """ import datetime import io from flask import flash, redirect, render_template, url_for 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, edit_description, ) from app.models import ( Formation, FormSemestre, FormSemestreDescription, FORMSEMESTRE_DISPOSITIFS, ScoDocSiteConfig, ) from app.scodoc import ( sco_edt_cal, sco_formations, sco_formation_versions, sco_groups_view, ) from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_permissions import Permission from app.scodoc import sco_utils as scu from app.views import notes_bp as bp from app.views import ScoData @bp.route( "/formsemestre_change_formation/", methods=["GET", "POST"] ) @scodoc @permission_required(Permission.EditFormSemestre) 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) formation_dict = sco_formations.formation_export_dict( 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 and sco_formation_versions.formations_are_equals( 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() sco_formation_versions.formsemestre_change_formation( 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), ) @bp.route( "/formsemestre_edit_modimpls_codes/", 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), ) @bp.route("/formsemestre/edt/") @scodoc @permission_required(Permission.ScoView) def formsemestre_edt(formsemestre_id: int): """Expérimental: affiche emploi du temps du semestre""" current_date = request.args.get("current_date") show_modules_titles = scu.to_bool(request.args.get("show_modules_titles", False)) 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") formsemestre = FormSemestre.get_formsemestre(formsemestre_id) 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" 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, ) return render_template( "formsemestre/edt.j2", current_date=current_date, formsemestre=formsemestre, hour_start=hour_start, hour_end=hour_end, 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, sco=ScoData(formsemestre=formsemestre), show_modules_titles=show_modules_titles, title=f"EDT S{formsemestre.semestre_id} {formsemestre.titre_formation()}", view=view, views_names=views_names, ) @bp.route("/formsemestre/edt_help_config/") @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), 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", )