1
0
Fork 0

Compare commits

...

15 Commits

18 changed files with 864 additions and 326 deletions

View File

@ -20,5 +20,5 @@ ignored-classes=Permission,
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=entreprises
good-names=d,e,f,i,j,k,n,nt,t,u,ue,v,x,y,z,H,F
good-names=d,df,e,f,i,j,k,n,nt,t,u,ue,v,x,y,z,H,F

View File

@ -273,5 +273,5 @@ def evaluation_delete(evaluation_id: int):
sco_saisie_notes.evaluation_suppress_alln(
evaluation_id=evaluation_id, dialog_confirmed=True
)
sco_evaluation_db.do_evaluation_delete(evaluation_id)
evaluation.delete()
return "ok"

View File

@ -10,7 +10,7 @@
import datetime
from flask import flash, g, request, url_for
from flask import g, request, url_for
from flask_json import as_json
from flask_login import current_user, login_required

View File

@ -141,6 +141,44 @@ class Evaluation(db.Model):
n = 0 # the only one
return n
def delete(self):
"delete evaluation (commit) (check permission)"
from app.scodoc import sco_evaluation_db
modimpl: ModuleImpl = self.moduleimpl
if not modimpl.can_edit_evaluation(current_user):
raise AccessDenied(
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
)
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(
self.id
) # { etudid : value }
notes = [x["value"] for x in notes_db.values()]
if notes:
raise ScoValueError(
"Impossible de supprimer cette évaluation: il reste des notes"
)
log(f"deleting evaluation {self}")
db.session.delete(self)
db.session.commit()
# inval cache pour ce semestre
sco_cache.invalidate_formsemestre(formsemestre_id=modimpl.formsemestre_id)
# news
url = url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=modimpl.id,
)
ScolarNews.add(
typ=ScolarNews.NEWS_NOTE,
obj=modimpl.id,
text=f"""Suppression d'une évaluation dans <a href="{
url
}">{modimpl.module.titre}</a>""",
url=url,
)
def to_dict(self) -> dict:
"Représentation dict (riche, compat ScoDoc 7)"
e_dict = dict(self.__dict__)

View File

