code cleaning

This commit is contained in:
Emmanuel Viennet 2022-09-30 09:37:20 +02:00 committed by iziram
parent 4925a57f8a
commit c91aabee8d
3 changed files with 1838 additions and 1838 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,352 +1,353 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2022 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
#
##############################################################################
"""Gestion des images logos (nouveau ScoDoc 9.1)
Les logos sont `logo_header.<ext>` et `logo_footer.<ext>`
avec `ext` membre de LOGOS_IMAGES_ALLOWED_TYPES (= jpg, png)
SCODOC_LOGOS_DIR /opt/scodoc-data/config/logos
"""
import glob
import imghdr
import os
import re
import shutil
from pathlib import Path
from flask import abort, current_app, url_for
from werkzeug.utils import secure_filename
from app import Departement, ScoValueError
from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import ScoValueError
from PIL import Image as PILImage
GLOBAL = "_" # category for server level logos
def find_logo(logoname, dept_id=None, strict=False, prefix=scu.LOGO_FILE_PREFIX):
"""
"Recherche un logo 'name' existant.
Deux strategies:
si strict:
reherche uniquement dans le département puis si non trouvé au niveau global
sinon
On recherche en local au dept d'abord puis si pas trouvé recherche globale
quelque soit la stratégie, retourne None si pas trouvé
:param logoname: le nom recherche
:param dept_id: l'id du département dans lequel se fait la recherche (None si global)
:param strict: stratégie de recherche (strict = False => dept ou global)
:param prefix: le prefix utilisé (parmi scu.LOGO_FILE_PREFIX / scu.BACKGROUND_FILE_PREFIX)
:return: un objet Logo désignant le fichier image trouvé (ou None)
"""
logo = Logo(logoname, dept_id, prefix).select()
if logo is None and not strict:
logo = Logo(logoname=logoname, dept_id=None, prefix=prefix).select()
return logo
def delete_logo(name, dept_id=None):
"""Delete all files matching logo (dept_id, name) (including all allowed extensions)
Args:
name: The name of the logo
dept_id: the dept_id (if local). Use None to destroy globals logos
"""
logo = find_logo(logoname=name, dept_id=dept_id)
while logo is not None:
os.unlink(logo.select().filepath)
logo = find_logo(logoname=name, dept_id=dept_id)
def write_logo(stream, name, dept_id=None):
"""Crée le fichier logo sur le serveur.
Le suffixe du fichier (parmi LOGO_IMAGES_ALLOWED_TYPES) est déduit du contenu du stream"""
Logo(logoname=name, dept_id=dept_id).create(stream)
def rename_logo(old_name, new_name, dept_id):
logo = find_logo(old_name, dept_id, True)
logo.rename(new_name)
def list_logos():
"""Crée l'inventaire de tous les logos existants.
L'inventaire se présente comme un dictionnaire de dictionnaire de Logo:
[None][name] pour les logos globaux
[dept_id][name] pour les logos propres à un département (attention id numérique du dept)
Les départements sans logos sont absents du résultat
"""
inventory = {None: _list_dept_logos()} # logos globaux (header / footer)
for dept in Departement.query.filter_by(visible=True).all():
logos_dept = _list_dept_logos(dept_id=dept.id)
if logos_dept:
inventory[dept.id] = _list_dept_logos(dept.id)
return inventory
def _list_dept_logos(dept_id=None, prefix=scu.LOGO_FILE_PREFIX):
"""Inventorie toutes les images existantes pour un niveau (GLOBAL ou un département).
retourne un dictionnaire de Logo [logoname] -> Logo
les noms des fichiers concernés doivent être de la forme: <rep>/<prefix><name>.<suffixe>
<rep> : répertoire de recherche (déduit du dept_id)
<prefix>: le prefix (LOGO_FILE_PREFIX pour les logos)
<suffix>: un des suffixes autorisés
:param dept_id: l'id du departement concerné (si None -> global)
:param prefix: le préfixe utilisé
:return: le résultat de la recherche ou None si aucune image trouvée
"""
allowed_ext = "|".join(scu.LOGOS_IMAGES_ALLOWED_TYPES)
# parse filename 'logo_<logoname>.<ext> . be carefull: logoname may include '.'
filename_parser = re.compile(f"{prefix}(([^.]*.)+)({allowed_ext})")
logos = {}
path_dir = Path(scu.SCODOC_LOGOS_DIR)
if dept_id:
path_dir = Path(
os.path.sep.join(
[scu.SCODOC_LOGOS_DIR, scu.LOGOS_DIR_PREFIX + str(dept_id)]
)
)
if path_dir.exists():
for entry in path_dir.iterdir():
if os.access(path_dir.joinpath(entry).absolute(), os.R_OK):
result = filename_parser.match(entry.name)
if result:
logoname = result.group(1)[
:-1
] # retreive logoname from filename (less final dot)
logos[logoname] = Logo(logoname=logoname, dept_id=dept_id).select()
return logos if len(logos.keys()) > 0 else None
class Logo:
"""Responsable des opérations (select, create), du calcul des chemins et url
ainsi que de la récupération des informations sur un logo.
Usage:
logo existant: Logo(<name>, <dept_id>, ...).select() (retourne None si fichier non trouvé)
logo en création: Logo(<name>, <dept_id>, ...).create(stream)
Les attributs filename, filepath, get_url() ne devraient pas être utilisés avant les opérations
select ou save (le format n'est pas encore connu à ce moement là)
"""
def __init__(self, logoname, dept_id=None, prefix=scu.LOGO_FILE_PREFIX):
"""Initialisation des noms et département des logos.
if prefix = None on recherche simplement une image 'logoname.*'
Le format est renseigné au moment de la lecture (select) ou de la création (create) de l'objet
"""
self.logoname = secure_filename(logoname)
if not self.logoname:
self.logoname = "*** *** nom de logo invalide *** à changer ! *** ***"
self.scodoc_dept_id = dept_id
self.prefix = prefix or ""
if self.scodoc_dept_id:
self.dirpath = os.path.sep.join(
[
scu.SCODOC_LOGOS_DIR,
scu.LOGOS_DIR_PREFIX + secure_filename(str(dept_id)),
]
)
else:
self.dirpath = scu.SCODOC_LOGOS_DIR
self.basepath = os.path.sep.join(
[self.dirpath, self.prefix + secure_filename(self.logoname)]
)
# next attributes are computed by the select function
self.suffix = (
"Not initialized: call the select or create function before access"
)
self.filepath = (
"Not initialized: call the select or create function before access"
)
self.filename = (
"Not initialized: call the select or create function before access"
)
self.size = "Not initialized: call the select or create function before access"
self.aspect_ratio = (
"Not initialized: call the select or create function before access"
)
self.density = (
"Not initialized: call the select or create function before access"
)
self.mm = "Not initialized: call the select or create function before access"
def __repr__(self) -> str:
return f"Logo(logoname='{self.logoname}', filename='{self.filename}')"
def _set_format(self, fmt):
self.suffix = fmt
self.filepath = self.basepath + "." + fmt
self.filename = self.logoname + "." + fmt
def _ensure_directory_exists(self):
"create enclosing directory if necessary"
if not Path(self.dirpath).exists():
current_app.logger.info("sco_logos creating directory %s", self.dirpath)
os.mkdir(self.dirpath)
def create(self, stream):
img_type = guess_image_type(stream)
if img_type not in scu.LOGOS_IMAGES_ALLOWED_TYPES:
raise ScoValueError("type d'image invalide")
self._set_format(img_type)
self._ensure_directory_exists()
filename = self.basepath + "." + self.suffix
with open(filename, "wb") as f:
f.write(stream.read())
current_app.logger.info("sco_logos.store_image %s", self.filename)
# erase other formats if they exists
for suffix in set(scu.LOGOS_IMAGES_ALLOWED_TYPES) - set([img_type]):
try:
os.unlink(self.basepath + "." + suffix)
except IOError:
pass
def _read_info(self, img):
"""computes some properties from the real image
aspect_ratio assumes that x_density and y_density are equals
"""
x_size, y_size = img.size
self.density = img.info.get("dpi", None)
unit = 1
if self.density is None: # no dpi found try jfif infos
self.density = img.info.get("jfif_density", None)
unit = img.info.get("jfif_unit", 0) # 0 = no unit ; 1 = inch ; 2 = mm
if self.density is not None:
x_density, y_density = self.density
if unit != 0 and x_density != 0 and y_density != 0:
unit2mm = [0, 1 / 0.254, 0.1][unit]
x_mm = round(x_size * unit2mm / x_density, 2)
y_mm = round(y_size * unit2mm / y_density, 2)
self.mm = (x_mm, y_mm)
else:
self.mm = None
else:
self.mm = None
self.size = (x_size, y_size)
self.aspect_ratio = round(float(x_size) / y_size, 2)
def select(self):
"""
Récupération des données pour un logo existant
il doit exister un et un seul fichier image parmi de suffixe/types autorisés
(sinon on prend le premier trouvé)
cette opération permet d'affiner le format d'un logo de format inconnu
"""
for suffix in scu.LOGOS_IMAGES_ALLOWED_TYPES:
path = Path(self.basepath + "." + suffix)
if path.exists():
self._set_format(suffix)
with open(self.filepath, "rb") as f:
img = PILImage.open(f)
self._read_info(img)
return self
return None
def get_url(self):
"""Retourne l'URL permettant d'obtenir l'image du logo"""
return url_for(
"scodoc.get_logo",
dept_id=self.scodoc_dept_id,
name=self.logoname,
global_if_not_found=False,
)
def get_url_small(self):
"""Retourne l'URL permettant d'obtenir l'image du logo sous forme de miniature"""
return url_for(
"scodoc.get_logo_small",
dept_id=self.scodoc_dept_id,
name=self.logoname,
global_if_not_found=False,
)
def get_usage(self):
if self.mm is None:
return f'<logo name="{self.logoname}" width="?? mm" height="?? mm">'
else:
return f'<logo name="{self.logoname}" width="{self.mm[0]}mm">'
def last_modified(self):
path = Path(self.filepath)
dt = path.stat().st_mtime
return path.stat().st_mtime
def rename(self, new_name):
"""Change le nom (pas le département)
Les éléments non utiles ne sont pas recalculés (car rechargés lors des accès ultérieurs)
"""
old_path = Path(self.filepath)
self.logoname = secure_filename(new_name)
if not self.logoname:
self.logoname = "*** *** nom de logo invalide *** à changer ! *** ***"
else:
new_path = os.path.sep.join(
[self.dirpath, self.prefix + self.logoname + "." + self.suffix]
)
old_path.rename(new_path)
def guess_image_type(stream) -> str:
"guess image type from header in stream"
header = stream.read(512)
stream.seek(0)
fmt = imghdr.what(None, header)
if not fmt:
return None
return fmt if fmt != "jpeg" else "jpg"
def make_logo_local(logoname, dept_name):
depts = Departement.query.filter_by(acronym=dept_name).all()
if len(depts) == 0:
print(f"no dept {dept_name} found. aborting")
return
if len(depts) > 1:
print(f"several depts {dept_name} found. aborting")
return
dept = depts[0]
print(f"Move logo {logoname}' from global to {dept.acronym}")
old_path_wild = f"/opt/scodoc-data/config/logos/logo_{logoname}.*"
new_dir = f"/opt/scodoc-data/config/logos/logos_{dept.id}"
logos = glob.glob(old_path_wild)
# checks that there is non local already present
for logo in logos:
filename = os.path.split(logo)[1]
new_name = os.path.sep.join([new_dir, filename])
if os.path.exists(new_name):
print("local version of global logo already exists. aborting")
return
# create new__dir if necessary
if not os.path.exists(new_dir):
print(f"- create {new_dir} directory")
os.mkdir(new_dir)
# move global logo (all suffixes) to local dir note: pre existent file (logo_XXX.*) in local dir does not
# prevent operation if there is no conflict with moved files
# At this point everything is ok so we can do files manipulation
for logo in logos:
shutil.move(logo, new_dir)
# print(f"moved {n_moves}/{n} etuds")
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2022 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
#
##############################################################################
"""Gestion des images logos (nouveau ScoDoc 9.1)
Les logos sont `logo_header.<ext>` et `logo_footer.<ext>`
avec `ext` membre de LOGOS_IMAGES_ALLOWED_TYPES (= jpg, png)
SCODOC_LOGOS_DIR /opt/scodoc-data/config/logos
"""
import glob
import imghdr
import os
import re
import shutil
from pathlib import Path
from flask import current_app, url_for
from PIL import Image as PILImage
from werkzeug.utils import secure_filename
from app import log
from app.models import Departement
from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import ScoValueError
GLOBAL = "_" # category for server level logos
def find_logo(logoname, dept_id=None, strict=False, prefix=scu.LOGO_FILE_PREFIX):
"""
"Recherche un logo 'name' existant.
Deux strategies:
si strict:
reherche uniquement dans le département puis si non trouvé au niveau global
sinon
On recherche en local au dept d'abord puis si pas trouvé recherche globale
quelque soit la stratégie, retourne None si pas trouvé
:param logoname: le nom recherche
:param dept_id: l'id du département dans lequel se fait la recherche (None si global)
:param strict: stratégie de recherche (strict = False => dept ou global)
:param prefix: le prefix utilisé (parmi scu.LOGO_FILE_PREFIX / scu.BACKGROUND_FILE_PREFIX)
:return: un objet Logo désignant le fichier image trouvé (ou None)
"""
logo = Logo(logoname, dept_id, prefix).select()
if logo is None and not strict:
logo = Logo(logoname=logoname, dept_id=None, prefix=prefix).select()
return logo
def delete_logo(name, dept_id=None):
"""Delete all files matching logo (dept_id, name) (including all allowed extensions)
Args:
name: The name of the logo
dept_id: the dept_id (if local). Use None to destroy globals logos
"""
logo = find_logo(logoname=name, dept_id=dept_id)
while logo is not None:
os.unlink(logo.select().filepath)
logo = find_logo(logoname=name, dept_id=dept_id)
def write_logo(stream, name, dept_id=None):
"""Crée le fichier logo sur le serveur.
Le suffixe du fichier (parmi LOGO_IMAGES_ALLOWED_TYPES) est déduit du contenu du stream"""
Logo(logoname=name, dept_id=dept_id).create(stream)
def rename_logo(old_name, new_name, dept_id):
logo = find_logo(old_name, dept_id, True)
logo.rename(new_name)
def list_logos():
"""Crée l'inventaire de tous les logos existants.
L'inventaire se présente comme un dictionnaire de dictionnaire de Logo:
[None][name] pour les logos globaux
[dept_id][name] pour les logos propres à un département (attention id numérique du dept)
Les départements sans logos sont absents du résultat
"""
inventory = {None: _list_dept_logos()} # logos globaux (header / footer)
for dept in Departement.query.filter_by(visible=True).all():
logos_dept = _list_dept_logos(dept_id=dept.id)
if logos_dept:
inventory[dept.id] = _list_dept_logos(dept.id)
return inventory
def _list_dept_logos(dept_id=None, prefix=scu.LOGO_FILE_PREFIX):
"""Inventorie toutes les images existantes pour un niveau (GLOBAL ou un département).
retourne un dictionnaire de Logo [logoname] -> Logo
les noms des fichiers concernés doivent être de la forme: <rep>/<prefix><name>.<suffixe>
<rep> : répertoire de recherche (déduit du dept_id)
<prefix>: le prefix (LOGO_FILE_PREFIX pour les logos)
<suffix>: un des suffixes autorisés
:param dept_id: l'id du departement concerné (si None -> global)
:param prefix: le préfixe utilisé
:return: le résultat de la recherche ou None si aucune image trouvée
"""
allowed_ext = "|".join(scu.LOGOS_IMAGES_ALLOWED_TYPES)
# parse filename 'logo_<logoname>.<ext> . be carefull: logoname may include '.'
filename_parser = re.compile(f"{prefix}(([^.]*.)+)({allowed_ext})")
logos = {}
path_dir = Path(scu.SCODOC_LOGOS_DIR)
if dept_id:
path_dir = Path(
os.path.sep.join(
[scu.SCODOC_LOGOS_DIR, scu.LOGOS_DIR_PREFIX + str(dept_id)]
)
)
if path_dir.exists():
for entry in path_dir.iterdir():
if os.access(path_dir.joinpath(entry).absolute(), os.R_OK):
result = filename_parser.match(entry.name)
if result:
logoname = result.group(1)[
:-1
] # retreive logoname from filename (less final dot)
logos[logoname] = Logo(logoname=logoname, dept_id=dept_id).select()
return logos if len(logos.keys()) > 0 else None
class Logo:
"""Responsable des opérations (select, create), du calcul des chemins et url
ainsi que de la récupération des informations sur un logo.
Usage:
logo existant: Logo(<name>, <dept_id>, ...).select() (retourne None si fichier non trouvé)
logo en création: Logo(<name>, <dept_id>, ...).create(stream)
Les attributs filename, filepath, get_url() ne devraient pas être utilisés avant les opérations
select ou save (le format n'est pas encore connu à ce moement là)
"""
def __init__(self, logoname, dept_id=None, prefix=scu.LOGO_FILE_PREFIX):
"""Initialisation des noms et département des logos.
if prefix = None on recherche simplement une image 'logoname.*'
Le format est renseigné au moment de la lecture (select) ou de la création (create) de l'objet
"""
self.logoname = secure_filename(logoname)
if not self.logoname:
self.logoname = "*** *** nom de logo invalide *** à changer ! *** ***"
self.scodoc_dept_id = dept_id
self.prefix = prefix or ""
if self.scodoc_dept_id:
self.dirpath = os.path.sep.join(
[
scu.SCODOC_LOGOS_DIR,
scu.LOGOS_DIR_PREFIX + secure_filename(str(dept_id)),
]
)
else:
self.dirpath = scu.SCODOC_LOGOS_DIR
self.basepath = os.path.sep.join(
[self.dirpath, self.prefix + secure_filename(self.logoname)]
)
# next attributes are computed by the select function
self.suffix = (
"Not initialized: call the select or create function before access"
)
self.filepath = (
"Not initialized: call the select or create function before access"
)
self.filename = (
"Not initialized: call the select or create function before access"
)
self.size = "Not initialized: call the select or create function before access"
self.aspect_ratio = (
"Not initialized: call the select or create function before access"
)
self.density = (
"Not initialized: call the select or create function before access"
)
self.mm = "Not initialized: call the select or create function before access"
def __repr__(self) -> str:
return f"Logo(logoname='{self.logoname}', filename='{self.filename}')"
def _set_format(self, fmt):
self.suffix = fmt
self.filepath = self.basepath + "." + fmt
self.filename = self.logoname + "." + fmt
def _ensure_directory_exists(self):
"create enclosing directory if necessary"
if not Path(self.dirpath).exists():
current_app.logger.info("sco_logos creating directory %s", self.dirpath)
os.mkdir(self.dirpath)
def create(self, stream):
img_type = guess_image_type(stream)
if img_type not in scu.LOGOS_IMAGES_ALLOWED_TYPES:
raise ScoValueError("type d'image invalide")
self._set_format(img_type)
self._ensure_directory_exists()
filename = self.basepath + "." + self.suffix
with open(filename, "wb") as f:
f.write(stream.read())
current_app.logger.info("sco_logos.store_image %s", self.filename)
# erase other formats if they exists
for suffix in set(scu.LOGOS_IMAGES_ALLOWED_TYPES) - set([img_type]):
try:
os.unlink(self.basepath + "." + suffix)
except IOError:
pass
def _read_info(self, img):
"""computes some properties from the real image
aspect_ratio assumes that x_density and y_density are equals
"""
x_size, y_size = img.size
self.density = img.info.get("dpi", None)
unit = 1
if self.density is None: # no dpi found try jfif infos
self.density = img.info.get("jfif_density", None)
unit = img.info.get("jfif_unit", 0) # 0 = no unit ; 1 = inch ; 2 = mm
if self.density is not None:
x_density, y_density = self.density
if unit != 0 and x_density != 0 and y_density != 0:
unit2mm = [0, 1 / 0.254, 0.1][unit]
x_mm = round(x_size * unit2mm / x_density, 2)
y_mm = round(y_size * unit2mm / y_density, 2)
self.mm = (x_mm, y_mm)
else:
self.mm = None
else:
self.mm = None
self.size = (x_size, y_size)
self.aspect_ratio = round(float(x_size) / y_size, 2)
def select(self):
"""
Récupération des données pour un logo existant
il doit exister un et un seul fichier image parmi de suffixe/types autorisés
(sinon on prend le premier trouvé)
cette opération permet d'affiner le format d'un logo de format inconnu
"""
for suffix in scu.LOGOS_IMAGES_ALLOWED_TYPES:
path = Path(self.basepath + "." + suffix)
if path.exists():
self._set_format(suffix)
with open(self.filepath, "rb") as f:
img = PILImage.open(f)
self._read_info(img)
return self
return None
def get_url(self):
"""Retourne l'URL permettant d'obtenir l'image du logo"""
return url_for(
"scodoc.get_logo",
dept_id=self.scodoc_dept_id,
name=self.logoname,
global_if_not_found=False,
)
def get_url_small(self):
"""Retourne l'URL permettant d'obtenir l'image du logo sous forme de miniature"""
return url_for(
"scodoc.get_logo_small",
dept_id=self.scodoc_dept_id,
name=self.logoname,
global_if_not_found=False,
)
def get_usage(self):
if self.mm is None:
return f'<logo name="{self.logoname}" width="?? mm" height="?? mm">'
else:
return f'<logo name="{self.logoname}" width="{self.mm[0]}mm">'
def last_modified(self):
path = Path(self.filepath)
dt = path.stat().st_mtime
return path.stat().st_mtime
def rename(self, new_name):
"""Change le nom (pas le département)
Les éléments non utiles ne sont pas recalculés (car rechargés lors des accès ultérieurs)
"""
old_path = Path(self.filepath)
self.logoname = secure_filename(new_name)
if not self.logoname:
self.logoname = "*** *** nom de logo invalide *** à changer ! *** ***"
else:
new_path = os.path.sep.join(
[self.dirpath, self.prefix + self.logoname + "." + self.suffix]
)
old_path.rename(new_path)
def guess_image_type(stream) -> str:
"guess image type from header in stream"
header = stream.read(512)
stream.seek(0)
fmt = imghdr.what(None, header)
if not fmt:
return None
return fmt if fmt != "jpeg" else "jpg"
def make_logo_local(logoname, dept_name):
depts = Departement.query.filter_by(acronym=dept_name).all()
if len(depts) == 0:
log(f"no dept {dept_name} found. aborting")
return
if len(depts) > 1:
log(f"several depts {dept_name} found. aborting")
return
dept = depts[0]
log(f"Move logo {logoname}' from global to {dept.acronym}")
old_path_wild = f"/opt/scodoc-data/config/logos/logo_{logoname}.*"
new_dir = f"/opt/scodoc-data/config/logos/logos_{dept.id}"
logos = glob.glob(old_path_wild)
# checks that there is non local already present
for logo in logos:
filename = os.path.split(logo)[1]
new_name = os.path.sep.join([new_dir, filename])
if os.path.exists(new_name):
log("local version of global logo already exists. aborting")
return
# create new__dir if necessary
if not os.path.exists(new_dir):
log(f"- create {new_dir} directory")
os.mkdir(new_dir)
# move global logo (all suffixes) to local dir note: pre existent file (logo_XXX.*) in local dir does not
# prevent operation if there is no conflict with moved files
# At this point everything is ok so we can do files manipulation
for logo in logos:
shutil.move(logo, new_dir)
# log(f"moved {n_moves}/{n} etuds")

