diff --git a/app/models/__init__.py b/app/models/__init__.py
index d5b88d12c..242615472 100644
--- a/app/models/__init__.py
+++ b/app/models/__init__.py
@@ -52,7 +52,7 @@ class ScoDocModel(db.Model):
def create_from_dict(cls, data: dict) -> "ScoDocModel":
"""Create a new instance of the model with attributes given in dict.
The instance is added to the session (but not flushed nor committed).
- Use only relevant arributes for the given model and ignore others.
+ Use only relevant attributes for the given model and ignore others.
"""
if data:
args = cls.convert_dict_fields(cls.filter_model_attributes(data))
diff --git a/app/models/etudiants.py b/app/models/etudiants.py
index d7eb0312d..8502ff5d4 100644
--- a/app/models/etudiants.py
+++ b/app/models/etudiants.py
@@ -742,6 +742,7 @@ def check_etud_duplicate_code(args, code_name, edit=True):
Raises ScoGenError si problème.
"""
etudid = args.get("etudid", None)
+ assert (not edit) or (etudid is not None) # si edit, etudid doit être spécifié
if not args.get(code_name, None):
return
etuds = Identite.query.filter_by(
@@ -749,9 +750,7 @@ def check_etud_duplicate_code(args, code_name, edit=True):
).all()
duplicate = False
if edit:
- duplicate = (len(etuds) > 1) or (
- (len(etuds) == 1) and etuds[0].id != args["etudid"]
- )
+ duplicate = (len(etuds) > 1) or ((len(etuds) == 1) and etuds[0].id != etudid)
else:
duplicate = len(etuds) > 0
if duplicate:
diff --git a/app/models/moduleimpls.py b/app/models/moduleimpls.py
index 9cb168eb9..9d2c2b2c8 100644
--- a/app/models/moduleimpls.py
+++ b/app/models/moduleimpls.py
@@ -3,12 +3,13 @@
"""
import pandas as pd
from flask import abort, g
+from flask_login import current_user
from flask_sqlalchemy.query import Query
from app import db
from app.auth.models import User
from app.comp import df_cache
-from app.models import APO_CODE_STR_LEN
+from app.models import APO_CODE_STR_LEN, ScoDocModel
from app.models.etudiants import Identite
from app.models.evaluations import Evaluation
from app.models.modules import Module
@@ -17,7 +18,7 @@ from app.scodoc.sco_permissions import Permission
from app.scodoc import sco_utils as scu
-class ModuleImpl(db.Model):
+class ModuleImpl(ScoDocModel):
"""Mise en oeuvre d'un module pour une annee/semestre"""
__tablename__ = "notes_moduleimpl"
@@ -52,7 +53,6 @@ class ModuleImpl(db.Model):
secondary="notes_modules_enseignants",
lazy="dynamic",
backref="moduleimpl",
- viewonly=True,
)
def __repr__(self):
@@ -85,7 +85,7 @@ class ModuleImpl(db.Model):
@classmethod
def get_modimpl(cls, moduleimpl_id: int | str, dept_id: int = None) -> "ModuleImpl":
- """FormSemestre ou 404, cherche uniquement dans le département spécifié ou le courant."""
+ """ModuleImpl ou 404, cherche uniquement dans le département spécifié ou le courant."""
from app.models.formsemestre import FormSemestre
if not isinstance(moduleimpl_id, int):
@@ -187,7 +187,7 @@ class ModuleImpl(db.Model):
return allow_ens and user.id in (ens.id for ens in self.enseignants)
return True
- def can_change_ens_by(self, user: User, raise_exc=False) -> bool:
+ def can_change_responsable(self, user: User, raise_exc=False) -> bool:
"""Check if user can modify module resp.
If raise_exc, raises exception (AccessDenied or ScoLockedSemError) if not.
= Admin, et dir des etud. (si option l'y autorise)
@@ -208,6 +208,27 @@ class ModuleImpl(db.Model):
raise AccessDenied(f"Modification impossible pour {user}")
return False
+ def can_change_ens(self, user: User | None = None, raise_exc=True) -> bool:
+ """check if user can modify ens list (raise exception if not)"
+ if user is None, current user.
+ """
+ user = current_user if user is None else user
+ if not self.formsemestre.etat:
+ if raise_exc:
+ raise ScoLockedSemError("Modification impossible: semestre verrouille")
+ return False
+ # -- check access
+ # admin, resp. module ou resp. semestre
+ if (
+ user.id != self.responsable_id
+ and not user.has_permission(Permission.EditFormSemestre)
+ and user.id not in (u.id for u in self.formsemestre.responsables)
+ ):
+ if raise_exc:
+ raise AccessDenied(f"Modification impossible pour {user}")
+ return False
+ return True
+
def est_inscrit(self, etud: Identite) -> bool:
"""
Vérifie si l'étudiant est bien inscrit au moduleimpl (même si DEM ou DEF au semestre).
diff --git a/app/scodoc/html_sidebar.py b/app/scodoc/html_sidebar.py
index b64cefc60..af6454456 100755
--- a/app/scodoc/html_sidebar.py
+++ b/app/scodoc/html_sidebar.py
@@ -33,7 +33,7 @@ from flask import g, request
from flask_login import current_user
from app import db
-from app.models import Evaluation, GroupDescr, ModuleImpl, Partition
+from app.models import Evaluation, GroupDescr, Identite, ModuleImpl, Partition
import app.scodoc.sco_utils as scu
from app.scodoc import sco_preferences
from app.scodoc.sco_permissions import Permission
@@ -160,27 +160,32 @@ def sidebar(etudid: int = None):
etudid = request.form.get("etudid", None)
if etudid is not None:
- etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
- params.update(etud)
- params["fiche_url"] = url_for(
- "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
- )
+ etud = Identite.get_etud(etudid)
# compte les absences du semestre en cours
H.append(
- """
- Absences"""
- % params
+ f"""
+ Absences"""
)
- if etud["cursem"]:
- cur_sem = etud["cursem"]
- nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, cur_sem)
+ inscription = etud.inscription_courante()
+ if inscription:
+ formsemestre = inscription.formsemestre
+ nbabs, nbabsjust = sco_assiduites.formsemestre_get_assiduites_count(
+ etudid, formsemestre
+ )
nbabsnj = nbabs - nbabsjust
H.append(
- f"""({
- sco_preferences.get_preference("assi_metrique", None)})
+ f"""({
+ sco_preferences.get_preference("assi_metrique", None)})
{ nbabsjust } J., { nbabsnj } N.J."""
)
H.append("")
@@ -189,21 +194,24 @@ def sidebar(etudid: int = None):
cur_formsemestre_id = retreive_formsemestre_from_request()
H.append(
f"""
- - Ajouter
- - Justifier
+ - Ajouter
+ - Justifier
"""
)
if sco_preferences.get_preference("handle_billets_abs"):
H.append(
- f"""- Billets
"""
+ f"""- Billets
"""
)
H.append(
f"""
diff --git a/app/scodoc/sco_abs.py b/app/scodoc/sco_abs.py
deleted file mode 100755
index 2f55f3c64..000000000
--- a/app/scodoc/sco_abs.py
+++ /dev/null
@@ -1,1109 +0,0 @@
-# -*- mode: python -*-
-# -*- coding: utf-8 -*-
-
-##############################################################################
-#
-# Gestion scolarite IUT
-#
-# 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
-#
-##############################################################################
-
-"""Fonctions sur les absences
-"""
-
-import calendar
-import datetime
-import html
-import time
-
-from app.scodoc import notesdb as ndb
-from app import log
-from app.scodoc.scolog import logdb
-from app.scodoc.sco_exceptions import ScoValueError, ScoInvalidDateError
-from app.scodoc import sco_abs_notification
-from app.scodoc import sco_cache
-from app.scodoc import sco_etud
-from app.scodoc import sco_formsemestre_inscriptions
-from app.scodoc import sco_preferences
-import app.scodoc.sco_utils as scu
-
-# --- Misc tools.... ------------------
-
-
-def _isFarFutur(jour):
- # check si jour est dans le futur "lointain"
- # pour autoriser les saisies dans le futur mais pas a plus de 6 mois
- y, m, d = [int(x) for x in jour.split("-")]
- try:
- j = datetime.date(y, m, d)
- except ValueError:
- # les dates erronées, genre année 20022, sont considéres dans le futur
- return True
- # 6 mois ~ 182 jours:
- return j - datetime.date.today() > datetime.timedelta(182)
-
-
-def _toboolean(x):
- "convert a value to boolean"
- return bool(x)
-
-
-def is_work_saturday():
- "Vrai si le samedi est travaillé"
- return int(sco_preferences.get_preference("work_saturday"))
-
-
-def MonthNbDays(month, year):
- "returns nb of days in month"
- if month > 7:
- month = month + 1
- if month % 2:
- return 31
- elif month == 2:
- if calendar.isleap(year):
- return 29
- else:
- return 28
- else:
- return 30
-
-
-class ddmmyyyy(object):
- """immutable dates"""
-
- def __init__(self, date=None, fmt="ddmmyyyy", work_saturday=False):
- self.work_saturday = work_saturday
- if date is None:
- return
- try:
- if fmt == "ddmmyyyy":
- self.day, self.month, self.year = date.split("/")
- elif fmt == "iso":
- self.year, self.month, self.day = date.split("-")
- else:
- raise ValueError("invalid format spec. (%s)" % fmt)
- self.year = int(self.year)
- self.month = int(self.month)
- self.day = int(self.day)
- except ValueError:
- raise ScoValueError("date invalide: %s" % date)
- # accept years YYYY or YY, uses 1970 as pivot
- if self.year < 1970:
- if self.year > 100:
- raise ScoInvalidDateError("Année invalide: %s" % self.year)
- if self.year < 70:
- self.year = self.year + 2000
- else:
- self.year = self.year + 1900
- if self.month < 1 or self.month > 12:
- raise ScoInvalidDateError("Mois invalide: %s" % self.month)
-
- if self.day < 1 or self.day > MonthNbDays(self.month, self.year):
- raise ScoInvalidDateError("Jour invalide: %s" % self.day)
-
- # weekday in 0-6, where 0 is monday
- self.weekday = calendar.weekday(self.year, self.month, self.day)
-
- self.time = time.mktime((self.year, self.month, self.day, 0, 0, 0, 0, 0, 0))
-
- def iswork(self):
- "returns true if workable day"
- if self.work_saturday:
- nbdays = 6
- else:
- nbdays = 5
- if (
- self.weekday >= 0 and self.weekday < nbdays
- ): # monday-friday or monday-saturday
- return 1
- else:
- return 0
-
- def __repr__(self):
- return "'%02d/%02d/%04d'" % (self.day, self.month, self.year)
-
- def __str__(self):
- return "%02d/%02d/%04d" % (self.day, self.month, self.year)
-
- def ISO(self):
- "iso8601 representation of the date"
- return "%04d-%02d-%02d" % (self.year, self.month, self.day)
-
- def next_day(self, days=1):
- "date for the next day (nota: may be a non workable day)"
- day = self.day + days
- month = self.month
- year = self.year
-
- while day > MonthNbDays(month, year):
- day = day - MonthNbDays(month, year)
- month = month + 1
- if month > 12:
- month = 1
- year = year + 1
- return self.__class__(
- "%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday
- )
-
- def prev(self, days=1):
- "date for previous day"
- day = self.day - days
- month = self.month
- year = self.year
- while day <= 0:
- month = month - 1
- if month == 0:
- month = 12
- year = year - 1
- day = day + MonthNbDays(month, year)
-
- return self.__class__(
- "%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday
- )
-
- def next_monday(self):
- "date of next monday"
- return self.next_day((7 - self.weekday) % 7)
-
- def prev_monday(self):
- "date of last monday, but on sunday, pick next monday"
- if self.weekday == 6:
- return self.next_monday()
- else:
- return self.prev(self.weekday)
-
- def __cmp__(self, other): # #py3 TODO à supprimer
- """return a negative integer if self < other,
- zero if self == other, a positive integer if self > other"""
- return int(self.time - other.time)
-
- def __eq__(self, other):
- return self.time == other.time
-
- def __ne__(self, other):
- return self.time != other.time
-
- def __lt__(self, other):
- return self.time < other.time
-
- def __le__(self, other):
- return self.time <= other.time
-
- def __gt__(self, other):
- return self.time > other.time
-
- def __ge__(self, other):
- return self.time >= other.time
-
- def __hash__(self):
- "we are immutable !"
- return hash(self.time) ^ hash(str(self))
-
-
-# d = ddmmyyyy( '21/12/99' )
-def DateRangeISO(date_beg, date_end, workable=1):
- """returns list of dates in [date_beg,date_end]
- workable = 1 => keeps only workable days"""
- if not date_beg:
- raise ScoValueError("pas de date spécifiée !")
- if not date_end:
- date_end = date_beg
- r = []
- work_saturday = is_work_saturday()
- try:
- cur = ddmmyyyy(date_beg, work_saturday=work_saturday)
- end = ddmmyyyy(date_end, work_saturday=work_saturday)
- except (AttributeError, ValueError) as e:
- raise ScoValueError("date invalide !") from e
- while cur <= end:
- if (not workable) or cur.iswork():
- r.append(cur)
- cur = cur.next_day()
-
- return [x.ISO() for x in r]
-
-
-def day_names():
- """Returns week day names.
- If work_saturday property is set, include saturday
- """
- if is_work_saturday():
- return scu.DAY_NAMES[:-1]
- else:
- return scu.DAY_NAMES[:-2]
-
-
-def next_iso_day(date):
- "return date after date"
- d = ddmmyyyy(date, fmt="iso", work_saturday=is_work_saturday())
- return d.next_day().ISO()
-
-
-def YearTable(
- year,
- events=[],
- firstmonth=9,
- lastmonth=7,
- halfday=0,
- dayattributes="",
- pad_width=8,
-):
- """Generate a calendar table
- events = list of tuples (date, text, color, href [,halfday])
- where date is a string in ISO format (yyyy-mm-dd)
- halfday is boolean (true: morning, false: afternoon)
- text = text to put in calendar (must be short, 1-5 cars) (optional)
- if halfday, generate 2 cells per day (morning, afternoon)
- """
- T = [
- ''
- ]
- T.append("")
- month = firstmonth
- while 1:
- T.append('')
- T.append(MonthTableHead(month))
- T.append(
- MonthTableBody(
- month,
- year,
- events,
- halfday,
- dayattributes,
- is_work_saturday(),
- pad_width=pad_width,
- )
- )
- T.append(MonthTableTail())
- T.append(" | ")
- if month == lastmonth:
- break
- month = month + 1
- if month > 12:
- month = 1
- year = year + 1
- T.append("
")
- return "\n".join(T)
-
-
-def list_abs_in_range(
- etudid, debut=None, fin=None, matin=None, moduleimpl_id=None, cursor=None
-):
- """Liste des absences entre deux dates.
-
- Args:
- etudid:
- debut: string iso date ("2020-03-12") ou None
- end: string iso date ("2020-03-12")
- matin: None, True, False
- moduleimpl_id: restreint le comptage aux absences dans ce module
-
- Returns:
- List of absences
- """
- if matin is not None:
- matin = _toboolean(matin)
- ismatin = " AND A.MATIN = %(matin)s "
- else:
- ismatin = ""
- if moduleimpl_id:
- modul = " AND A.MODULEIMPL_ID = %(moduleimpl_id)s "
- else:
- modul = ""
- if not cursor:
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
- cursor.execute(
- """SELECT DISTINCT A.JOUR, A.MATIN
- FROM ABSENCES A
- WHERE A.ETUDID = %(etudid)s
- AND A.ESTABS"""
- + ismatin
- + modul
- + (
- ""
- if debut is None
- else """
- AND A.JOUR BETWEEN %(debut)s AND %(fin)s
- """
- ),
- {
- "etudid": etudid,
- "debut": debut,
- "fin": fin,
- "matin": matin,
- "moduleimpl_id": moduleimpl_id,
- },
- )
- res = cursor.dictfetchall()
- return res
-
-
-def count_abs(etudid, debut, fin, matin=None, moduleimpl_id=None) -> int:
- """compte le nombre d'absences
-
- Args:
- etudid: l'étudiant considéré
- debut: date, chaîne iso, eg "2021-06-15"
- fin: date de fin, incluse
- matin: True (compte les matinées), False (les après-midi), None (les deux)
- moduleimpl_id: restreint le comptage aux absences dans ce module.
-
- Returns:
- An integer.
- """
- return len(
- list_abs_in_range(etudid, debut, fin, matin=matin, moduleimpl_id=moduleimpl_id)
- )
-
-
-def count_abs_just(etudid, debut, fin, matin=None, moduleimpl_id=None) -> int:
- """compte le nombre d'absences justifiées
-
- Args:
- etudid: l'étudiant considéré
- debut: date, chaîne iso, eg "2021-06-15"
- fin: date de fin, incluse
- matin: True (compte les matinées), False (les après-midi), None (les deux)
- moduleimpl_id: restreint le comptage aux absences dans ce module.
-
- Returns:
- An integer.
- """
- if matin is not None:
- matin = _toboolean(matin)
- ismatin = " AND A.MATIN = %(matin)s "
- else:
- ismatin = ""
- if moduleimpl_id:
- modul = " AND A.MODULEIMPL_ID = %(moduleimpl_id)s "
- else:
- modul = ""
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
- cursor.execute(
- """SELECT COUNT(*) AS NbAbsJust FROM (
-SELECT DISTINCT A.JOUR, A.MATIN
-FROM ABSENCES A, ABSENCES B
-WHERE A.ETUDID = %(etudid)s
- AND A.ETUDID = B.ETUDID
- AND A.JOUR = B.JOUR AND A.MATIN = B.MATIN
- AND A.JOUR BETWEEN %(debut)s AND %(fin)s
- AND A.ESTABS AND (A.ESTJUST OR B.ESTJUST)"""
- + ismatin
- + modul
- + """
-) AS tmp
- """,
- vars(),
- )
- res = cursor.fetchone()[0]
- return res
-
-
-def list_abs_date(etudid, beg_date=None, end_date=None):
- """Liste des absences et justifs entre deux dates (inclues)."""
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
-
- req = """SELECT jour, matin, estabs, estjust, description
- FROM ABSENCES A
- WHERE A.ETUDID = %(etudid)s""" + (
- ""
- if beg_date is None
- else """
- AND A.jour >= %(beg_date)s
- AND A.jour <= %(end_date)s
- """
- )
- cursor.execute(
- req,
- vars(),
- )
-
- absences = cursor.dictfetchall()
- # remove duplicates
- A = {} # { (jour, matin) : abs }
- for a in absences:
- jour, matin = a["jour"], a["matin"]
- if (jour, matin) in A:
- # garde toujours la description
- a["description"] = a["description"] or A[(jour, matin)]["description"]
- # et la justif:
- a["estjust"] = a["estjust"] or A[(jour, matin)]["estjust"]
- a["estabs"] = a["estabs"] or A[(jour, matin)]["estabs"]
- A[(jour, matin)] = a
- else:
- A[(jour, matin)] = a
- if A[(jour, matin)]["description"] is None:
- A[(jour, matin)]["description"] = ""
- # add hours: matin = 8:00 - 12:00, apresmidi = 12:00 - 18:00
- dat = "%04d-%02d-%02d" % (a["jour"].year, a["jour"].month, a["jour"].day)
- if a["matin"]:
- A[(jour, matin)]["begin"] = dat + " 08:00:00"
- A[(jour, matin)]["end"] = dat + " 11:59:59"
- else:
- A[(jour, matin)]["begin"] = dat + " 12:00:00"
- A[(jour, matin)]["end"] = dat + " 17:59:59"
- # sort
- R = list(A.values())
- R.sort(key=lambda x: (x["begin"]))
- return R
-
-
-def _get_abs_description(a, cursor=None):
- "Description associee a l'absence"
- from app.scodoc import sco_moduleimpl
-
- if not cursor:
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
- a = a.copy()
- # a['jour'] = a['jour'].date()
- if a["matin"]: # devrait etre booleen... :-(
- a["matin"] = True
- else:
- a["matin"] = False
- cursor.execute(
- """SELECT * FROM absences
- WHERE etudid=%(etudid)s AND jour=%(jour)s AND matin=%(matin)s
- ORDER BY entry_date desc""",
- a,
- )
- A = cursor.dictfetchall()
- desc = None
- module = ""
- for a in A:
- if a["description"]:
- desc = a["description"]
- if a["moduleimpl_id"] and a["moduleimpl_id"] != "NULL":
- # Trouver le nom du module
- Mlist = sco_moduleimpl.moduleimpl_withmodule_list(
- moduleimpl_id=a["moduleimpl_id"]
- )
- if Mlist:
- M = Mlist[0]
- module += "%s " % (M["module"]["code"] or "(module sans code)")
-
- if desc:
- return "(%s) %s" % (desc, module)
- if module:
- return module
- return ""
-
-
-def list_abs_jour(date, am=True, pm=True, is_abs=True, is_just=None) -> list[dict]:
- """Liste des absences et/ou justificatifs ce jour.
- is_abs: None (peu importe), True, False
- is_just: idem
- """
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
- req = """SELECT DISTINCT etudid, jour, matin FROM ABSENCES A
-WHERE A.jour = %(date)s
-"""
- if is_abs is not None:
- req += " AND A.estabs = %(is_abs)s"
- if is_just is not None:
- req += " AND A.estjust = %(is_just)s"
- if not am:
- req += " AND NOT matin "
- if not pm:
- req += " AND matin"
-
- cursor.execute(req, {"date": date, "is_just": is_just, "is_abs": is_abs})
- A = cursor.dictfetchall()
- for a in A:
- a["description"] = _get_abs_description(a, cursor=cursor)
- return A
-
-
-def list_abs_non_just_jour(date, am=True, pm=True) -> list[dict]:
- "Liste des absences non justifiees ce jour"
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
- reqa = ""
- if not am:
- reqa += " AND NOT matin "
- if not pm:
- reqa += " AND matin "
- req = (
- """SELECT etudid, jour, matin FROM ABSENCES A
-WHERE A.estabs
-AND A.jour = %(date)s
-"""
- + reqa
- + """EXCEPT SELECT etudid, jour, matin FROM ABSENCES B
-WHERE B.estjust AND B.jour = %(date)s"""
- + reqa
- )
-
- cursor.execute(req, {"date": date})
- A = cursor.dictfetchall()
- for a in A:
- a["description"] = _get_abs_description(a, cursor=cursor)
- return A
-
-
-def list_abs_non_just(etudid, datedebut):
- "Liste des absences NON justifiees (par ordre chronologique)"
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
- cursor.execute(
- """SELECT ETUDID, JOUR, MATIN FROM ABSENCES A
-WHERE A.ETUDID = %(etudid)s
-AND A.estabs
-AND A.jour >= %(datedebut)s
-EXCEPT SELECT ETUDID, JOUR, MATIN FROM ABSENCES B
-WHERE B.estjust
-AND B.ETUDID = %(etudid)s
-ORDER BY JOUR
- """,
- vars(),
- )
- abs_list = cursor.dictfetchall()
- for a in abs_list:
- a["description"] = _get_abs_description(a, cursor=cursor)
- return abs_list
-
-
-def list_abs_just(etudid, datedebut):
- "Liste des absences justifiees (par ordre chronologique)"
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
- cursor.execute(
- """SELECT DISTINCT A.ETUDID, A.JOUR, A.MATIN FROM ABSENCES A, ABSENCES B
-WHERE A.ETUDID = %(etudid)s
-AND A.ETUDID = B.ETUDID
-AND A.JOUR = B.JOUR AND A.MATIN = B.MATIN AND A.JOUR >= %(datedebut)s
-AND A.ESTABS AND (A.ESTJUST OR B.ESTJUST)
-ORDER BY A.JOUR
- """,
- vars(),
- )
- A = cursor.dictfetchall()
- for a in A:
- a["description"] = _get_abs_description(a, cursor=cursor)
- return A
-
-
-def list_abs_justifs(etudid, datedebut, datefin=None, only_no_abs=False):
- """Liste des justificatifs (avec ou sans absence relevée) à partir d'une date,
- ou, si datefin spécifié, entre deux dates.
-
- Args:
- etudid:
- datedebut: date de début, iso, eg "2002-03-15"
- datefin: date de fin, incluse, eg "2002-03-15"
- only_no_abs: si vrai, seulement les justificatifs correspondant
- aux jours sans absences relevées.
- Returns:
- Liste de dict absences
- {'etudid': 'EID214', 'jour': datetime.date(2021, 1, 15),
- 'matin': True, 'description': ''
- }
- """
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
- req = """SELECT DISTINCT ETUDID, JOUR, MATIN FROM ABSENCES A
-WHERE A.ETUDID = %(etudid)s
-AND A.ESTJUST
-AND A.JOUR >= %(datedebut)s"""
- if datefin:
- req += """AND A.JOUR <= %(datefin)s"""
- if only_no_abs:
- req += """
-EXCEPT SELECT ETUDID, JOUR, MATIN FROM ABSENCES B
-WHERE B.estabs
-AND B.ETUDID = %(etudid)s
- """
- cursor.execute(req, vars())
- A = cursor.dictfetchall()
- for a in A:
- a["description"] = _get_abs_description(a, cursor=cursor)
-
- return A
-
-
-def add_absence(
- etudid,
- jour,
- matin,
- estjust,
- description=None,
- moduleimpl_id=None,
-):
- "Ajoute une absence dans la bd"
- if _isFarFutur(jour):
- raise ScoValueError("date absence erronée ou trop loin dans le futur !")
- estjust = _toboolean(estjust)
- matin = _toboolean(matin)
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
- cursor.execute(
- """
- INSERT into absences
- (etudid, jour, estabs, estjust, matin, description, moduleimpl_id)
- VALUES
- (%(etudid)s, %(jour)s, true, %(estjust)s, %(matin)s,
- %(description)s, %(moduleimpl_id)s
- )
- """,
- vars(),
- )
- logdb(
- cnx,
- "AddAbsence",
- etudid=etudid,
- msg="JOUR=%(jour)s,MATIN=%(matin)s,ESTJUST=%(estjust)s,description=%(description)s,moduleimpl_id=%(moduleimpl_id)s"
- % vars(),
- )
- cnx.commit()
- invalidate_abs_etud_date(etudid, jour)
- sco_abs_notification.abs_notify(etudid, jour)
-
-
-def add_justif(etudid, jour, matin, description=None):
- "Ajoute un justificatif dans la base"
- # unpublished
- if _isFarFutur(jour):
- raise ScoValueError("date justificatif trop loin dans le futur !")
- matin = _toboolean(matin)
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
- cursor.execute(
- """INSERT INTO absences (etudid, jour, estabs, estjust, matin, description)
- VALUES (%(etudid)s, %(jour)s, FALSE, TRUE, %(matin)s, %(description)s)
- """,
- vars(),
- )
- logdb(
- cnx,
- "AddJustif",
- etudid=etudid,
- msg="JOUR=%(jour)s,MATIN=%(matin)s" % vars(),
- )
- cnx.commit()
- invalidate_abs_etud_date(etudid, jour)
-
-
-def add_abslist(abslist, moduleimpl_id=None):
- for a in abslist:
- etudid, jour, ampm = a.split(":")
- if ampm == "am":
- matin = 1
- elif ampm == "pm":
- matin = 0
- else:
- raise ValueError("invalid ampm !")
- # ajoute abs si pas deja absent
- if count_abs(etudid, jour, jour, matin, moduleimpl_id) == 0:
- add_absence(etudid, jour, matin, 0, "", moduleimpl_id)
-
-
-def annule_absence(etudid, jour, matin, moduleimpl_id=None):
- """Annule une absence dans la base. N'efface pas l'éventuel justificatif.
- Args:
- etudid:
- jour: date, chaîne iso, eg "1999-12-31"
- matin:
- moduleimpl_id: si spécifié, n'annule que pour ce module.
-
- Returns:
- None
- """
- # unpublished
- matin = _toboolean(matin)
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
- req = "delete from absences where jour=%(jour)s and matin=%(matin)s and etudid=%(etudid)s and estabs"
- if moduleimpl_id:
- req += " and moduleimpl_id=%(moduleimpl_id)s"
- cursor.execute(req, vars())
- logdb(
- cnx,
- "AnnuleAbsence",
- etudid=etudid,
- msg="JOUR=%(jour)s,MATIN=%(matin)s,moduleimpl_id=%(moduleimpl_id)s" % vars(),
- )
- cnx.commit()
- invalidate_abs_etud_date(etudid, jour)
-
-
-def annule_justif(etudid, jour, matin):
- "Annule un justificatif"
- # unpublished
- matin = _toboolean(matin)
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
- cursor.execute(
- "delete from absences where jour=%(jour)s and matin=%(matin)s and etudid=%(etudid)s and ESTJUST AND NOT ESTABS",
- vars(),
- )
- cursor.execute(
- "update absences set estjust=false where jour=%(jour)s and matin=%(matin)s and etudid=%(etudid)s",
- vars(),
- )
- logdb(
- cnx,
- "AnnuleJustif",
- etudid=etudid,
- msg="JOUR=%(jour)s,MATIN=%(matin)s" % vars(),
- )
- cnx.commit()
- invalidate_abs_etud_date(etudid, jour)
-
-
-# ------ HTML Calendar functions (see YearTable function)
-
-# MONTH/DAY NAMES:
-
-MONTHNAMES = (
- "Janvier",
- "Février",
- "Mars",
- "Avril",
- "Mai",
- "Juin",
- "Juillet",
- "Aout",
- "Septembre",
- "Octobre",
- "Novembre",
- "Décembre",
-)
-
-MONTHNAMES_ABREV = (
- "Jan.",
- "Fév.",
- "Mars",
- "Avr.",
- "Mai ",
- "Juin",
- "Juil",
- "Aout",
- "Sept",
- "Oct.",
- "Nov.",
- "Déc.",
-)
-
-DAYNAMES = ("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche")
-
-DAYNAMES_ABREV = ("L", "M", "M", "J", "V", "S", "D")
-
-# COLORS:
-
-WHITE = "#FFFFFF"
-GRAY1 = "#EEEEEE"
-GREEN3 = "#99CC99"
-WEEKDAYCOLOR = GRAY1
-WEEKENDCOLOR = GREEN3
-
-
-def MonthTableHead(month):
- color = WHITE
- return """
- %s |
\n""" % (
- color,
- MONTHNAMES_ABREV[month - 1],
- )
-
-
-def MonthTableTail():
- return "
\n"
-
-
-def MonthTableBody(
- month, year, events=[], halfday=0, trattributes="", work_saturday=False, pad_width=8
-):
- firstday, nbdays = calendar.monthrange(year, month)
- localtime = time.localtime()
- current_weeknum = time.strftime("%U", localtime)
- current_year = localtime[0]
- T = []
- # cherche date du lundi de la 1ere semaine de ce mois
- monday = ddmmyyyy("1/%d/%d" % (month, year))
- while monday.weekday != 0:
- monday = monday.prev()
-
- if work_saturday:
- weekend = ("D",)
- else:
- weekend = ("S", "D")
-
- if not halfday:
- for d in range(1, nbdays + 1):
- weeknum = time.strftime(
- "%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y")
- )
- day = DAYNAMES_ABREV[(firstday + d - 1) % 7]
- if day in weekend:
- bgcolor = WEEKENDCOLOR
- weekclass = "wkend"
- attrs = ""
- else:
- bgcolor = WEEKDAYCOLOR
- weekclass = "wk" + str(monday).replace("/", "_")
- attrs = trattributes
- color = None
- legend = ""
- href = ""
- descr = ""
- # event this day ?
- # each event is a tuple (date, text, color, href)
- # where date is a string in ISO format (yyyy-mm-dd)
- for ev in events:
- ev_year = int(ev[0][:4])
- ev_month = int(ev[0][5:7])
- ev_day = int(ev[0][8:10])
- if year == ev_year and month == ev_month and ev_day == d:
- if ev[1]:
- legend = ev[1]
- if ev[2]:
- color = ev[2]
- if ev[3]:
- href = ev[3]
- if len(ev) > 4 and ev[4]:
- descr = ev[4]
- #
- cc = []
- if color is not None:
- cc.append('' % color)
- else:
- cc.append(' | ')
-
- if href:
- href = 'href="%s"' % href
- if descr:
- descr = 'title="%s"' % html.escape(descr, quote=True)
- if href or descr:
- cc.append("" % (href, descr))
-
- if legend or d == 1:
- if pad_width is not None:
- n = pad_width - len(legend) # pad to 8 cars
- if n > 0:
- legend = (
- " " * (n // 2) + legend + " " * ((n + 1) // 2)
- )
- else:
- legend = " " # empty cell
- cc.append(legend)
- if href or descr:
- cc.append("")
- cc.append(" | ")
- cell = "".join(cc)
- if day == "D":
- monday = monday.next_day(7)
- if (
- weeknum == current_weeknum
- and current_year == year
- and weekclass != "wkend"
- ):
- weekclass += " currentweek"
- T.append(
- '%d%s | %s
'
- % (bgcolor, weekclass, attrs, d, day, cell)
- )
- else:
- # Calendar with 2 cells / day
- for d in range(1, nbdays + 1):
- weeknum = time.strftime(
- "%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y")
- )
- day = DAYNAMES_ABREV[(firstday + d - 1) % 7]
- if day in weekend:
- bgcolor = WEEKENDCOLOR
- weekclass = "wkend"
- attrs = ""
- else:
- bgcolor = WEEKDAYCOLOR
- weekclass = "wk" + str(monday).replace("/", "_")
- attrs = trattributes
- if (
- weeknum == current_weeknum
- and current_year == year
- and weekclass != "wkend"
- ):
- weeknum += " currentweek"
-
- if day == "D":
- monday = monday.next_day(7)
- T.append(
- '%d%s | '
- % (bgcolor, weekclass, attrs, d, day)
- )
- cc = []
- for morning in (True, False):
- color = None
- legend = ""
- href = ""
- descr = ""
- for ev in events:
- ev_year = int(ev[0][:4])
- ev_month = int(ev[0][5:7])
- ev_day = int(ev[0][8:10])
- if ev[4] is not None:
- ev_half = int(ev[4])
- else:
- ev_half = 0
- if (
- year == ev_year
- and month == ev_month
- and ev_day == d
- and morning == ev_half
- ):
- if ev[1]:
- legend = ev[1]
- if ev[2]:
- color = ev[2]
- if ev[3]:
- href = ev[3]
- if len(ev) > 5 and ev[5]:
- descr = ev[5]
- #
- if color is not None:
- cc.append('' % (color))
- else:
- cc.append(' | ')
- if href:
- href = 'href="%s"' % href
- if descr:
- descr = 'title="%s"' % html.escape(descr, quote=True)
- if href or descr:
- cc.append("" % (href, descr))
- if legend or d == 1:
- n = 3 - len(legend) # pad to 3 cars
- if n > 0:
- legend = (
- " " * (n // 2) + legend + " " * ((n + 1) // 2)
- )
- else:
- legend = " " # empty cell
- cc.append(legend)
- if href or descr:
- cc.append("")
- cc.append(" | \n")
- T.append("".join(cc) + "
")
- return "\n".join(T)
-
-
-# --------------------------------------------------------------------
-#
-# Cache absences
-#
-# On cache (via REDIS ou autre, voir sco_cache.py) les _nombres_ d'absences
-# (justifiées et non justifiées) de chaque etudiant dans un semestre donné.
-# Le cache peut être invalidé soit par étudiant/semestre, soit pour tous
-# les étudiant d'un semestre.
-#
-# On ne cache pas la liste des absences car elle est rarement utilisée (calendrier,
-# absences à une date donnée).
-#
-# --------------------------------------------------------------------
-
-
-def get_abs_count(etudid, sem):
- """Les comptes d'absences de cet étudiant dans ce semestre:
- tuple (nb abs non justifiées, nb abs justifiées)
- Utilise un cache.
- """
- return get_abs_count_in_interval(etudid, sem["date_debut_iso"], sem["date_fin_iso"])
-
-
-def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso):
- """Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
- tuple (nb abs, nb abs justifiées)
- Utilise un cache.
- """
- key = str(etudid) + "_" + date_debut_iso + "_" + date_fin_iso
- r = sco_cache.AbsSemEtudCache.get(key)
- if not r:
- nb_abs = count_abs(
- etudid=etudid,
- debut=date_debut_iso,
- fin=date_fin_iso,
- )
- nb_abs_just = count_abs_just(
- etudid=etudid,
- debut=date_debut_iso,
- fin=date_fin_iso,
- )
- r = (nb_abs, nb_abs_just)
- ans = sco_cache.AbsSemEtudCache.set(key, r)
- if not ans:
- log("warning: get_abs_count failed to cache")
- return r
-
-
-def invalidate_abs_count(etudid, sem):
- """Invalidate (clear) cached counts"""
- date_debut = sem["date_debut_iso"]
- date_fin = sem["date_fin_iso"]
- key = str(etudid) + "_" + date_debut + "_" + date_fin
- sco_cache.AbsSemEtudCache.delete(key)
-
-
-def invalidate_abs_count_sem(sem):
- """Invalidate (clear) cached abs counts for all the students of this semestre"""
- inscriptions = (
- sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
- sem["formsemestre_id"]
- )
- )
- for ins in inscriptions:
- invalidate_abs_count(ins["etudid"], sem)
-
-
-def invalidate_abs_etud_date(etudid, date): # was invalidateAbsEtudDate
- """Doit etre appelé à chaque modification des absences
- pour cet étudiant et cette date.
- Invalide cache absence et caches semestre
- date: date au format ISO
- """
- from app.scodoc import sco_compute_moy
-
- # Semestres a cette date:
- etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
- sems = [
- sem
- for sem in etud["sems"]
- if sem["date_debut_iso"] <= date and sem["date_fin_iso"] >= date
- ]
-
- # Invalide les PDF et les absences:
- for sem in sems:
- # Inval cache bulletin et/ou note_table
- if sco_compute_moy.formsemestre_expressions_use_abscounts(
- sem["formsemestre_id"]
- ):
- # certaines formules utilisent les absences
- pdfonly = False
- else:
- # efface toujours le PDF car il affiche en général les absences
- pdfonly = True
-
- sco_cache.invalidate_formsemestre(
- formsemestre_id=sem["formsemestre_id"], pdfonly=pdfonly
- )
-
- # Inval cache compteurs absences:
- invalidate_abs_count_sem(sem)
diff --git a/app/scodoc/sco_abs_notification.py b/app/scodoc/sco_abs_notification.py
index 50c50ae45..0b4eeba7c 100644
--- a/app/scodoc/sco_abs_notification.py
+++ b/app/scodoc/sco_abs_notification.py
@@ -268,7 +268,7 @@ def abs_notification_message(
"""
from app.scodoc import sco_bulletins
- etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
+ etud = Identite.get_etud(etudid)
# Variables accessibles dans les balises du template: %(nom_variable)s :
values = sco_bulletins.make_context_dict(formsemestre, etud)
@@ -287,7 +287,7 @@ def abs_notification_message(
log("abs_notification_message: empty template, not sending message")
return None
- subject = f"""[ScoDoc] Trop d'absences pour {etud["nomprenom"]}"""
+ subject = f"""[ScoDoc] Trop d'absences pour {etud.nomprenom}"""
msg = Message(subject, sender=email.get_from_addr(formsemestre.departement.acronym))
msg.body = txt
return msg
diff --git a/app/scodoc/sco_archives_etud.py b/app/scodoc/sco_archives_etud.py
index 6f174f15d..19dbf70ac 100644
--- a/app/scodoc/sco_archives_etud.py
+++ b/app/scodoc/sco_archives_etud.py
@@ -138,21 +138,18 @@ def etud_upload_file_form(etudid):
"""Page with a form to choose and upload a file, with a description."""
# check permission
if not can_edit_etud_archive(current_user):
- raise AccessDenied("opération non autorisée pour %s" % current_user)
- etuds = sco_etud.get_etud_info(filled=True)
- if not etuds:
- raise ScoValueError("étudiant inexistant")
- etud = etuds[0]
+ raise AccessDenied(f"opération non autorisée pour {current_user}")
+ etud = Identite.get_etud(etudid)
+
H = [
html_sco_header.sco_header(
- page_title="Chargement d'un document associé à %(nomprenom)s" % etud,
+ page_title=f"Chargement d'un document associé à {etud.nomprenom}",
),
- """Chargement d'un document associé à %(nomprenom)s
- """
- % etud,
- """Le fichier ne doit pas dépasser %sMo.
- """
- % (scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)),
+ f"""Chargement d'un document associé à {etud.nomprenom}
+
+ Le fichier ne doit pas dépasser {
+ scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)}Mo.
+ """,
]
tf = TrivialFormulator(
request.base_url,
@@ -176,20 +173,13 @@ def etud_upload_file_form(etudid):
if tf[0] == 0:
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
elif tf[0] == -1:
- return flask.redirect(
- url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
- )
- else:
- data = tf[2]["datafile"].read()
- descr = tf[2]["description"]
- filename = tf[2]["datafile"].filename
- etud_archive_id = etud["etudid"]
- _store_etud_file_to_new_archive(
- etud_archive_id, data, filename, description=descr
- )
- return flask.redirect(
- url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
- )
+ return flask.redirect(etud.url_fiche())
+ data = tf[2]["datafile"].read()
+ descr = tf[2]["description"]
+ filename = tf[2]["datafile"].filename
+ etud_archive_id = (etudid,)
+ _store_etud_file_to_new_archive(etud_archive_id, data, filename, description=descr)
+ return flask.redirect(etud.url_fiche())
def _store_etud_file_to_new_archive(
@@ -209,23 +199,20 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
# check permission
if not can_edit_etud_archive(current_user):
raise AccessDenied(f"opération non autorisée pour {current_user}")
- etuds = sco_etud.get_etud_info(filled=True)
- if not etuds:
- raise ScoValueError("étudiant inexistant")
- etud = etuds[0]
- etud_archive_id = etud["etudid"]
+ etud = Identite.get_etud(etudid)
+ etud_archive_id = etudid
archive_id = ETUDS_ARCHIVER.get_id_from_name(
- etud_archive_id, archive_name, dept_id=etud["dept_id"]
+ etud_archive_id, archive_name, dept_id=etud.dept_id
)
if not dialog_confirmed:
return scu.confirm_dialog(
- """Confirmer la suppression des fichiers ?
- Fichier associé le %s à l'étudiant %s
- La suppression sera définitive.
"""
- % (
- ETUDS_ARCHIVER.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M"),
- etud["nomprenom"],
- ),
+ f"""Confirmer la suppression des fichiers ?
+ Fichier associé le {
+ ETUDS_ARCHIVER.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M")
+ } à l'étudiant {etud.nomprenom}
+
+ La suppression sera définitive.
+ """,
dest_url="",
cancel_url=url_for(
"scolar.fiche_etud",
@@ -236,22 +223,17 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
parameters={"etudid": etudid, "archive_name": archive_name},
)
- ETUDS_ARCHIVER.delete_archive(archive_id, dept_id=etud["dept_id"])
+ ETUDS_ARCHIVER.delete_archive(archive_id, dept_id=etud.dept_id)
flash("Archive supprimée")
- return flask.redirect(
- url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
- )
+ return flask.redirect(etud.url_fiche())
def etud_get_archived_file(etudid, archive_name, filename):
"""Send file to client."""
- etuds = sco_etud.get_etud_info(etudid=etudid, filled=True)
- if not etuds:
- raise ScoValueError("étudiant inexistant")
- etud = etuds[0]
- etud_archive_id = etud["etudid"]
+ etud = Identite.get_etud(etudid)
+ etud_archive_id = etud.id
return ETUDS_ARCHIVER.get_archived_file(
- etud_archive_id, archive_name, filename, dept_id=etud["dept_id"]
+ etud_archive_id, archive_name, filename, dept_id=etud.dept_id
)
diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py
index 57cafe6c7..4f4d5993f 100644
--- a/app/scodoc/sco_formsemestre_edit.py
+++ b/app/scodoc/sco_formsemestre_edit.py
@@ -1273,29 +1273,24 @@ def do_formsemestre_clone(
log(f"created formsemestre {formsemestre_id}")
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
# 2- create moduleimpls
- mods_orig = sco_moduleimpl.moduleimpl_list(formsemestre_id=orig_formsemestre_id)
- for mod_orig in mods_orig:
- args = mod_orig.copy()
+ modimpl_orig: ModuleImpl
+ for modimpl_orig in formsemestre_orig.modimpls:
+ args = modimpl_orig.to_dict(with_module=False)
args["formsemestre_id"] = formsemestre_id
- mid = sco_moduleimpl.do_moduleimpl_create(args)
- # copy notes_modules_enseignants
- ens = sco_moduleimpl.do_ens_list(
- args={"moduleimpl_id": mod_orig["moduleimpl_id"]}
- )
- for e in ens:
- args = e.copy()
- args["moduleimpl_id"] = mid
- sco_moduleimpl.do_ens_create(args)
+ modimpl_new = ModuleImpl.create_from_dict(args)
+ db.session.flush()
+ # copy enseignants
+ for ens in modimpl_orig.enseignants:
+ modimpl_new.enseignants.append(ens)
+ db.session.add(modimpl_new)
# optionally, copy evaluations
if clone_evaluations:
- for e in Evaluation.query.filter_by(
- moduleimpl_id=mod_orig["moduleimpl_id"]
- ):
+ for e in Evaluation.query.filter_by(moduleimpl_id=modimpl_orig.id):
# copie en enlevant la date
new_eval = e.clone(
not_copying=("date_debut", "date_fin", "moduleimpl_id")
)
- new_eval.moduleimpl_id = mid
+ new_eval.moduleimpl_id = modimpl_new.id
# Copie les poids APC de l'évaluation
new_eval.set_ue_poids_dict(e.get_ue_poids_dict())
db.session.commit()
diff --git a/app/scodoc/sco_moduleimpl.py b/app/scodoc/sco_moduleimpl.py
index 197180f1b..5a785b8a7 100644
--- a/app/scodoc/sco_moduleimpl.py
+++ b/app/scodoc/sco_moduleimpl.py
@@ -37,7 +37,6 @@ from app.models import Formation
from app.scodoc import scolog
from app.scodoc import sco_formsemestre
from app.scodoc import sco_cache
-import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import ScoValueError, AccessDenied
@@ -362,45 +361,3 @@ def do_ens_create(args):
cnx = ndb.GetDBConnexion()
r = _modules_enseignantsEditor.create(cnx, args)
return r
-
-
-def can_change_module_resp(moduleimpl_id):
- """Check if current user can modify module resp. (raise exception if not).
- = Admin, et dir des etud. (si option l'y autorise)
- """
- M = moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
- # -- check lock
- sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
- if not sem["etat"]:
- raise ScoValueError("Modification impossible: semestre verrouille")
- # -- check access
- # admin ou resp. semestre avec flag resp_can_change_resp
- if not current_user.has_permission(Permission.EditFormSemestre) and (
- (current_user.id not in sem["responsables"]) or (not sem["resp_can_change_ens"])
- ):
- raise AccessDenied(f"Modification impossible pour {current_user}")
- return M, sem
-
-
-def can_change_ens(moduleimpl_id, raise_exc=True):
- "check if current user can modify ens list (raise exception if not)"
- M = moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
- # -- check lock
- sem = sco_formsemestre.get_formsemestre(M["formsemestre_id"])
- if not sem["etat"]:
- if raise_exc:
- raise ScoValueError("Modification impossible: semestre verrouille")
- else:
- return False
- # -- check access
- # admin, resp. module ou resp. semestre
- if (
- current_user.id != M["responsable_id"]
- and not current_user.has_permission(Permission.EditFormSemestre)
- and (current_user.id not in sem["responsables"])
- ):
- if raise_exc:
- raise AccessDenied("Modification impossible pour %s" % current_user)
- else:
- return False
- return M, sem
diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py
index e97716b6a..698e212a7 100644
--- a/app/scodoc/sco_moduleimpl_status.py
+++ b/app/scodoc/sco_moduleimpl_status.py
@@ -244,7 +244,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
({module_resp.user_name})
""",
]
- if modimpl.can_change_ens_by(current_user):
+ if modimpl.can_change_responsable(current_user):
H.append(
f"""""")
H.append(", ".join([u.get_nomprenom() for u in modimpl.enseignants]))
H.append(""" | """)
- try:
- sco_moduleimpl.can_change_ens(moduleimpl_id)
+ if modimpl.can_change_ens(raise_exc=False):
H.append(
- """modifier les enseignants"""
- % moduleimpl_id
+ f"""modifier les enseignants"""
)
- except:
- pass
+
H.append(""" | """)
# 2ieme ligne: Semestre, Coef
diff --git a/app/views/notes.py b/app/views/notes.py
index 1201747ed..4fa59fc57 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -961,12 +961,15 @@ def formsemestre_custommenu_edit(formsemestre_id):
@scodoc7func
def edit_enseignants_form(moduleimpl_id):
"modif liste enseignants/moduleimpl"
- M, sem = sco_moduleimpl.can_change_ens(moduleimpl_id)
+ modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
+ modimpl.can_change_ens(raise_exc=True)
# --
header = html_sco_header.html_sem_header(
- 'Enseignants du module %s'
- % (moduleimpl_id, M["module"]["titre"]),
- page_title="Enseignants du module %s" % M["module"]["titre"],
+ f"""Enseignants du module {modimpl.module.titre or modimpl.module.code}""",
+ page_title=f"Enseignants du module {modimpl.module.titre or modimpl.module.code}",
javascripts=["libjs/AutoSuggest.js"],
cssstyles=["css/autosuggest_inquisitor.css"],
bodyOnLoad="init_tf_form('')",
@@ -981,21 +984,18 @@ def edit_enseignants_form(moduleimpl_id):
allowed_user_names = list(uid2display.values())
H = [
- "- %s (responsable)
"
- % uid2display.get(M["responsable_id"], M["responsable_id"])
+ f"""- {
+ uid2display.get(modimpl.responsable_id, modimpl.responsable_id)
+ } (responsable)
"""
]
- for ens in M["ens"]:
- u = db.session.get(User, ens["ens_id"])
- if u:
- nom = u.get_nomcomplet()
- else:
- nom = "? (compte inconnu)"
+ u: User
+ for u in modimpl.enseignants:
H.append(
f"""
- - {nom} (supprimer)
"""
)
@@ -1006,7 +1006,7 @@ def edit_enseignants_form(moduleimpl_id):
Pour changer le responsable du module, passez par la
page "Modification du semestre",
accessible uniquement au responsable de la formation (chef de département)
@@ -1061,8 +1061,8 @@ def edit_enseignants_form(moduleimpl_id):
else:
# et qu'il n'est pas deja:
if (
- ens_id in [x["ens_id"] for x in M["ens"]]
- or ens_id == M["responsable_id"]
+ ens_id in (x.id for x in modimpl.enseignants)
+ or ens_id == modimpl.responsable_id
):
H.append(
f"""Enseignant {ens_id} déjà dans la liste !
"""
@@ -1090,7 +1090,7 @@ def edit_moduleimpl_resp(moduleimpl_id: int):
Accessible par Admin et dir des etud si flag resp_can_change_ens
"""
modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
- modimpl.can_change_ens_by(current_user, raise_exc=True) # access control
+ modimpl.can_change_responsable(current_user, raise_exc=True) # access control
H = [
html_sco_header.html_sem_header(
f"""Modification du responsable du