@ -118,6 +118,32 @@ class ModuleImpl(db.Model):
return False
def can_edit_notes(self, user: "User", allow_ens=True) -> bool:
"""True if authuser can enter or edit notes in this module.
If allow_ens, grant access to all ens in this module
Si des décisions de jury ont déjà été saisies dans ce semestre,
seul le directeur des études peut saisir des notes (et il ne devrait pas).
"""
# was sco_permissions_check.can_edit_notes
from app.scodoc import sco_cursus_dut
if not self.formsemestre.etat:
return False # semestre verrouillé
is_dir_etud = user.id in (u.id for u in self.formsemestre.responsables)
can_edit_all_notes = user.has_permission(Permission.ScoEditAllNotes)
if sco_cursus_dut.formsemestre_has_decisions(self.formsemestre_id):
# il y a des décisions de jury dans ce semestre !
return can_edit_all_notes or is_dir_etud
if (
not can_edit_all_notes
and user.id != self.responsable_id
and not is_dir_etud
):
# enseignant (chargé de TD) ?
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:
"""Check if user can modify module resp.
If raise_exc, raises exception (AccessDenied or ScoLockedSemError) if not.

511
app/scodoc/sco_cal.py Normal file
View File

@ -0,0 +1,511 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2023 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
#
##############################################################################
"""Génération calendrier (ancienne présentation)
"""
import calendar
import datetime
import html
import time
from app.scodoc.sco_exceptions import ScoValueError, ScoInvalidDateError
from app.scodoc import sco_preferences
import app.scodoc.sco_utils as scu
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 = [
'<table id="maincalendar" class="maincalendar" border="3" cellpadding="1" cellspacing="1" frame="box">'
]
T.append("<tr>")
month = firstmonth
while 1:
T.append('<td valign="top">')
T.append(MonthTableHead(month))
T.append(
MonthTableBody(
month,
year,
events,
halfday,
dayattributes,
is_work_saturday(),
pad_width=pad_width,
)
)
T.append(MonthTableTail())
T.append("</td>")
if month == lastmonth:
break
month = month + 1
if month > 12:
month = 1
year = year + 1
T.append("</table>")
return "\n".join(T)
# ------ 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&nbsp;",
"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 """<table class="monthcalendar" border="0" cellpadding="0" cellspacing="0" frame="box">
<tr bgcolor="%s"><td class="calcol" colspan="2" align="center">%s</td></tr>\n""" % (
color,
MONTHNAMES_ABREV[month - 1],
)
def MonthTableTail():
return "</table>\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('<td bgcolor="%s" class="calcell">' % color)
else:
cc.append('<td class="calcell">')
if href:
href = 'href="%s"' % href
if descr:
descr = 'title="%s"' % html.escape(descr, quote=True)
if href or descr:
cc.append("<a %s %s>" % (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 = (
"&nbsp;" * (n // 2) + legend + "&nbsp;" * ((n + 1) // 2)
)
else:
legend = "&nbsp;" # empty cell
cc.append(legend)
if href or descr:
cc.append("</a>")
cc.append("</td>")
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(
'<tr bgcolor="%s" class="%s" %s><td class="calday">%d%s</td>%s</tr>'
% (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(
'<tr bgcolor="%s" class="wk%s" %s><td class="calday">%d%s</td>'
% (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('<td bgcolor="%s" class="calcell">' % (color))
else:
cc.append('<td class="calcell">')
if href:
href = 'href="%s"' % href
if descr:
descr = 'title="%s"' % html.escape(descr, quote=True)
if href or descr:
cc.append("<a %s %s>" % (href, descr))
if legend or d == 1:
n = 3 - len(legend) # pad to 3 cars
if n > 0:
legend = (
"&nbsp;" * (n // 2) + legend + "&nbsp;" * ((n + 1) // 2)
)
else:
legend = "&nbsp;&nbsp;&nbsp;" # empty cell
cc.append(legend)
if href or descr:
cc.append("</a>")
cc.append("</td>\n")
T.append("".join(cc) + "</tr>")
return "\n".join(T)

View File

@ -28,19 +28,17 @@
"""Gestion évaluations (ScoDoc7, code en voie de modernisation)
"""
import pprint
import flask
from flask import url_for, g
from flask_login import current_user
from app import db, log
from app.models import Evaluation, ModuleImpl, ScolarNews
from app.models import Evaluation
from app.models.evaluations import check_convert_evaluation_args
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.sco_exceptions import AccessDenied
from app.scodoc import sco_cache
from app.scodoc import sco_moduleimpl
@ -119,42 +117,6 @@ def do_evaluation_edit(args):
)
def do_evaluation_delete(evaluation_id):
"delete evaluation"
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
modimpl: ModuleImpl = evaluation.moduleimpl
if not modimpl.can_edit_evaluation(current_user):
raise AccessDenied(
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
)
notes_db = do_evaluation_get_all_notes(evaluation_id) # { etudid : value }
notes = [x["value"] for x in notes_db.values()]
if notes:
raise ScoValueError(
"Impossible de supprimer cette évaluation: il reste des notes"
)
log(f"deleting evaluation {evaluation}")
db.session.delete(evaluation)
db.session.commit()
# inval cache pour ce semestre
sco_cache.invalidate_formsemestre(formsemestre_id=modimpl.formsemestre_id)
# news
url = url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=modimpl.id,
)
ScolarNews.add(
typ=ScolarNews.NEWS_NOTE,
obj=modimpl.id,
text=f"""Suppression d'une évaluation dans <a href="{
url
}">{modimpl.module.titre}</a>""",
url=url,
)
# ancien _notes_getall
def do_evaluation_get_all_notes(
evaluation_id, table="notes_notes", filter_suppressed=True, by_uid=None

View File

@ -351,14 +351,15 @@ def evaluation_create_form(
else:
date_debut = None
args.pop("jour", None)
if args.get("heure_debut"):
if date_debut and args.get("heure_debut"):
try:
heure_debut = heure_to_time(args["heure_debut"])
except ValueError as exc:
raise ScoValueError("Heure début invalide") from exc
args["date_debut"] = datetime.datetime.combine(date_debut, heure_debut)
args.pop("heure_debut", None)
if args.get("heure_fin"):
# note: ce formulaire ne permet de créer que des évaluation avec debut et fin sur le même jour.
if date_debut and args.get("heure_fin"):
try:
heure_fin = heure_to_time(args["heure_fin"])
except ValueError as exc:

View File

@ -30,16 +30,17 @@
import collections
import datetime
import operator
import time
from flask import url_for
from flask import g
from flask_login import current_user
from flask import request
from app import db
from app.auth.models import User
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.models import Evaluation, FormSemestre
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
@ -645,78 +646,64 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True):
"""HTML description of evaluation, for page headers
edit_in_place: allow in-place editing when permitted (not implemented)
"""
E = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id})[0]
moduleimpl_id = E["moduleimpl_id"]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
formsemestre_id = M["formsemestre_id"]
u = sco_users.user_info(M["responsable_id"])
resp = u["prenomnom"]
nomcomplet = u["nomcomplet"]
can_edit = sco_permissions_check.can_edit_notes(
current_user, moduleimpl_id, allow_ens=False
)
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
modimpl = evaluation.moduleimpl
responsable: User = db.session.get(User, modimpl.responsable_id)
resp_nomprenom = responsable.get_prenomnom()
resp_nomcomplet = responsable.get_nomcomplet()
can_edit = modimpl.can_edit_notes(current_user, allow_ens=False)
link = (
'<span class="evallink"><a class="stdlink" href="evaluation_listenotes?moduleimpl_id=%s">voir toutes les notes du module</a></span>'
% moduleimpl_id
)
mod_descr = (
'<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> <span class="resp">(resp. <a title="%s">%s</a>)</span> %s'
% (
moduleimpl_id,
Mod["code"] or "",
Mod["titre"] or "?",
nomcomplet,
resp,
link,
)
)
mod_descr = f"""<a class="stdlink" href="{url_for("notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=modimpl.id,
)}">{modimpl.module.code or ""} {modimpl.module.abbrev or modimpl.module.titre or "?"}</a>
<span class="resp">(resp. <a title="{resp_nomcomplet}">{resp_nomprenom}</a>)</span>
<span class="evallink"><a class="stdlink"
href="{url_for(
"notes.evaluation_listenotes",
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
}">voir toutes les notes du module</a></span>
"""
etit = E["description"] or ""
if etit:
etit = ' "' + etit + '"'
if Mod["module_type"] == ModuleType.MALUS:
etit += ' <span class="eval_malus">(points de malus)</span>'
eval_titre = f' "{evaluation.description}"' if evaluation.description else ""
if modimpl.module.module_type == ModuleType.MALUS:
eval_titre += ' <span class="eval_malus">(points de malus)</span>'
H = [
'<span class="eval_title">Évaluation%s</span><p><b>Module : %s</b></p>'
% (etit, mod_descr)
f"""<span class="eval_title">Évaluation{eval_titre}</span>
<p><b>Module : {mod_descr}</b>
</p>"""
]
if Mod["module_type"] == ModuleType.MALUS:
if modimpl.module.module_type == ModuleType.MALUS:
# Indique l'UE
ue = sco_edit_ue.ue_list(args={"ue_id": Mod["ue_id"]})[0]
H.append("<p><b>UE : %(acronyme)s</b></p>" % ue)
ue = modimpl.module.ue
H.append(f"<p><b>UE : {ue.acronyme}</b></p>")
# store min/max values used by JS client-side checks:
H.append(
'<span id="eval_note_min" class="sco-hidden">-20.</span><span id="eval_note_max" class="sco-hidden">20.</span>'
"""<span id="eval_note_min" class="sco-hidden">-20.</span>
<span id="eval_note_max" class="sco-hidden">20.</span>"""
)
else:
# date et absences (pas pour evals de malus)
if E["jour"]:
jour = E["jour"]
H.append("<p>Réalisée le <b>%s</b> " % (jour))
if E["heure_debut"] != E["heure_fin"]:
H.append("de %s à %s " % (E["heure_debut"], E["heure_fin"]))
group_id = sco_groups.get_default_group(formsemestre_id)
if evaluation.date_debut is not None:
H.append(f"<p>Réalisée le <b>{evaluation.descr_date()}</b> ")
group_id = sco_groups.get_default_group(modimpl.formsemestre_id)
H.append(
f"""<span class="noprint"><a href="{url_for(
'assiduites.get_etat_abs_date',
f"""<span class="evallink"><a class="stdlink" href="{url_for(
'assiduites.etat_abs_date',
scodoc_dept=g.scodoc_dept,
group_ids=group_id,
desc=E["description"],
jour=E["jour"],
heure_debut=E["heure_debut"],
heure_fin=E["heure_fin"],
desc=evaluation.description or "",
date_debut=evaluation.date_debut.isoformat(),
date_fin=evaluation.date_fin.isoformat(),
)
}">(absences ce jour)</a></span>"""
}">absences ce jour</a></span>"""
)
else:
jour = "<em>pas de date</em>"
H.append("<p>Réalisée le <b>%s</b> " % (jour))
H.append("<p><em>sans date</em> ")
H.append(
'</p><p>Coefficient dans le module: <b>%s</b>, notes sur <span id="eval_note_max">%g</span> '
% (E["coefficient"], E["note_max"])
f"""</p><p>Coefficient dans le module: <b>{evaluation.coefficient or "0"}</b>,
notes sur <span id="eval_note_max">{(evaluation.note_max or 0):g}</span> """
)
H.append('<span id="eval_note_min" class="sco-hidden">0.</span>')
if can_edit:
@ -730,7 +717,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True):
if link_saisie:
H.append(
f"""
<a class="stdlink" href="{url_for(
<a style="margin-left: 12px;" class="stdlink" href="{url_for(
"notes.saisie_notes", scodoc_dept=g.scodoc_dept, evaluation_id=evaluation_id)
}">saisie des notes</a>
"""

View File

@ -50,7 +50,6 @@ from app.scodoc import htmlutils
from app.scodoc import sco_cal
from app.scodoc import sco_compute_moy
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre_status
from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl
@ -59,19 +58,15 @@ from app.tables import list_etuds
# menu evaluation dans moduleimpl
def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
"Menu avec actions sur une evaluation"
E = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id})[0]
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
modimpl: ModuleImpl = evaluation.moduleimpl
group_id = sco_groups.get_default_group(modimpl.formsemestre_id)
evaluation_id = evaluation.id
can_edit_notes = modimpl.can_edit_notes(current_user, allow_ens=False)
can_edit_notes_ens = modimpl.can_edit_notes(current_user)
group_id = sco_groups.get_default_group(modimpl["formsemestre_id"])
if (
sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"], allow_ens=False
)
and nbnotes != 0
):
if can_edit_notes and nbnotes != 0:
sup_label = "Supprimer évaluation impossible (il y a des notes)"
else:
sup_label = "Supprimer évaluation"
@ -83,9 +78,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
"args": {
"evaluation_id": evaluation_id,
},
"enabled": sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"]
),
"enabled": can_edit_notes_ens,
},
{
"title": "Modifier évaluation",
@ -93,9 +86,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
"args": {
"evaluation_id": evaluation_id,
},
"enabled": sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"], allow_ens=False
),
"enabled": can_edit_notes,
},
{
"title": sup_label,
@ -103,10 +94,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
"args": {
"evaluation_id": evaluation_id,
},
"enabled": nbnotes == 0
and sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"], allow_ens=False
),
"enabled": nbnotes == 0 and can_edit_notes,
},
{
"title": "Supprimer toutes les notes",
@ -114,9 +102,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
"args": {
"evaluation_id": evaluation_id,
},
"enabled": sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"], allow_ens=False
),
"enabled": can_edit_notes,
},
{
"title": "Afficher les notes",
@ -132,21 +118,18 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
"args": {
"evaluation_id": evaluation_id,
},
"enabled": sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"]
),
"enabled": can_edit_notes_ens,
},
{
"title": "Absences ce jour",
"endpoint": "assiduites.get_etat_abs_date",
"endpoint": "assiduites.etat_abs_date",
"args": {
"group_ids": group_id,
"desc": E["description"],
"jour": E["jour"],
"heure_debut": E["heure_debut"],
"heure_fin": E["heure_fin"],
"desc": evaluation.description or "",
"date_debut": evaluation.date_debut.isoformat(),
"date_fin": evaluation.date_fin.isoformat(),
},
"enabled": E["jour"],
"enabled": evaluation.date_debut is not None,
},
{
"title": "Vérifier notes vs absents",
@ -154,7 +137,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
"args": {
"evaluation_id": evaluation_id,
},
"enabled": nbnotes > 0 and E["jour"],
"enabled": nbnotes > 0 and evaluation.date_debut is not None,
},
]
@ -714,7 +697,7 @@ def _ligne_evaluation(
if can_edit_notes:
H.append(
moduleimpl_evaluation_menu(
evaluation.id,
evaluation,
nbnotes=etat["nb_notes"],
)
)

View File

@ -1315,7 +1315,7 @@ a.smallbutton {
}
span.evallink {
font-size: 80%;
margin-left: 16px;
font-weight: normal;
}

View File

@ -468,7 +468,8 @@
if (to[0] != "n") {
groupeSelected.closest(".grpPartitions").querySelector(`[value="${to}"]`).click();
} else {
groupeSelected.closest(".grpPartitions").querySelector(`[value="aucun"]`).click();
let toNumber = to.split("-")[1];
groupeSelected.closest(".grpPartitions").querySelector(`[data-idpartition="${toNumber}"] [value="aucun"]`).click();
}
})