View File

@ -1,379 +1,379 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "9.3.47"
SCONAME = "ScoDoc"
SCONEWS = """
<h4>Année 2022</h4>
<ul>
<li>ScoDoc 9.3</li>
<ul>
<li>Nouvelle API REST pour connecter ScoDoc à d'autres applications<li>
<li>Module de gestion des relations avec les entreprises</li>
<li>Prise en charge des parcours BUT</li>
<li>Association des UEs aux compétences du référentiel</li>
<li>Jury BUT1</li>
</ul>
<h4>Année 2021</h4>
<ul>
<li>ScoDoc 9.2:
<ul>
<li>Tableau récap. complet pour BUT et autres formations.</li>
<li>Tableau état évaluations</li>
<li>Export des trombinoscope en document docx</li>
<li>Très nombreux correctifs</li>
</ul>
<li>ScoDoc 9.1.75: bulletins BUT pdf</li>
<li>ScoDoc 9.1.50: nombreuses amélioration gestion BUT</li>
<li>ScoDoc 9.1: gestion des formations par compétences, type BUT.</li>
<li>ScoDoc 9.0: nouvelle architecture logicielle (Flask/Python3/Debian 11)</li>
<li>Version mobile (en test)</li>
<li>Évaluations de type "deuxième session"</li>
<li>Gestion du genre neutre (pas d'affichage de la civilité)</li>
<li>Diverses corrections (PV de jurys, ...)</li>
<li>Modernisation du code Python</li>
</ul>
<h4>Année 2020</h4>
<ul>
<li>Corrections d'erreurs, améliorations saisie absences et affichage bulletins</li>
<li>Nouveau site <a href="https://scodoc.org" target="_blank" rel="noopener noreferrer">scodoc.org</a> pour la documentation</li>
<li>Enregistrement de semestres extérieurs</li>
<li>Améliorations PV de Jury</li>
<li>Contributions J.-M. Place: aide au diagnostic problèmes export Apogée
</li>
</ul>
<h4>Année 2019</h4>
<ul>
<li>Support Linux Debian 10</li>
<li>Petites améliorations: import groupes, droits de suppression notes pour vacataires, etc.</li>
<li>Exports listes pour Moodle</li>
<li>Fonction facilitant l'envoi de données pour l'assistance technique</li>
</ul>
<h4>Année 2018</h4>
<ul>
<li>Affichage date finalisation inscription Apogée</li>
<li>Co-responsables de semestres</li>
<li>Amélioration page d'accueil département</li>
<li>Corrections diverses et petites améliorations</li>
<li>Avis de poursuites d'études plus robustes et configurables</li>
</ul>
<h4>Année 2017</h4>
<ul>
<li>Bonus/Malus sur les moyennes d'UE</li>
<li>Enregistrement des informations sur le devenir de l'étudiant</li>
<li>Export global des résultats d'un département (utile pour les rapports d'évaluation)</li>
<li>Compatibilité Linux Debian 9, et modernisation de certains composants</li>
<li>Génération des avis de poursuite d'études</li>
<li>Toilettage page liste programme pédagogiques</li>
<li>Amélioration exports résultats vers Apogée</li>
<li>Amélioration calcul des ECTS</li>
<li>Possibilité d'utilisation des adresses mail personnelles des étudiant</li>
<li>Corrections diverses</li>
</ul>
<h4>Année 2016</h4>
<ul>
<li>Import des données d'admissions depuis fichiers APB ou autres</li>
<li>Nouveau formulaire saisie des notes</li>
<li>Export des résultats vers Apogée pour un ensemble de semestre</li>
<li>Enregistrement du classement lors de l'admission</li>
<li>Modification du calcul des coefficients des UE capitalisées</li>
</ul>
<h4>Année 2015</h4>
<ul>
<li>Exports fichiers Apogée</li>
<li>Recherche étudiants sur plusieurs départements</li>
<li>Corrections diverses</li>
</ul>
<h4>Année 2014</h4>
<ul>
<li>Nouvelle interface pour listes groupes, photos, feuilles d'émargement.</li>
</ul>
<h4>Année 2013</h4>
<ul>
<li>Modernisation de nombreux composants logiciels (ScoDoc 7)</li>
<li>Saisie des absences par matières</li>
</ul>
<h4>Année 2012</h4>
<ul>
<li>Table lycées d'origine avec carte google</li>
<li>Amélioration des PV de jury (logos, ...)</li>
<li>Accélération du code de calcul des semestres</li>
<li>Changement documentation en ligne (nouveau site web)</li>
</ul>
<h4>Année 2011</h4>
<ul>
<li>Amélioration de la présentation des bulletins de notes, et possibilité de définir de nouveaux formats</li>
<li>Possibilité de modifier les moyennes d'UE via un "bonus" (sport/culture)</li>
<li>Ajout parcours spécifique pour UCAC (Cameroun)</li>
<li>Possibilité d'indiquer des mentions sur les PV</li>
<li>Evaluations de "rattrapage"</li>
<li>Support pour installation en Linux Debian "Squeeze"</li>
<li>Corrections diverses</li>
</ul>
<h4>Novembre 2010</h4>
<ul>
<li>Possibilité d'indiquer des évaluations avec publication immédiate des notes (même si incomplètes)</li>
</ul>
<h4>Octobre 2010</h4>
<ul>
<li>Nouvelle API JSON</li>
<li>Possibilité d'associer 2 étapes Apogée au même semestre</li>
<li>Table "poursuite études"</li>
<li>Possibilité d'envoyer un mail auto aux étudiants absentéistes<li>
</ul>
<h4>Août 2010</h4>
<ul>
<li>Définitions de parcours (DUT, LP, ...) avec prise en compte des spécificités (par ex., certaines barres d'UE différentes en LP)</li>
</ul>
<h4>Avril - Juin 2010</h4>
<ul>
<li>Formules utilisateur pour le calcul des moyennes d'UE</li>
<li>Nouveau système de notification des absences par mail</li>
<li>Affichage optionnel des valeurs mini et maxi des moyennes sur les bulletins</li>
<li>Nouveau code de décision jury semestre: "RAT" : en attente de rattrapage</li>
</ul>
<h4>Janvier 2010</h4>
<ul>
<li>Nouveau menu "Groupes" pour faciliter la prise en main</li>
<li>Possibilité de définir des règles ad hoc de calcul des moyennes de modules (formules)</li>
<li>Possibilité d'inclure des images (logos) dans les bulletins PDF</li>
<li>Bandeau "provisoire" sur les bulletins en cours de semestre</li>
<li>Possibilite de valider (capitaliser) une UE passee hors ScoDoc</li>
<li>Amelioration de l'édition des programmes (formations)</li>
<li>Nombreuses améliorations mineures</li>
</ul>
<h4>Novembre 2009</h4>
<ul>
<li>Gestion des partitions et groupes en nombres quelconques</li>
<li>Nouvelle gestion des photos</li>
<lI>Imports d'étudiants excel incrémentaux</li>
<li>Optimisations et petites améliorations</li>
</ul>
<h4>Septembre 2009</h4>
<ul>
<li>Traitement de "billets d'absences" (saisis par les étudiants sur le portail)</li>
</ul>
<h4>Juin 2009</h4>
<ul>
<li>Nouveau système plus flexibles de gestion des préférences (ou "paramètres")</li>
<li>Possiblité d'associer une nouvelle version de programme à un semestre</li>
<li>Corrections et améliorations diverses</h4>
</ul>
<h4>Juillet 2008: version 6.0</h4>
<ul>
<li>Installeur automatisé pour Linux</li>
<li>Amélioration ergonomie (barre menu pages semestres)</li>
<li>Refonte fiche étudiant (parcours)</li>
<li>Archivage des documents (PV)</li>
<li>Nouvel affichage des notes des évaluations</li>
<li>Nombreuses corrections et améliorations</li>
</ul>
<h4>Juin 2008</h4>
<ul>
<li>Rangs sur les bulletins</li>
</ul>
<h4>Février 2008</h4>
<ul>
<li>Statistiques et suivis de cohortes (chiffres et graphes)</li>
<li>Nombreuses petites corrections suites aux jurys de janvier</li>
</ul>
<h4>Janvier 2008</h4>
<ul>
<li>Personnalisation des régles de calculs notes d'option (sport, culture)</li>
<li>Edition de PV de jury individuel</li>
</ul>
<h4>Novembre 2007</h4>
<ul>
<li>Vérification des absences aux évaluations</li>
<li>Import des photos depuis portail, trombinoscopes en PDF</li>
</ul>
<h4>Septembre 2007</h4>
<ul>
<li>Importation des etudiants depuis étapes Apogée</li>
<li>Inscription de groupes à des modules (options ou parcours)</li>
<li>Listes de étapes Apogée (importées du portail)</li>
</ul>
<h4>Juillet 2007</h4>
<ul>
<li>Import utilisateurs depuis Excel</li>
<li>Nouvelle gestion des passage d'un semestre à l'autre</li>
</ul>
<h4>Juin 2007: version 5.0</h4>
<ul>
<li>Suivi des parcours et règles de décision des jurys DUT</li>
<li>Capitalisation des UEs</li>
<li>Edition des PV de jurys et courriers aux étudiants</li>
<li>Feuilles (excel) pour préparation jurys</li>
<li>Nombreuses petites améliorations</li>
</ul>
<h4>Avril 2007</h4>
<ul>
<li>Paramètres de mise en page des bulletins en PDF</li>
</ul>
<h4>Février 2007</h4>
<ul>
<li>Possibilité de ne <em>pas</em> publier les bulletins sur le portail</li>
<li>Gestion des notes "en attente" (publication d'évaluations sans correction de toutes les copies)</li>
<li>Amélioration formulaire saisie absences, saisie absences par semestre.</li>
</ul>
<h4>Janvier 2007</h4>
<ul>
<li>Possibilité d'initialiser les notes manquantes d'une évaluation</li>
<li>Recupération des codes NIP depuis Apogée</li>
<li>Gestion des compensations inter-semestre DUT (en cours de développement)</li>
<li>Export trombinoscope en archive zip</li>
</ul>
<h4>Octobre 2006</h4>
<ul>
<li>Réorganisation des pages d'accueil</li>
<li>Ajout des "nouvelles" (dernières opérations), avec flux RSS</li>
<li>Import/Export XML des formations, duplication d'une formation (versions)</li>
<li>Bulletins toujours sur une seule feuille (passage à ReportLab 2.0)</li>
<li>Suppression d'un utilisateur</il>
</ul>
<h4>Septembre 2006</h4>
<ul>
<li>Page pour suppression des groupes.</li>
<li>Amélioration gestion des utilisateurs</li>
<li>"Verrouillage" des semestres</li>
<li>Liste d'enseignants (chargés de TD) associés à un module (et pouvant saisir des notes)</li>
<li>Noms de types de groupes (TD, TP, ...) modifiables</li>
<li>Tableau rudimentaire donnant la répartition des bacs dans un semestre</li>
<li>Amélioration mise en page des listes au format excel</li>
<li>Annulation des démissions</li>
</ul>
<h4>Juillet 2006</h4>
<ul>
<li>Dialogue permettant au directeur des études de modifier
les options d'un semestre</li>
<li>Option pour ne pas afficher les UE validées sur les bulletins</li>
</ul>
<h4>30 juin 2006</h4>
<ul>
<li>Option pour ne pas afficher les décisions sur les bulletins</li>
<li>Génération feuilles pour préparation jury</li>
<li>Gestion des modules optionnels</li>
<li>Prise en compte note "activités culturelles ou sportives"</li>
<li>Amélioration tableau de bord semestre</li>
<li>Import listes étudiants depuis Excel (avec code Apogée)</li>
</ul>
<h4>12 juin 2006</h4>
<ul>
<li>Formulaire dynamique d'affectation aux groupes</li>
<li>Tri des tableaux (listes, récapitulatif)</li>
<li>Export XML des infos sur un etudiant et des groupes</li>
</ul>
<h4>12 mai 2006</h4>
<ul>
<li>Possibilité de suppression d'un semestre</li>
<li>Export XML du tableau recapitulatif des notes du semestre</li>
<li>Possibilité de supression d'une formation complète</li>
</ul>
<h4>24 avril 2006</h4>
<ul>
<li>Export bulletins en XML (expérimental)</li>
<li>Flag "gestion_absence" sur les semestres de formation</li>
</ul>
<h4>4 mars 2006</h4>
<ul>
<li>Formulaire d'inscription au semestre suivant.</li>
<li>Format "nombre" dans les feuilles excel exportées.</li>
</ul>
<h4>23 février 2006</h4>
<ul>
<li>Décisions jury sur bulletins.</li>
</ul>
<h4>17 janvier 2006</h4>
<ul>
<li>Ajout et édition d'appréciations sur les bulletins.</li>
</ul>
<h4>12 janvier 2006</h4>
<ul>
<li>Envoi des bulletins en PDF par mail aux étudiants.</li>
</ul>
<h4>6 janvier 2006</h4>
<ul>
<li>Affichage des ex-aequos.</li>
<li>Classeurs bulletins PDF en différentes versions.</li>
<li>Corrigé gestion des notes des démissionnaires.</li>
</ul>
<h4>1er janvier 2006</h4>
<ul>
<li>Import du projet dans Subversion / LIPN.</li>
<li>Lecture des feuilles de notes Excel.</li>
</ul>
<h4>31 décembre 2005</h4>
<ul>
<li>Listes générées au format Excel au lieu de CSV.</li>
<li>Bug fix (création/saisie evals).</li>
</ul>
<h4>29 décembre 2005</h4>
<ul>
<li>Affichage des moyennes de chaque groupe dans tableau de bord module.
</ul>
<h4>26 décembre 2005</h4>
<ul>
<li>Révision inscription/édition <em>individuelle</em> d'étudiants.</li>
<li>Amélioration fiche étudiant (cosmétique, liste formations, actions).</li>
<li>Listings notes d'évaluations anonymes (utilité douteuse ?).</li>
<li>Amélioration formulaire saisie notes ('enter' -> champ suivant).</li>
</ul>
<h4>24 décembre 2005</h4>
<ul>
<li>Génération de bulletins PDF
</li>
<li>Suppression de notes (permet donc de supprimer une évaluation)
</li>
<li>Bulletins en versions courtes (seulement moyennes de chaque module), longues
(toutes les notes) et intermédiaire (moyenne de chaque module plus notes dans les évaluations sélectionnées).
</li>
<li>Notes moyennes sous les barres en rouge dans le tableau récapitulatif (seuil=10 sur la moyenne générale, et 8 sur chaque UE).
</li>
<li>Colonne "groupe de TD" dans le tableau récapitulatif des notes.
</ul>
"""
# -*- mode: python -*-
# -*- coding: utf-8 -*-
SCOVERSION = "9.3.48"
SCONAME = "ScoDoc"
SCONEWS = """
<h4>Année 2022</h4>
<ul>
<li>ScoDoc 9.3</li>
<ul>
<li>Nouvelle API REST pour connecter ScoDoc à d'autres applications<li>
<li>Module de gestion des relations avec les entreprises</li>
<li>Prise en charge des parcours BUT</li>
<li>Association des UEs aux compétences du référentiel</li>
<li>Jury BUT1</li>
</ul>
<h4>Année 2021</h4>
<ul>
<li>ScoDoc 9.2:
<ul>
<li>Tableau récap. complet pour BUT et autres formations.</li>
<li>Tableau état évaluations</li>
<li>Export des trombinoscope en document docx</li>
<li>Très nombreux correctifs</li>
</ul>
<li>ScoDoc 9.1.75: bulletins BUT pdf</li>
<li>ScoDoc 9.1.50: nombreuses amélioration gestion BUT</li>
<li>ScoDoc 9.1: gestion des formations par compétences, type BUT.</li>
<li>ScoDoc 9.0: nouvelle architecture logicielle (Flask/Python3/Debian 11)</li>
<li>Version mobile (en test)</li>
<li>Évaluations de type "deuxième session"</li>
<li>Gestion du genre neutre (pas d'affichage de la civilité)</li>
<li>Diverses corrections (PV de jurys, ...)</li>
<li>Modernisation du code Python</li>
</ul>
<h4>Année 2020</h4>
<ul>
<li>Corrections d'erreurs, améliorations saisie absences et affichage bulletins</li>
<li>Nouveau site <a href="https://scodoc.org" target="_blank" rel="noopener noreferrer">scodoc.org</a> pour la documentation</li>
<li>Enregistrement de semestres extérieurs</li>
<li>Améliorations PV de Jury</li>
<li>Contributions J.-M. Place: aide au diagnostic problèmes export Apogée
</li>
</ul>
<h4>Année 2019</h4>
<ul>
<li>Support Linux Debian 10</li>
<li>Petites améliorations: import groupes, droits de suppression notes pour vacataires, etc.</li>
<li>Exports listes pour Moodle</li>
<li>Fonction facilitant l'envoi de données pour l'assistance technique</li>
</ul>
<h4>Année 2018</h4>
<ul>
<li>Affichage date finalisation inscription Apogée</li>
<li>Co-responsables de semestres</li>
<li>Amélioration page d'accueil département</li>
<li>Corrections diverses et petites améliorations</li>
<li>Avis de poursuites d'études plus robustes et configurables</li>
</ul>
<h4>Année 2017</h4>
<ul>
<li>Bonus/Malus sur les moyennes d'UE</li>
<li>Enregistrement des informations sur le devenir de l'étudiant</li>
<li>Export global des résultats d'un département (utile pour les rapports d'évaluation)</li>
<li>Compatibilité Linux Debian 9, et modernisation de certains composants</li>
<li>Génération des avis de poursuite d'études</li>
<li>Toilettage page liste programme pédagogiques</li>
<li>Amélioration exports résultats vers Apogée</li>
<li>Amélioration calcul des ECTS</li>
<li>Possibilité d'utilisation des adresses mail personnelles des étudiant</li>
<li>Corrections diverses</li>
</ul>
<h4>Année 2016</h4>
<ul>
<li>Import des données d'admissions depuis fichiers APB ou autres</li>
<li>Nouveau formulaire saisie des notes</li>
<li>Export des résultats vers Apogée pour un ensemble de semestre</li>
<li>Enregistrement du classement lors de l'admission</li>
<li>Modification du calcul des coefficients des UE capitalisées</li>
</ul>
<h4>Année 2015</h4>
<ul>
<li>Exports fichiers Apogée</li>
<li>Recherche étudiants sur plusieurs départements</li>
<li>Corrections diverses</li>
</ul>
<h4>Année 2014</h4>
<ul>
<li>Nouvelle interface pour listes groupes, photos, feuilles d'émargement.</li>
</ul>
<h4>Année 2013</h4>
<ul>
<li>Modernisation de nombreux composants logiciels (ScoDoc 7)</li>
<li>Saisie des absences par matières</li>
</ul>
<h4>Année 2012</h4>
<ul>
<li>Table lycées d'origine avec carte google</li>
<li>Amélioration des PV de jury (logos, ...)</li>
<li>Accélération du code de calcul des semestres</li>
<li>Changement documentation en ligne (nouveau site web)</li>
</ul>
<h4>Année 2011</h4>
<ul>
<li>Amélioration de la présentation des bulletins de notes, et possibilité de définir de nouveaux formats</li>
<li>Possibilité de modifier les moyennes d'UE via un "bonus" (sport/culture)</li>
<li>Ajout parcours spécifique pour UCAC (Cameroun)</li>
<li>Possibilité d'indiquer des mentions sur les PV</li>
<li>Evaluations de "rattrapage"</li>
<li>Support pour installation en Linux Debian "Squeeze"</li>
<li>Corrections diverses</li>
</ul>
<h4>Novembre 2010</h4>
<ul>
<li>Possibilité d'indiquer des évaluations avec publication immédiate des notes (même si incomplètes)</li>
</ul>
<h4>Octobre 2010</h4>
<ul>
<li>Nouvelle API JSON</li>
<li>Possibilité d'associer 2 étapes Apogée au même semestre</li>
<li>Table "poursuite études"</li>
<li>Possibilité d'envoyer un mail auto aux étudiants absentéistes<li>
</ul>
<h4>Août 2010</h4>
<ul>
<li>Définitions de parcours (DUT, LP, ...) avec prise en compte des spécificités (par ex., certaines barres d'UE différentes en LP)</li>
</ul>
<h4>Avril - Juin 2010</h4>
<ul>
<li>Formules utilisateur pour le calcul des moyennes d'UE</li>
<li>Nouveau système de notification des absences par mail</li>
<li>Affichage optionnel des valeurs mini et maxi des moyennes sur les bulletins</li>
<li>Nouveau code de décision jury semestre: "RAT" : en attente de rattrapage</li>
</ul>
<h4>Janvier 2010</h4>
<ul>
<li>Nouveau menu "Groupes" pour faciliter la prise en main</li>
<li>Possibilité de définir des règles ad hoc de calcul des moyennes de modules (formules)</li>
<li>Possibilité d'inclure des images (logos) dans les bulletins PDF</li>
<li>Bandeau "provisoire" sur les bulletins en cours de semestre</li>
<li>Possibilite de valider (capitaliser) une UE passee hors ScoDoc</li>
<li>Amelioration de l'édition des programmes (formations)</li>
<li>Nombreuses améliorations mineures</li>
</ul>
<h4>Novembre 2009</h4>
<ul>
<li>Gestion des partitions et groupes en nombres quelconques</li>
<li>Nouvelle gestion des photos</li>
<lI>Imports d'étudiants excel incrémentaux</li>
<li>Optimisations et petites améliorations</li>
</ul>
<h4>Septembre 2009</h4>
<ul>
<li>Traitement de "billets d'absences" (saisis par les étudiants sur le portail)</li>
</ul>
<h4>Juin 2009</h4>
<ul>
<li>Nouveau système plus flexibles de gestion des préférences (ou "paramètres")</li>
<li>Possiblité d'associer une nouvelle version de programme à un semestre</li>
<li>Corrections et améliorations diverses</h4>
</ul>
<h4>Juillet 2008: version 6.0</h4>
<ul>
<li>Installeur automatisé pour Linux</li>
<li>Amélioration ergonomie (barre menu pages semestres)</li>
<li>Refonte fiche étudiant (parcours)</li>
<li>Archivage des documents (PV)</li>
<li>Nouvel affichage des notes des évaluations</li>
<li>Nombreuses corrections et améliorations</li>
</ul>
<h4>Juin 2008</h4>
<ul>
<li>Rangs sur les bulletins</li>
</ul>
<h4>Février 2008</h4>
<ul>
<li>Statistiques et suivis de cohortes (chiffres et graphes)</li>
<li>Nombreuses petites corrections suites aux jurys de janvier</li>
</ul>
<h4>Janvier 2008</h4>
<ul>
<li>Personnalisation des régles de calculs notes d'option (sport, culture)</li>
<li>Edition de PV de jury individuel</li>
</ul>
<h4>Novembre 2007</h4>
<ul>
<li>Vérification des absences aux évaluations</li>
<li>Import des photos depuis portail, trombinoscopes en PDF</li>
</ul>
<h4>Septembre 2007</h4>
<ul>
<li>Importation des etudiants depuis étapes Apogée</li>
<li>Inscription de groupes à des modules (options ou parcours)</li>
<li>Listes de étapes Apogée (importées du portail)</li>
</ul>
<h4>Juillet 2007</h4>
<ul>
<li>Import utilisateurs depuis Excel</li>
<li>Nouvelle gestion des passage d'un semestre à l'autre</li>
</ul>
<h4>Juin 2007: version 5.0</h4>
<ul>
<li>Suivi des parcours et règles de décision des jurys DUT</li>
<li>Capitalisation des UEs</li>
<li>Edition des PV de jurys et courriers aux étudiants</li>
<li>Feuilles (excel) pour préparation jurys</li>
<li>Nombreuses petites améliorations</li>
</ul>
<h4>Avril 2007</h4>
<ul>
<li>Paramètres de mise en page des bulletins en PDF</li>
</ul>
<h4>Février 2007</h4>
<ul>
<li>Possibilité de ne <em>pas</em> publier les bulletins sur le portail</li>
<li>Gestion des notes "en attente" (publication d'évaluations sans correction de toutes les copies)</li>
<li>Amélioration formulaire saisie absences, saisie absences par semestre.</li>
</ul>
<h4>Janvier 2007</h4>
<ul>
<li>Possibilité d'initialiser les notes manquantes d'une évaluation</li>
<li>Recupération des codes NIP depuis Apogée</li>
<li>Gestion des compensations inter-semestre DUT (en cours de développement)</li>
<li>Export trombinoscope en archive zip</li>
</ul>
<h4>Octobre 2006</h4>
<ul>
<li>Réorganisation des pages d'accueil</li>
<li>Ajout des "nouvelles" (dernières opérations), avec flux RSS</li>
<li>Import/Export XML des formations, duplication d'une formation (versions)</li>
<li>Bulletins toujours sur une seule feuille (passage à ReportLab 2.0)</li>
<li>Suppression d'un utilisateur</il>
</ul>
<h4>Septembre 2006</h4>
<ul>
<li>Page pour suppression des groupes.</li>
<li>Amélioration gestion des utilisateurs</li>
<li>"Verrouillage" des semestres</li>
<li>Liste d'enseignants (chargés de TD) associés à un module (et pouvant saisir des notes)</li>
<li>Noms de types de groupes (TD, TP, ...) modifiables</li>
<li>Tableau rudimentaire donnant la répartition des bacs dans un semestre</li>
<li>Amélioration mise en page des listes au format excel</li>
<li>Annulation des démissions</li>
</ul>
<h4>Juillet 2006</h4>
<ul>
<li>Dialogue permettant au directeur des études de modifier
les options d'un semestre</li>
<li>Option pour ne pas afficher les UE validées sur les bulletins</li>
</ul>
<h4>30 juin 2006</h4>
<ul>
<li>Option pour ne pas afficher les décisions sur les bulletins</li>
<li>Génération feuilles pour préparation jury</li>
<li>Gestion des modules optionnels</li>
<li>Prise en compte note "activités culturelles ou sportives"</li>
<li>Amélioration tableau de bord semestre</li>
<li>Import listes étudiants depuis Excel (avec code Apogée)</li>
</ul>
<h4>12 juin 2006</h4>
<ul>
<li>Formulaire dynamique d'affectation aux groupes</li>
<li>Tri des tableaux (listes, récapitulatif)</li>
<li>Export XML des infos sur un etudiant et des groupes</li>
</ul>
<h4>12 mai 2006</h4>
<ul>
<li>Possibilité de suppression d'un semestre</li>
<li>Export XML du tableau recapitulatif des notes du semestre</li>
<li>Possibilité de supression d'une formation complète</li>
</ul>
<h4>24 avril 2006</h4>
<ul>
<li>Export bulletins en XML (expérimental)</li>
<li>Flag "gestion_absence" sur les semestres de formation</li>
</ul>
<h4>4 mars 2006</h4>
<ul>
<li>Formulaire d'inscription au semestre suivant.</li>
<li>Format "nombre" dans les feuilles excel exportées.</li>
</ul>
<h4>23 février 2006</h4>
<ul>
<li>Décisions jury sur bulletins.</li>
</ul>
<h4>17 janvier 2006</h4>
<ul>
<li>Ajout et édition d'appréciations sur les bulletins.</li>
</ul>
<h4>12 janvier 2006</h4>
<ul>
<li>Envoi des bulletins en PDF par mail aux étudiants.</li>
</ul>
<h4>6 janvier 2006</h4>
<ul>
<li>Affichage des ex-aequos.</li>
<li>Classeurs bulletins PDF en différentes versions.</li>
<li>Corrigé gestion des notes des démissionnaires.</li>
</ul>
<h4>1er janvier 2006</h4>
<ul>
<li>Import du projet dans Subversion / LIPN.</li>
<li>Lecture des feuilles de notes Excel.</li>
</ul>
<h4>31 décembre 2005</h4>
<ul>
<li>Listes générées au format Excel au lieu de CSV.</li>
<li>Bug fix (création/saisie evals).</li>
</ul>
<h4>29 décembre 2005</h4>
<ul>
<li>Affichage des moyennes de chaque groupe dans tableau de bord module.
</ul>
<h4>26 décembre 2005</h4>
<ul>
<li>Révision inscription/édition <em>individuelle</em> d'étudiants.</li>
<li>Amélioration fiche étudiant (cosmétique, liste formations, actions).</li>
<li>Listings notes d'évaluations anonymes (utilité douteuse ?).</li>
<li>Amélioration formulaire saisie notes ('enter' -> champ suivant).</li>
</ul>
<h4>24 décembre 2005</h4>
<ul>
<li>Génération de bulletins PDF
</li>
<li>Suppression de notes (permet donc de supprimer une évaluation)
</li>
<li>Bulletins en versions courtes (seulement moyennes de chaque module), longues
(toutes les notes) et intermédiaire (moyenne de chaque module plus notes dans les évaluations sélectionnées).
</li>
<li>Notes moyennes sous les barres en rouge dans le tableau récapitulatif (seuil=10 sur la moyenne générale, et 8 sur chaque UE).
</li>
<li>Colonne "groupe de TD" dans le tableau récapitulatif des notes.
</ul>
"""