View File

@ -1,8 +1,8 @@
import datetime
from flask import g, request, render_template
from flask import abort, url_for
from flask_login import current_user
from app import db
from app.comp import res_sem
@ -25,14 +25,16 @@ from app.views import ScoData
# ---------------
from app.scodoc.sco_permissions import Permission
from app.scodoc import html_sco_header
from app.scodoc import safehtml
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_preferences
from app.scodoc import sco_groups_view
from app.scodoc import sco_etud
from app.scodoc import sco_find_etud
from flask_login import current_user
from app.scodoc import sco_utils as scu
from app.scodoc import sco_assiduites as scass
from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import ScoValueError
from app.tables.visu_assiduites import TableAssi, etuds_sorted_from_ids
@ -731,17 +733,23 @@ def visu_assiduites_group():
).build()
@bp.route("/EtatAbsencesDate")
@bp.route("/etat_abs_date")
@scodoc
@permission_required(Permission.ScoView)
def get_etat_abs_date():
infos_date = {
"jour": request.args.get("jour"),
"heure_debut": request.args.get("heure_debut"),
"heure_fin": request.args.get("heure_fin"),
"title": request.args.get("desc"),
}
def etat_abs_date():
"""date_debut, date_fin en ISO"""
date_debut_str = request.args.get("date_debut")
date_fin_str = request.args.get("date_fin")
title = request.args.get("desc")
group_ids: list[int] = request.args.get("group_ids", None)
try:
date_debut = datetime.datetime.fromisoformat(date_debut_str)
except ValueError as exc:
raise ScoValueError("date_debut invalide") from exc
try:
date_fin = datetime.datetime.fromisoformat(date_fin_str)
except ValueError as exc:
raise ScoValueError("date_fin invalide") from exc
if group_ids is None:
group_ids = []
else:
@ -754,14 +762,6 @@ def get_etat_abs_date():
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
for m in groups_infos.members
]
date_debut = scu.is_iso_formated(
f"{infos_date['jour']}T{infos_date['heure_debut'].replace('h',':')}", True
)
date_fin = scu.is_iso_formated(
f"{infos_date['jour']}T{infos_date['heure_fin'].replace('h',':')}", True
)
assiduites: Assiduite = Assiduite.query.filter(
Assiduite.etudid.in_([e["etudid"] for e in etuds])
)
@ -791,7 +791,7 @@ def get_etat_abs_date():
etudiants = list(sorted(etudiants, key=lambda x: x["nom"]))
header: str = html_sco_header.sco_header(
page_title=infos_date["title"],
page_title=safehtml.html_to_safe_html(title),
init_qtip=True,
)

View File

@ -1654,30 +1654,37 @@ sco_publish(
@scodoc7func
def evaluation_delete(evaluation_id):
"""Form delete evaluation"""
El = sco_evaluation_db.get_evaluation_dict(args={"evaluation_id": evaluation_id})
if not El:
raise ScoValueError("Evaluation inexistante ! (%s)" % evaluation_id)
E = El[0]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
tit = "Suppression de l'évaluation %(description)s (%(jour)s)" % E
etat = sco_evaluations.do_evaluation_etat(evaluation_id)
evaluation: Evaluation = (
Evaluation.query.filter_by(id=evaluation_id)
.join(ModuleImpl)
.join(FormSemestre)
.filter_by(dept_id=g.scodoc_dept_id)
.first_or_404()
)
tit = f"""Suppression de l'évaluation {evaluation.description or ""} ({evaluation.descr_date()})"""
etat = sco_evaluations.do_evaluation_etat(evaluation.id)
H = [
html_sco_header.html_sem_header(tit, with_h2=False),
"""<h2 class="formsemestre">Module <tt>%(code)s</tt> %(titre)s</h2>""" % Mod,
"""<h3>%s</h3>""" % tit,
"""<p class="help">Opération <span class="redboldtext">irréversible</span>. Si vous supprimez l'évaluation, vous ne pourrez pas retrouver les notes associées.</p>""",
f"""
{html_sco_header.html_sem_header(tit, with_h2=False)}
<h2 class="formsemestre">Module <tt>{evaluation.moduleimpl.module.code}</tt>
{evaluation.moduleimpl.module.titre_str()}</h2>
<h3>{tit}</h3>
<p class="help">Opération <span class="redboldtext">irréversible</span>.
Si vous supprimez l'évaluation, vous ne pourrez pas retrouver les notes associées.
</p>
""",
]
warning = False
if etat["nb_notes_total"]:
warning = True
nb_desinscrits = etat["nb_notes_total"] - etat["nb_notes"]
H.append(
"""<div class="ue_warning"><span>Il y a %s notes""" % etat["nb_notes_total"]
f"""<div class="ue_warning"><span>Il y a {etat["nb_notes_total"]} notes"""
)
if nb_desinscrits:
H.append(
""" (dont %s d'étudiants qui ne sont plus inscrits)""" % nb_desinscrits
""" (dont {nb_desinscrits} d'étudiants qui ne sont plus inscrits)"""
)
H.append(""" dans l'évaluation</span>""")
if etat["nb_notes"] == 0:
@ -1687,8 +1694,13 @@ def evaluation_delete(evaluation_id):
if etat["nb_notes"]:
H.append(
"""<p>Suppression impossible (effacer les notes d'abord)</p><p><a class="stdlink" href="moduleimpl_status?moduleimpl_id=%s">retour au tableau de bord du module</a></p></div>"""
% E["moduleimpl_id"]
f"""<p>Suppression impossible (effacer les notes d'abord)</p>
<p><a class="stdlink" href="{
url_for("notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept, moduleimpl_id=evaluation.moduleimpl_id)
}">retour au tableau de bord du module</a>
</p>
</div>"""
)
return "\n".join(H) + html_sco_header.sco_footer()
if warning:
@ -1698,7 +1710,7 @@ def evaluation_delete(evaluation_id):
request.base_url,
scu.get_request_args(),
(("evaluation_id", {"input_type": "hidden"}),),
initvalues=E,
initvalues={"evaluation_id": evaluation.id},
submitlabel="Confirmer la suppression",
cancelbutton="Annuler",
)
@ -1709,17 +1721,17 @@ def evaluation_delete(evaluation_id):
url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=E["moduleimpl_id"],
moduleimpl_id=evaluation.moduleimpl_id,
)
)
else:
sco_evaluation_db.do_evaluation_delete(E["evaluation_id"])
evaluation.delete()
return (
"\n".join(H)
+ f"""<p>OK, évaluation supprimée.</p>
<p><a class="stdlink" href="{
url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept,
moduleimpl_id=E["moduleimpl_id"])
moduleimpl_id=evaluation.moduleimpl_id)
}">Continuer</a></p>"""
+ html_sco_header.sco_footer()
)

View File

@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "9.6.11"
SCOVERSION = "9.6.16"
SCONAME = "ScoDoc"

View File

@ -5,40 +5,51 @@
"""Construction des fichiers exemples pour la documentation.
Usage:
cd /opt/scodoc/tests/api
python make_samples.py [entry_names]
python make_samples.py -i <filepath> [entrynames]
python tests/api/make_samples.py [entry_names]
python tests/api/make_samples.py -i <filepath> [entrynames]
si entry_names est spécifié, la génération est restreints aux exemples cités. expl: `python make_samples departements departement-formsemestres`
doit être exécutée immédiatement apres une initialisation de la base pour test API! (car dépendant des identifiants générés lors de la création des objets)
cd /opt/scodoc/tests/api
tools/create_database.sh --drop SCODOC_TEST_API && flask db upgrade &&flask sco-db-init --erase && flask init-test-database
Si entry_names est spécifié, la génération est restreinte aux exemples cités.
Exemple:
python make_samples departements departement-formsemestres
Créer éventuellement un fichier `.env` dans /opt/scodoc/tests/api
avec la config du client API:
```
SCODOC_URL = "http://localhost:5000/"
Doit être exécutée immédiatement apres une initialisation de la base pour test API!
(car dépendant des identifiants générés lors de la création des objets)
Modifer le /opt/scodoc/.env pour pointer sur la base test
SCODOC_DATABASE_URI="postgresql:///SCODOC_TEST_API"
puis re-créer cette base
tools/create_database.sh --drop SCODOC_TEST_API
flask db upgrade
flask sco-db-init --erase
flask init-test-database
et lancer le serveur test:
flask run --debug
```
Cet utilitaire prend en donnée le fichier de nom `samples.csv` contenant la description des exemples (séparés par une tabulation (\t), une ligne par exemple)
* Le nom de l'exemple donne le nom du fichier généré (nom_exemple => nom_exemple.json.md). plusieurs lignes peuvent partager le même nom. dans ce cas le fichier contiendra chacun des exemples
Cet utilitaire prend en argument le fichier de nom `samples.csv` contenant la description
des exemples (séparés par une tabulation (\t), une ligne par exemple)
* Le nom de l'exemple donne le nom du fichier généré (nom_exemple => nom_exemple.json.md).
Plusieurs lignes peuvent partager le même nom. dans ce cas le fichier contiendra
chacun des exemples
* l'url utilisée
* la permission nécessaire (par défaut ScoView)
* la méthode GET,POST à utiliser (si commence par #, la ligne est ignorée)
* les arguments éventuel (en cas de POST): une chaîne de caractère selon json
Implémentation:
Le code complète une structure de données (Samples) qui est un dictionnaire de set (indicé par le nom des exemple.
Le code complète une structure de données (Samples) qui est un dictionnaire de set
(indicé par le nom des exemples).
Chacun des éléments du set est un exemple (Sample)
Quand la structure est complète, on génére tous les fichiers textes
- nom de l exemple
- un ou plusieurs exemples avec pour chaucn
- l url utilisée
- nom de l'exemple
- un ou plusieurs exemples avec pour chacun
- l'url utilisée
- les arguments éventuels
- le résultat
Le tout mis en forme au format markdown et rangé dans le répertoire DATA_DIR (/tmp/samples) qui est créé ou écrasé si déjà existant
Le tout mis en forme au format markdown et rangé dans le répertoire DATA_DIR (/tmp/samples)
qui est créé ou écrasé si déjà existant.
"""
import os
import shutil
@ -50,7 +61,7 @@ from pprint import pprint as pp
import urllib3
import json
from pandas import read_csv
import pandas as pd
from setup_test_api import (
API_PASSWORD,
@ -68,6 +79,10 @@ DATA_DIR = "/tmp/samples/"
SAMPLES_FILENAME = "tests/ressources/samples/samples.csv"
class SampleException(Exception):
pass
class Sample:
def __init__(self, url, method="GET", permission="ScoView", content=None):
self.content = content
@ -83,7 +98,7 @@ class Sample:
elif permission == "ScoUsersAdmin":
HEADERS = get_auth_headers("admin_api", "admin_api")
else:
raise Exception(f"Bad permission : {permission}")
raise SampleException(f"Bad permission : {permission}")
if self.method == "GET":
self.result = GET(self.url, HEADERS)
elif self.method == "POST":
@ -94,20 +109,19 @@ class Sample:
self.result = POST_JSON(self.url, json.loads(self.content), HEADERS)
elif self.method[0] != "#":
error = f'Bad method : "{self.method}"'
raise Exception(error)
raise SampleException(error)
self.shorten()
file = open(f"sample_TEST.json.md", "tw")
self.dump(file)
file.close()
with open("sample_TEST.json.md", "tw", encoding="utf-8") as f:
self.dump(f)
def _shorten(
self, item
): # abrege les longues listes (limite à 2 éléments et affiche "... etc. à la place"
def _shorten(self, item):
"Abrège les longues listes: limite à 2 éléments et affiche '...' etc. à la place"
if isinstance(item, list):
return [self._shorten(child) for child in item[:2]] + ["... etc."]
return [self._shorten(child) for child in item[:2] + ["..."]]
return item
def shorten(self):
"Abrège le résultat"
self.result = self._shorten(self.result)
def pp(self):
@ -122,8 +136,8 @@ class Sample:
file.write(f"#### {self.method} {self.url}\n")
if len(self.content) > 0:
file.write(f"> `Content-Type: application/json`\n")
file.write(f"> \n")
file.write("> `Content-Type: application/json`\n")
file.write("> \n")
file.write(f"> `{self.content}`\n\n")
file.write("```json\n")
@ -143,7 +157,7 @@ class Samples:
"""Entry_names: la liste des entrées à reconstruire.
si None, la totalité des lignes de samples.csv est prise en compte
"""
self.entries = defaultdict(lambda: set())
self.entries = defaultdict(set)
self.entry_names = entry_names
def add_sample(self, line):
@ -171,35 +185,35 @@ class Samples:
def dump(self):
for entry, samples in self.entries.items():
file = open(f"{DATA_DIR}sample_{entry}.json.md", "tw")
file.write(f"### {entry}\n\n")
for sample in sorted(
samples, key=lambda s: s.url
): # sorted de façon à rendre le fichier résultat déterministe (i.e. indépendant de l ordre d arrivée des résultats)
sample.dump(file)
file.close()
with open(f"{DATA_DIR}sample_{entry}.json.md", "tw", encoding="utf-8") as f:
f.write(f"### {entry}\n\n")
# Trié de façon à rendre le fichier indépendant de l'ordre des résultats
for sample in sorted(samples, key=lambda s: s.url):
sample.dump(f)
def make_samples(samples_filename):
if len(sys.argv) == 1:
entry_names = None
elif len(sys.argv) >= 3 and sys.argv[1] == "-i":
"Génère les samples"
entry_names = None
if len(sys.argv) >= 3 and sys.argv[1] == "-i":
samples_filename = sys.argv[2]
entry_names = sys.argv[3:] if len(sys.argv) > 3 else None
else:
entry_names = sys.argv[1:]
if os.path.exists(DATA_DIR):
if not os.path.isdir(DATA_DIR):
raise f"{DATA_DIR} existe déjà et n'est pas un répertoire"
else:
# DATA_DIR existe déjà - effacer et recréer
shutil.rmtree(DATA_DIR)
os.mkdir(DATA_DIR)
raise SampleException(f"{DATA_DIR} existe déjà et n'est pas un répertoire")
# DATA_DIR existe déjà - effacer et recréer
shutil.rmtree(DATA_DIR)
os.mkdir(DATA_DIR)
else:
os.mkdir(DATA_DIR)
samples = Samples(entry_names)
df = read_csv(
df = pd.read_csv(
samples_filename,
comment="#",
sep=";",
quotechar='"',
dtype={
@ -212,11 +226,12 @@ def make_samples(samples_filename):
keep_default_na=False,
)
df = df.reset_index()
df.apply(lambda line: samples.add_sample(line), axis=1)
df.apply(samples.add_sample, axis=1)
samples.dump()
return samples
if not CHECK_CERTIFICATE:
urllib3.disable_warnings()
make_samples(SAMPLES_FILENAME)

View File

@ -1,119 +1,120 @@
"entry_name";"url";"permission";"method";"content"
"assiduite_create";"/assiduite/1/create";"ScoView";"POST";"{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""absent""}"
"assiduite_create";"/assiduite/1/create/batch";"ScoView";"POST";"{""batch"":[{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""absent""},{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""retard""},{""date_debut"": ""2022-10-27T11:00"",""date_fin"": ""2022-10-27T13:00"",""etat"": ""present""}]}"
"assiduite_delete";"/assiduite/delete";"ScoView";"POST";"{""assiduite_id"": 1}"
"assiduite_delete";"/assiduite/delete/batch";"ScoView";"POST";"{""batch"":[2,2,3]}"
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""etat"": ""retard"",""moduleimpl_id"":3}"
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""etat"":""absent""}"
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""moduleimpl_id"":2}"
"assiduite";"/assiduite/1";"ScoView";"GET";
"assiduites";"/assiduites/1";"ScoView";"GET";
"assiduites";"/assiduites/1/query?etat=retard";"ScoView";"GET";
"assiduites";"/assiduites/1/query?moduleimpl_id=1";"ScoView";"GET";
"assiduites_count";"/assiduites/1/count";"ScoView";"GET";
"assiduites_count";"/assiduites/1/count/query?etat=retard";"ScoView";"GET";
"assiduites_count";"/assiduites/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
"assiduites_count";"/assiduites/1/count/query?etat=retard";"ScoView";"GET";
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count";"ScoView";"GET";
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=retard";"ScoView";"GET";
"assiduites_formsemestre";"/assiduites/formsemestre/1";"ScoView";"GET";
"assiduites_formsemestre";"/assiduites/formsemestre/1/query?etat=retard";"ScoView";"GET";
"assiduites_formsemestre";"/assiduites/formsemestre/1/query?moduleimpl_id=1";"ScoView";"GET";
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count";"ScoView";"GET";
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=retard";"ScoView";"GET";
"assiduites_formsemestre_count";"/assiduites/formsemestre/1/count/query?etat=present,retard&metric=compte,heure";"ScoView";"GET";
"assiduite_create";"/assiduite/1/create";"ScoView";"POST";"{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""absent""}"
"assiduite_create";"/assiduite/1/create/batch";"ScoView";"POST";"{""batch"":[{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""absent""},{""date_debut"": ""2022-10-27T08:00"",""date_fin"": ""2022-10-27T10:00"",""etat"": ""retard""},{""date_debut"": ""2022-10-27T11:00"",""date_fin"": ""2022-10-27T13:00"",""etat"": ""present""}]}"
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""etat"":""absent""}"
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""moduleimpl_id"":2}"
"assiduite_edit";"/assiduite/1/edit";"ScoView";"POST";"{""etat"": ""retard"",""moduleimpl_id"":3}"
"assiduite_delete";"/assiduite/delete";"ScoView";"POST";"{""assiduite_id"": 1}"
"assiduite_delete";"/assiduite/delete/batch";"ScoView";"POST";"{""batch"":[2,2,3]}"
"departements";"/departements";"ScoView";"GET";
"departements-ids";"/departements_ids";"ScoView";"GET";
"departement";"/departement/TAPI";"ScoView";"GET";
"departement";"/departement/id/1";"ScoView";"GET";
"departement-etudiants";"/departement/TAPI/etudiants";"ScoView";"GET";
"departement-etudiants";"/departement/id/1/etudiants";"ScoView";"GET";
"departement-formsemestres_ids";"/departement/TAPI/formsemestres_ids";"ScoView";"GET";
"departement-formsemestres_ids";"/departement/id/1/formsemestres_ids";"ScoView";"GET";
"departement-formsemestres-courants";"/departement/TAPI/formsemestres_courants";"ScoView";"GET";
"departement-formsemestres-courants";"/departement/id/1/formsemestres_courants";"ScoView";"GET";
"assiduites";"/assiduites/1";"ScoView";"GET";
"assiduites";"/assiduites/1/query?etat=retard";"ScoView";"GET";
"assiduites";"/assiduites/1/query?moduleimpl_id=1";"ScoView";"GET";
"departement-create";"/departement/create";"ScoSuperAdmin";"POST";"{""acronym"": ""NEWONE"" , ""visible"": true}"
"departement-edit";"/departement/NEWONE/edit";"ScoSuperAdmin";"POST";"{""visible"": false}"
"departement-delete";"/departement/NEWONE/delete";"ScoSuperAdmin";"POST";
"etudiants-courants";"/etudiants/courants?date_courante=2022-07-20";"ScoView";"GET";
"etudiants-courants";"/etudiants/courants/long?date_courante=2022-07-20";"ScoView";"GET";
"departement-edit";"/departement/NEWONE/edit";"ScoSuperAdmin";"POST";"{""visible"": false}"
"departement-etudiants";"/departement/id/1/etudiants";"ScoView";"GET";
"departement-etudiants";"/departement/TAPI/etudiants";"ScoView";"GET";
"departement-formsemestres_ids";"/departement/id/1/formsemestres_ids";"ScoView";"GET";
"departement-formsemestres_ids";"/departement/TAPI/formsemestres_ids";"ScoView";"GET";
"departement-formsemestres-courants";"/departement/id/1/formsemestres_courants";"ScoView";"GET";
"departement-formsemestres-courants";"/departement/TAPI/formsemestres_courants";"ScoView";"GET";
"departement-logo";"/departement/id/1/logo/D";"ScoSuperAdmin";"GET";
"departement-logo";"/departement/TAPI/logo/D";"ScoSuperAdmin";"GET";
"departement-logos";"/departement/id/1/logos";"ScoSuperAdmin";"GET";
"departement-logos";"/departement/TAPI/logos";"ScoSuperAdmin";"GET";
"departement";"/departement/id/1";"ScoView";"GET";
"departement";"/departement/TAPI";"ScoView";"GET";
"departements-ids";"/departements_ids";"ScoView";"GET";
"departements";"/departements";"ScoView";"GET";
"etudiant_formsemestres";"/etudiant/nip/11/formsemestres";"ScoView";"GET";
"etudiant-formsemestre-bulletin";"/etudiant/etudid/11/formsemestre/1/bulletin";"ScoView";"GET";
#"etudiant-formsemestre-bulletin";"/etudiant/ine/INE11/formsemestre/1/bulletin";"ScoView";"GET";
#"etudiant-formsemestre-bulletin";"/etudiant/nip/11/formsemestre/1/bulletin";"ScoView";"GET";
#"etudiant-formsemestre-bulletin";"/etudiant/nip/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
"etudiant-formsemestre-groups";"/etudiant/etudid/11/formsemestre/1/groups";"ScoView";"GET";
"etudiant-formsemestres";"/etudiant/etudid/11/formsemestres";"ScoView";"GET";
"etudiant-formsemestres";"/etudiant/ine/INE11/formsemestres";"ScoView";"GET";
"etudiant";"/etudiant/etudid/11";"ScoView";"GET";
"etudiant";"/etudiant/nip/11";"ScoView";"GET";
"etudiant";"/etudiant/ine/INE11";"ScoView";"GET";
"etudiant";"/etudiant/nip/11";"ScoView";"GET";
"etudiants-clef";"/etudiants/etudid/11";"ScoView";"GET";
"etudiants-clef";"/etudiants/ine/INE11";"ScoView";"GET";
"etudiants-clef";"/etudiants/nip/11";"ScoView";"GET";
"etudiant-formsemestres";"/etudiant/etudid/11/formsemestres";"ScoView";"GET";
"etudiant-formsemestres";"/etudiant/ine/INE11/formsemestres";"ScoView";"GET";
"etudiant_formsemestres";"/etudiant/nip/11/formsemestres";"ScoView";"GET";
"etudiant-formsemestre-bulletin";"/etudiant/etudid/11/formsemestre/1/bulletin";"ScoView";"GET";
"etudiant-formsemestre-bulletin";"/etudiant/ine/INE11/formsemestre/1/bulletin";"ScoView";"GET";
"etudiant-formsemestre-bulletin";"/etudiant/nip/11/formsemestre/1/bulletin";"ScoView";"GET";
"etudiant-formsemestre-bulletin";"/etudiant/nip/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
"etudiant-formsemestre-groups";"/etudiant/etudid/11/formsemestre/1/groups";"ScoView";"GET";
"formations";"/formations";"ScoView";"GET";
"formations_ids";"/formations_ids";"ScoView";"GET";
"formation";"/formation/1";"ScoView";"GET";
"formation-export";"/formation/1/export";"ScoView";"GET";
"etudiants-courants";"/etudiants/courants?date_courante=2022-07-20";"ScoView";"GET";
"etudiants-courants";"/etudiants/courants/long?date_courante=2022-07-20";"ScoView";"GET";
"evaluation";"/evaluation/1";"ScoView";"GET";
"evaluation-notes";"/evaluation/1/notes";"ScoView";"GET";
"formation-export";"/formation/1/export_with_ids";"ScoView";"GET";
"formation-export";"/formation/1/export";"ScoView";"GET";
"formation-referentiel_competences";"/formation/1/referentiel_competences";"ScoView";"GET";
"moduleimpl";"/moduleimpl/1";"ScoView";"GET";
"formation";"/formation/1";"ScoView";"GET";
"formations_ids";"/formations_ids";"ScoView";"GET";
"formations";"/formations";"ScoView";"GET";
"formsemestre-bulletins";"/formsemestre/1/bulletins";"ScoView";"GET";
"formsemestre-decisions_jury";"/formsemestre/1/decisions_jury";"ScoView";"GET";
"formsemestre-etat_evals";"/formsemestre/1/etat_evals";"ScoView";"GET";
"formsemestre-etudiants-query";"/formsemestre/1/etudiants/query?etat=D";"ScoView";"GET";
"formsemestre-etudiants";"/formsemestre/1/etudiants";"ScoView";"GET";
"formsemestre-etudiants";"/formsemestre/1/etudiants/long";"ScoView";"GET";
"formsemestre-partition-create";"/formsemestre/1/partition/create";"ScoSuperAdmin";"POST";"{""partition_name"": ""PART""} "
"formsemestre-partitions-order";"/formsemestre/1/partitions/order";"ScoSuperAdmin";"POST";"[ 1 ]"
"formsemestre-partitions";"/formsemestre/1/partitions";"ScoView";"GET";
"formsemestre-programme";"/formsemestre/1/programme";"ScoView";"GET";
"formsemestre-resultats";"/formsemestre/1/resultats";"ScoView";"GET";
"formsemestre";"/formsemestre/1";"ScoView";"GET";
"formsemestres-query";"/formsemestres/query?annee_scolaire=2022&etape_apo=A2";"ScoView";"GET";
"formsemestres-query";"/formsemestres/query?nip=11";"ScoView";"GET";
"formsemestre-bulletins";"/formsemestre/1/bulletins";"ScoView";"GET";
"formsemestre-programme";"/formsemestre/1/programme";"ScoView";"GET";
"formsemestre-etudiants";"/formsemestre/1/etudiants";"ScoView";"GET";
"formsemestre-etudiants";"/formsemestre/1/etudiants/long";"ScoView";"GET";
"formsemestre-etudiants-query";"/formsemestre/1/etudiants/query?etat=D";"ScoView";"GET";
"formsemestre-etat_evals";"/formsemestre/1/etat_evals";"ScoView";"GET";
"formsemestre-resultats";"/formsemestre/1/resultats";"ScoView";"GET";
"formsemestre-decisions_jury";"/formsemestre/1/decisions_jury";"ScoView";"GET";
"formsemestre-partitions";"/formsemestre/1/partitions";"ScoView";"GET";
"partition";"/partition/1";"ScoView";"GET";
"group-etudiants";"/group/1/etudiants";"ScoView";"GET";
"group-etudiants-query";"/group/1/etudiants/query?etat=D";"ScoView";"GET";
"moduleimpl-evaluations";"/moduleimpl/1/evaluations";"ScoView";"GET";
"evaluation-notes";"/evaluation/1/notes";"ScoView";"GET";
"user";"/user/1";"ScoView";"GET";
"users-query";"/users/query?starts_with=u_";"ScoView";"GET";
"permissions";"/permissions";"ScoView";"GET";
"roles";"/roles";"ScoView";"GET";
"role";"/role/Observateur";"ScoView";"GET";
"group-set_etudiant";"/group/1/set_etudiant/10";"ScoSuperAdmin";"POST";
"group-remove_etudiant";"/group/1/remove_etudiant/10";"ScoSuperAdmin";"POST";
"partition-group-create";"/partition/1/group/create";"ScoSuperAdmin";"POST";"{""group_name"": ""NEW_GROUP""}"
"group-edit";"/group/2/edit";"ScoSuperAdmin";"POST";"{""group_name"": ""NEW_GROUP2""}"
"group-delete";"/group/2/delete";"ScoSuperAdmin";"POST";
"formsemestre-partition-create";"/formsemestre/1/partition/create";"ScoSuperAdmin";"POST";"{""partition_name"": ""PART""} "
"formsemestre-partitions-order";"/formsemestre/1/partitions/order";"ScoSuperAdmin";"POST";"[ 1 ]"
"partition-edit";"/partition/1/edit";"ScoSuperAdmin";"POST";"{""partition_name"":""P2BIS"", ""numero"":3,""bul_show_rank"":true,""show_in_lists"":false, ""groups_editable"":true}"
"partition-remove_etudiant";"/partition/2/remove_etudiant/10";"ScoSuperAdmin";"POST";
"partition-groups-order";"/partition/1/groups/order";"ScoSuperAdmin";"POST";"[ 1 ]"
"group-edit";"/group/2/edit";"ScoSuperAdmin";"POST";"{""group_name"": ""NEW_GROUP2""}"
"group-etudiants-query";"/group/1/etudiants/query?etat=D";"ScoView";"GET";
"group-etudiants";"/group/1/etudiants";"ScoView";"GET";
"group-remove_etudiant";"/group/1/remove_etudiant/10";"ScoSuperAdmin";"POST";
"group-set_etudiant";"/group/1/set_etudiant/10";"ScoSuperAdmin";"POST";
"logo";"/logo/B";"ScoSuperAdmin";"GET";
"logos";"/logos";"ScoSuperAdmin";"GET";
"moduleimpl-evaluations";"/moduleimpl/1/evaluations";"ScoView";"GET";
"moduleimpl";"/moduleimpl/1";"ScoView";"GET";
"partition-delete";"/partition/2/delete";"ScoSuperAdmin";"POST";
"partition-edit";"/partition/1/edit";"ScoSuperAdmin";"POST";"{""partition_name"":""P2BIS"", ""numero"":3,""bul_show_rank"":true,""show_in_lists"":false, ""groups_editable"":true}"
"partition-group-create";"/partition/1/group/create";"ScoSuperAdmin";"POST";"{""group_name"": ""NEW_GROUP""}"
"partition-groups-order";"/partition/1/groups/order";"ScoSuperAdmin";"POST";"[ 1 ]"
"partition-remove_etudiant";"/partition/2/remove_etudiant/10";"ScoSuperAdmin";"POST";
"partition";"/partition/1";"ScoView";"GET";
"permissions";"/permissions";"ScoView";"GET";
"role-add_permission";"/role/customRole/add_permission/ScoUsersView";"ScoSuperAdmin";"POST";
"role-create";"/role/create/customRole";"ScoSuperAdmin";"POST";"{""permissions"": [""ScoView"", ""ScoUsersView""]}"
"role-delete";"/role/customRole/delete";"ScoSuperAdmin";"POST";
"role-edit";"/role/customRole/edit";"ScoSuperAdmin";"POST";"{ ""name"" : ""LaveurDeVitres"", ""permissions"" : [ ""ScoView"" ] }"
"role-remove_permission";"/role/customRole/remove_permission/ScoUsersView";"ScoSuperAdmin";"POST";
"role";"/role/Observateur";"ScoView";"GET";
"roles";"/roles";"ScoView";"GET";
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin";"ScoView";"GET";
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/short";"ScoView";"GET";
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin";"ScoView";"GET";
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin/short";"ScoView";"GET";
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin";"ScoView";"GET";
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/short";"ScoView";"GET";
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
"user-create";"/user/create";"ScoSuperAdmin";"POST";"{""user_name"": ""alain"", ""dept"": null, ""nom"": ""alain"", ""prenom"": ""bruno"", ""active"": true }"
"user-edit";"/user/10/edit";"ScoSuperAdmin";"POST";"{ ""dept"": ""TAPI"", ""nom"": ""alain2"", ""prenom"": ""bruno2"", ""active"": false }"
"user-password";"/user/3/password";"ScoSuperAdmin";"POST";"{ ""password"": ""rePlaCemeNT456averylongandcomplicated"" }"
"user-password";"/user/3/password";"ScoSuperAdmin";"POST";"{ ""password"": ""too_simple"" }"
"user-role-add";"/user/10/role/Observateur/add";"ScoSuperAdmin";"POST";
"user-role-remove";"/user/10/role/Observateur/remove";"ScoSuperAdmin";"POST";
"role-create";"/role/create/customRole";"ScoSuperAdmin";"POST";"{""permissions"": [""ScoView"", ""ScoUsersView""]}"
"role-remove_permission";"/role/customRole/remove_permission/ScoUsersView";"ScoSuperAdmin";"POST";
"role-add_permission";"/role/customRole/add_permission/ScoUsersView";"ScoSuperAdmin";"POST";
"role-edit";"/role/customRole/edit";"ScoSuperAdmin";"POST";"{ ""name"" : ""LaveurDeVitres"", ""permissions"" : [ ""ScoView"" ] }"
"role-delete";"/role/customRole/delete";"ScoSuperAdmin";"POST";
"logos";"/logos";"ScoSuperAdmin";"GET";
"logo";"/logo/B";"ScoSuperAdmin";"GET";
"departement-logos";"/departement/TAPI/logos";"ScoSuperAdmin";"GET";
"departement-logos";"/departement/id/1/logos";"ScoSuperAdmin";"GET";
"departement-logo";"/departement/TAPI/logo/D";"ScoSuperAdmin";"GET";
"departement-logo";"/departement/id/1/logo/D";"ScoSuperAdmin";"GET";
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/short/pdf";"ScoView";"GET";
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/pdf";"ScoView";"GET";
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin/short";"ScoView";"GET";
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin/short";"ScoView";"GET";
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin/short";"ScoView";"GET";
"test-pdf";"/etudiant/etudid/11/formsemestre/1/bulletin";"ScoView";"GET";
"test-pdf";"/etudiant/ine/INE11/formsemestre/1/bulletin";"ScoView";"GET";
"test-pdf";"/etudiant/nip/11/formsemestre/1/bulletin";"ScoView";"GET";
"user";"/user/1";"ScoView";"GET";
"users-query";"/users/query?starts_with=u_";"ScoView";"GET";

Can't render this file because it contains an unexpected character in line 41 and column 2.

View File

@ -6,10 +6,9 @@ import app
from app import db
from app.comp import res_sem
from app.comp.res_but import ResultatsSemestreBUT
from app.models import FormSemestre, ModuleImpl
from app.models import Evaluation, FormSemestre, ModuleImpl
from app.scodoc import (
sco_bulletins,
sco_evaluation_db,
sco_formsemestre,
sco_saisie_notes,
)
@ -131,7 +130,9 @@ def test_notes_rattrapage(test_client):
# Note moyenne: reviens à 10/20
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(10.0)
# Supprime l'évaluation de rattrapage:
sco_evaluation_db.do_evaluation_delete(e_rat["id"])
evaluation = db.session.get(Evaluation, e_rat["id"])
assert evaluation
evaluation.delete()
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)