placement_group_size_control #2

Closed
jmplace wants to merge 150 commits from placement_group_size_control into master
131 changed files with 4311 additions and 3983 deletions

1
.gitignore vendored
View File

@ -170,3 +170,4 @@ Thumbs.db
*.code-workspace
copy

View File

@ -3,16 +3,12 @@
(c) Emmanuel Viennet 1999 - 2021 (voir LICENCE.txt)
VERSION EXPERIMENTALE - NE PAS DEPLOYER - TESTS EN COURS
Installation: voir instructions à jour sur <https://scodoc.org/GuideInstallDebian11>
Documentation utilisateur: <https://scodoc.org>
## Version ScoDoc 9
N'utiliser que pour les développements et tests.
La version ScoDoc 9 est basée sur Flask (au lieu de Zope) et sur
**python 3.9+**.
@ -22,13 +18,13 @@ Flask, SQLAlchemy, au lien de Python2/Zope dans les versions précédentes).
### État actuel (27 août 21)
### État actuel (26 sept 21)
- Tests en cours, notamment système d'installation et de migration.
- 9.0 reproduit l'ensemble des fonctions de ScoDoc 7 (donc pas de BUT), sauf:
**Fonctionnalités non intégrées:**
- feuille "placement" (en cours)
- génération LaTeX des avis de poursuite d'études
- ancien module "Entreprises" (obsolete)

View File

@ -17,14 +17,14 @@ from flask import render_template
from flask.logging import default_handler
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_login import LoginManager, current_user
from flask_mail import Mail
from flask_bootstrap import Bootstrap
from flask_moment import Moment
from flask_caching import Cache
import sqlalchemy
from app.scodoc.sco_exceptions import ScoValueError, APIInvalidParams
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError, APIInvalidParams
from config import DevConfig
import sco_version
@ -82,7 +82,7 @@ def postgresql_server_error(e):
return render_raw_html("error_503.html", SCOVERSION=sco_version.SCOVERSION), 503
class RequestFormatter(logging.Formatter):
class LogRequestFormatter(logging.Formatter):
"""Ajoute URL et remote_addr for logging"""
def format(self, record):
@ -92,6 +92,34 @@ class RequestFormatter(logging.Formatter):
else:
record.url = None
record.remote_addr = None
record.sco_user = current_user
return super().format(record)
class LogExceptionFormatter(logging.Formatter):
"""Formatteur pour les exceptions: ajoute détails"""
def format(self, record):
if has_request_context():
record.url = request.url
record.remote_addr = request.environ.get(
"HTTP_X_FORWARDED_FOR", request.remote_addr
)
record.http_referrer = request.referrer
record.http_method = request.method
if request.method == "GET":
record.http_params = str(request.args)
else:
# rep = reprlib.Repr() # abbrège
record.http_params = str(request.form)[:2048]
else:
record.url = None
record.remote_addr = None
record.http_referrer = None
record.http_method = None
record.http_params = None
record.sco_user = current_user
return super().format(record)
@ -105,8 +133,24 @@ class ScoSMTPHandler(SMTPHandler):
return subject
class ReverseProxied(object):
"""Adaptateur wsgi qui nous permet d'avoir toutes les URL calculées en https
sauf quand on est en dev.
La variable HTTP_X_FORWARDED_PROTO est positionnée par notre config nginx"""
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
scheme = environ.get("HTTP_X_FORWARDED_PROTO")
if scheme:
environ["wsgi.url_scheme"] = scheme # ou forcer à https ici ?
return self.app(environ, start_response)
def create_app(config_class=DevConfig):
app = Flask(__name__, static_url_path="/ScoDoc/static", static_folder="static")
app.wsgi_app = ReverseProxied(app.wsgi_app)
app.logger.setLevel(logging.DEBUG)
app.config.from_object(config_class)
@ -119,6 +163,7 @@ def create_app(config_class=DevConfig):
cache.init_app(app)
sco_cache.CACHE = cache
app.register_error_handler(ScoGenError, handle_sco_value_error)
app.register_error_handler(ScoValueError, handle_sco_value_error)
app.register_error_handler(500, internal_server_error)
app.register_error_handler(503, postgresql_server_error)
@ -148,9 +193,16 @@ def create_app(config_class=DevConfig):
absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences"
)
app.register_blueprint(api_bp, url_prefix="/ScoDoc/api")
scodoc_exc_formatter = RequestFormatter(
"[%(asctime)s] %(remote_addr)s requested %(url)s\n"
"%(levelname)s in %(module)s: %(message)s"
scodoc_log_formatter = LogRequestFormatter(
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
"%(levelname)s: %(message)s"
)
scodoc_exc_formatter = LogExceptionFormatter(
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
"%(levelname)s: %(message)s\n"
"Referrer: %(http_referrer)s\n"
"Method: %(http_method)s\n"
"Params: %(http_params)s\n"
)
if not app.testing:
if not app.debug:
@ -179,7 +231,7 @@ def create_app(config_class=DevConfig):
app.logger.addHandler(mail_handler)
else:
# Pour logs en DEV uniquement:
default_handler.setFormatter(scodoc_exc_formatter)
default_handler.setFormatter(scodoc_log_formatter)
# Config logs pour DEV et PRODUCTION
# Configuration des logs (actifs aussi en mode development)
@ -188,9 +240,17 @@ def create_app(config_class=DevConfig):
file_handler = WatchedFileHandler(
app.config["SCODOC_LOG_FILE"], encoding="utf-8"
)
file_handler.setFormatter(scodoc_exc_formatter)
file_handler.setFormatter(scodoc_log_formatter)
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
# Log pour les erreurs (exceptions) uniquement:
# usually /opt/scodoc-data/log/scodoc_exc.log
file_handler = WatchedFileHandler(
app.config["SCODOC_ERR_FILE"], encoding="utf-8"
)
file_handler.setFormatter(scodoc_exc_formatter)
file_handler.setLevel(logging.ERROR)
app.logger.addHandler(file_handler)
# app.logger.setLevel(logging.INFO)
app.logger.info(f"{sco_version.SCONAME} {sco_version.SCOVERSION} startup")

View File

@ -20,7 +20,9 @@
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.from flask import jsonify
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from flask import jsonify
from werkzeug.http import HTTP_STATUS_CODES

View File

@ -25,7 +25,7 @@ from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS
import app.scodoc.sco_utils as scu
from app.scodoc import sco_etud # a deplacer dans scu
VALID_LOGIN_EXP = re.compile(r"^[a-zA-Z0-9@\\\-_\\\.]+$")
VALID_LOGIN_EXP = re.compile(r"^[a-zA-Z0-9@\\\-_\.]+$")
class User(UserMixin, db.Model):

View File

@ -20,6 +20,7 @@ import flask_login
import app
from app.auth.models import User
import app.scodoc.sco_utils as scu
class ZUser(object):
@ -39,69 +40,6 @@ class ZUser(object):
raise NotImplementedError()
class ZRequest(object):
"Emulating Zope 2 REQUEST"
def __init__(self):
if current_app.config["DEBUG"]:
self.URL = request.base_url
self.BASE0 = request.url_root
else:
self.URL = request.base_url.replace("http://", "https://")
self.BASE0 = request.url_root.replace("http://", "https://")
self.URL0 = self.URL
# query_string is bytes:
self.QUERY_STRING = request.query_string.decode("utf-8")
self.REQUEST_METHOD = request.method
self.AUTHENTICATED_USER = current_user
self.REMOTE_ADDR = request.remote_addr
if request.method == "POST":
# request.form is a werkzeug.datastructures.ImmutableMultiDict
# must copy to get a mutable version (needed by TrivialFormulator)
self.form = request.form.copy()
if request.files:
# Add files in form:
self.form.update(request.files)
for k in request.form:
if k.endswith(":list"):
self.form[k[:-5]] = request.form.getlist(k)
elif request.method == "GET":
self.form = {}
for k in request.args:
# current_app.logger.debug("%s\t%s" % (k, request.args.getlist(k)))
if k.endswith(":list"):
self.form[k[:-5]] = request.args.getlist(k)
else:
self.form[k] = request.args[k]
# current_app.logger.info("ZRequest.form=%s" % str(self.form))
self.RESPONSE = ZResponse()
def __str__(self):
return """REQUEST
URL={r.URL}
QUERY_STRING={r.QUERY_STRING}
REQUEST_METHOD={r.REQUEST_METHOD}
AUTHENTICATED_USER={r.AUTHENTICATED_USER}
form={r.form}
""".format(
r=self
)
class ZResponse(object):
"Emulating Zope 2 RESPONSE"
def __init__(self):
self.headers = {}
def redirect(self, url):
# current_app.logger.debug("ZResponse redirect to:" + str(url))
return flask.redirect(url) # http 302
def setHeader(self, header, value):
self.headers[header.lower()] = value
def scodoc(func):
"""Décorateur pour toutes les fonctions ScoDoc
Affecte le département à g
@ -132,7 +70,6 @@ def permission_required(permission):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# current_app.logger.info("PERMISSION; kwargs=%s" % str(kwargs))
scodoc_dept = getattr(g, "scodoc_dept", None)
if not current_user.has_permission(permission, scodoc_dept):
abort(403)
@ -193,7 +130,6 @@ def admin_required(f):
def scodoc7func(func):
"""Décorateur pour intégrer les fonctions Zope 2 de ScoDoc 7.
Ajoute l'argument REQUEST s'il est dans la signature de la fonction.
Les paramètres de la query string deviennent des (keywords) paramètres de la fonction.
"""
@ -206,19 +142,20 @@ def scodoc7func(func):
1. via a Flask route ("top level call")
2. or be called directly from Python.
If called via a route, this decorator setups a REQUEST object (emulating Zope2 REQUEST)
"""
# Détermine si on est appelé via une route ("toplevel")
# ou par un appel de fonction python normal.
top_level = not hasattr(g, "zrequest")
top_level = not hasattr(g, "scodoc7_decorated")
if not top_level:
# ne "redécore" pas
return func(*args, **kwargs)
g.scodoc7_decorated = True
# --- Emulate Zope's REQUEST
REQUEST = ZRequest()
g.zrequest = REQUEST
req_args = REQUEST.form # args from query string (get) or form (post)
# --- Add positional arguments
# REQUEST = ZRequest()
# g.zrequest = REQUEST
# args from query string (get) or form (post)
req_args = scu.get_request_args()
## --- Add positional arguments
pos_arg_values = []
argspec = inspect.getfullargspec(func)
# current_app.logger.info("argspec=%s" % str(argspec))
@ -227,10 +164,12 @@ def scodoc7func(func):
arg_names = argspec.args[:-nb_default_args]
else:
arg_names = argspec.args
for arg_name in arg_names:
if arg_name == "REQUEST": # special case
pos_arg_values.append(REQUEST)
for arg_name in arg_names: # pour chaque arg de la fonction vue
if arg_name == "REQUEST": # ne devrait plus arriver !
# debug check, TODO remove after tests
raise ValueError("invalid REQUEST parameter !")
else:
# peut produire une KeyError s'il manque un argument attendu:
v = req_args[arg_name]
# try to convert all arguments to INTEGERS
# necessary for db ids and boolean values
@ -244,9 +183,9 @@ def scodoc7func(func):
# Add keyword arguments
if nb_default_args:
for arg_name in argspec.args[-nb_default_args:]:
if arg_name == "REQUEST": # special case
kwargs[arg_name] = REQUEST
elif arg_name in req_args:
# if arg_name == "REQUEST": # special case
# kwargs[arg_name] = REQUEST
if arg_name in req_args:
# set argument kw optionnel
v = req_args[arg_name]
# try to convert all arguments to INTEGERS
@ -270,13 +209,13 @@ def scodoc7func(func):
# Build response, adding collected http headers:
headers = []
kw = {"response": value, "status": 200}
if g.zrequest:
headers = g.zrequest.RESPONSE.headers
if not headers:
# no customized header, speedup:
return value
if "content-type" in headers:
kw["mimetype"] = headers["content-type"]
# if g.zrequest:
# headers = g.zrequest.RESPONSE.headers
# if not headers:
# # no customized header, speedup:
# return value
# if "content-type" in headers:
# kw["mimetype"] = headers["content-type"]
r = flask.Response(**kw)
for h in headers:
r.headers[h] = headers[h]

View File

@ -73,3 +73,17 @@ class BilletAbsence(db.Model):
entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
# true si l'absence _pourrait_ etre justifiée
justified = db.Column(db.Boolean(), default=False, server_default="false")
def to_dict(self):
data = {
"id": self.id,
"billet_id": self.id,
"etudid": self.etudid,
"abs_begin": self.abs_begin,
"abs_end": self.abs_begin,
"description": self.description,
"etat": self.etat,
"entry_date": self.entry_date,
"justified": self.justified,
}
return data

View File

@ -33,7 +33,7 @@ class Departement(db.Model):
semsets = db.relationship("NotesSemSet", lazy="dynamic", backref="departement")
def __repr__(self):
return f"<Departement {self.acronym}>"
return f"<{self.__class__.__name__}(id={self.id}, acronym='{self.acronym}')>"
def to_dict(self):
data = {

View File

@ -41,7 +41,10 @@ class Identite(db.Model):
code_nip = db.Column(db.Text())
code_ine = db.Column(db.Text())
# Ancien id ScoDoc7 pour les migrations de bases anciennes
# ne pas utiliser après migrate_scodoc7_dept_archive
scodoc7_id = db.Column(db.Text(), nullable=True)
#
billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
class Adresse(db.Model):

View File

@ -11,7 +11,7 @@ class NotesFormation(db.Model):
"""Programme pédagogique d'une formation"""
__tablename__ = "notes_formations"
__table_args__ = (db.UniqueConstraint("acronyme", "titre", "version"),)
__table_args__ = (db.UniqueConstraint("dept_id", "acronyme", "titre", "version"),)
id = db.Column(db.Integer, primary_key=True)
formation_id = db.synonym("id")
@ -30,8 +30,12 @@ class NotesFormation(db.Model):
type_parcours = db.Column(db.Integer, default=0, server_default="0")
code_specialite = db.Column(db.String(SHORT_STR_LEN))
ues = db.relationship("NotesUE", backref="formation", lazy="dynamic")
formsemestres = db.relationship("FormSemestre", lazy="dynamic", backref="formation")
def __repr__(self):
return f"<{self.__class__.__name__}(id={self.id}, dept_id={self.dept_id}, acronyme='{self.acronyme}')>"
class NotesUE(db.Model):
"""Unité d'Enseignement"""
@ -61,6 +65,9 @@ class NotesUE(db.Model):
# coef UE, utilise seulement si l'option use_ue_coefs est activée:
coefficient = db.Column(db.Float)
def __repr__(self):
return f"<{self.__class__.__name__}(id={self.id}, formation_id={self.formation_id}, acronyme='{self.acronyme}')>"
class NotesMatiere(db.Model):
"""Matières: regroupe les modules d'une UE

View File

@ -41,6 +41,10 @@ class FormSemestre(db.Model):
bul_hide_xml = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false"
)
# Bloque le calcul des moyennes (générale et d'UE)
block_moyennes = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false"
)
# semestres decales (pour gestion jurys):
gestion_semestrielle = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false"
@ -70,6 +74,7 @@ class FormSemestre(db.Model):
"NotesFormsemestreEtape", cascade="all,delete", backref="notes_formsemestre"
)
# Ancien id ScoDoc7 pour les migrations de bases anciennes
# ne pas utiliser après migrate_scodoc7_dept_archive
scodoc7_id = db.Column(db.Text(), nullable=True)
def __init__(self, **kwargs):

8
app/pe/README.md Normal file
View File

@ -0,0 +1,8 @@
# Module "Avis de poursuite d'étude"
Conçu et développé sur ScoDoc 7 par Cléo Baras (IUT de Grenoble) pour le DUT.
Actuellement non opérationnel dans ScoDoc 9.

View File

@ -33,9 +33,9 @@
import os
import codecs
import re
from app.scodoc import pe_jurype
from app.scodoc import pe_tagtable
from app.scodoc import pe_tools
from app.pe import pe_tagtable
from app.pe import pe_jurype
from app.pe import pe_tools
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -48,7 +48,7 @@ from app.scodoc import sco_etud
DEBUG = False # Pour debug et repérage des prints à changer en Log
DONNEE_MANQUANTE = (
u"" # Caractère de remplacement des données manquantes dans un avis PE
"" # Caractère de remplacement des données manquantes dans un avis PE
)
# ----------------------------------------------------------------------------------------
@ -102,17 +102,17 @@ def comp_latex_parcourstimeline(etudiant, promo, taille=17):
result: chaine unicode (EV:)
"""
codelatexDebut = (
u"""
""""
\\begin{parcourstimeline}{**debut**}{**fin**}{**nbreSemestres**}{%d}
"""
% taille
)
modeleEvent = u"""
modeleEvent = """
\\parcoursevent{**nosem**}{**nomsem**}{**descr**}
"""
codelatexFin = u"""
codelatexFin = """
\\end{parcourstimeline}
"""
reslatex = codelatexDebut
@ -125,13 +125,13 @@ def comp_latex_parcourstimeline(etudiant, promo, taille=17):
for no_sem in range(etudiant["nbSemestres"]):
descr = modeleEvent
nom_semestre_dans_parcours = parcours[no_sem]["nom_semestre_dans_parcours"]
descr = descr.replace(u"**nosem**", str(no_sem + 1))
descr = descr.replace("**nosem**", str(no_sem + 1))
if no_sem % 2 == 0:
descr = descr.replace(u"**nomsem**", nom_semestre_dans_parcours)
descr = descr.replace(u"**descr**", u"")
descr = descr.replace("**nomsem**", nom_semestre_dans_parcours)
descr = descr.replace("**descr**", "")
else:
descr = descr.replace(u"**nomsem**", u"")
descr = descr.replace(u"**descr**", nom_semestre_dans_parcours)
descr = descr.replace("**nomsem**", "")
descr = descr.replace("**descr**", nom_semestre_dans_parcours)
reslatex += descr
reslatex += codelatexFin
return reslatex
@ -166,7 +166,7 @@ def get_code_latex_avis_etudiant(
result: chaine unicode
"""
if not donnees_etudiant or not un_avis_latex: # Cas d'un template vide
return annotationPE if annotationPE else u""
return annotationPE if annotationPE else ""
# Le template latex (corps + footer)
code = un_avis_latex + "\n\n" + footer_latex
@ -189,17 +189,17 @@ def get_code_latex_avis_etudiant(
)
# La macro parcourstimeline
elif tag_latex == u"parcourstimeline":
elif tag_latex == "parcourstimeline":
valeur = comp_latex_parcourstimeline(
donnees_etudiant, donnees_etudiant["promo"]
)
# Le tag annotationPE
elif tag_latex == u"annotation":
elif tag_latex == "annotation":
valeur = annotationPE
# Le tag bilanParTag
elif tag_latex == u"bilanParTag":
elif tag_latex == "bilanParTag":
valeur = get_bilanParTag(donnees_etudiant)
# Les tags "simples": par ex. nom, prenom, civilite, ...
@ -249,14 +249,14 @@ def get_annotation_PE(etudid, tag_annotation_pe):
]["comment_u"]
annotationPE = exp.sub(
u"", annotationPE
"", annotationPE
) # Suppression du tag d'annotation PE
annotationPE = annotationPE.replace(u"\r", u"") # Suppression des \r
annotationPE = annotationPE.replace("\r", "") # Suppression des \r
annotationPE = annotationPE.replace(
u"<br/>", u"\n\n"
"<br/>", "\n\n"
) # Interprète les retours chariots html
return annotationPE
return u"" # pas d'annotations
return "" # pas d'annotations
# ----------------------------------------------------------------------------------------
@ -282,7 +282,7 @@ def str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, champ)
):
donnees_numeriques = donnees_etudiant[aggregat][groupe][tag_scodoc]
if champ == "rang":
valeur = u"%s/%d" % (
valeur = "%s/%d" % (
donnees_numeriques[
pe_tagtable.TableTag.FORMAT_DONNEES_ETUDIANTS.index("rang")
],
@ -303,9 +303,9 @@ def str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, champ)
if isinstance(
donnees_numeriques[indice_champ], float
): # valeur numérique avec formattage unicode
valeur = u"%2.2f" % donnees_numeriques[indice_champ]
valeur = "%2.2f" % donnees_numeriques[indice_champ]
else:
valeur = u"%s" % donnees_numeriques[indice_champ]
valeur = "%s" % donnees_numeriques[indice_champ]
return valeur
@ -356,29 +356,27 @@ def get_bilanParTag(donnees_etudiant, groupe="groupe"):
("\\textit{" + rang + "}") if note else ""
) # rang masqué si pas de notes
code_latex = u"\\begin{tabular}{|c|" + "|c" * (len(entete)) + "|}\n"
code_latex += u"\\hline \n"
code_latex = "\\begin{tabular}{|c|" + "|c" * (len(entete)) + "|}\n"
code_latex += "\\hline \n"
code_latex += (
u" & "
" & "
+ " & ".join(["\\textbf{" + intitule + "}" for (agg, intitule, _) in entete])
+ " \\\\ \n"
)
code_latex += u"\\hline"
code_latex += u"\\hline \n"
code_latex += "\\hline"
code_latex += "\\hline \n"
for (i, ligne_val) in enumerate(valeurs["note"]):
titre = lignes[i] # règle le pb d'encodage
code_latex += "\\textbf{" + titre + "} & " + " & ".join(ligne_val) + "\\\\ \n"
code_latex += (
u"\\textbf{" + titre + u"} & " + " & ".join(ligne_val) + u"\\\\ \n"
)
code_latex += (
u" & "
+ u" & ".join(
[u"{\\scriptsize " + clsmt + u"}" for clsmt in valeurs["rang"][i]]
" & "
+ " & ".join(
["{\\scriptsize " + clsmt + "}" for clsmt in valeurs["rang"][i]]
)
+ u"\\\\ \n"
+ "\\\\ \n"
)
code_latex += u"\\hline \n"
code_latex += u"\\end{tabular}"
code_latex += "\\hline \n"
code_latex += "\\end{tabular}"
return code_latex
@ -397,21 +395,15 @@ def get_avis_poursuite_par_etudiant(
nom = jury.syntheseJury[etudid]["nom"].replace(" ", "-")
prenom = jury.syntheseJury[etudid]["prenom"].replace(" ", "-")
nom_fichier = (
u"avis_poursuite_"
+ pe_tools.remove_accents(nom)
+ "_"
+ pe_tools.remove_accents(prenom)
+ "_"
+ str(etudid)
nom_fichier = scu.sanitize_filename(
"avis_poursuite_%s_%s_%s" % (nom, prenom, etudid)
)
if pe_tools.PE_DEBUG:
pe_tools.pe_print("fichier latex =" + nom_fichier, type(nom_fichier))
# Entete (commentaire)
contenu_latex = (
u"%% ---- Etudiant: " + civilite_str + " " + nom + " " + prenom + u"\n"
"%% ---- Etudiant: " + civilite_str + " " + nom + " " + prenom + "\n"
)
# les annnotations

View File

@ -52,10 +52,10 @@ from app.scodoc import sco_cache
from app.scodoc import sco_codes_parcours # sco_codes_parcours.NEXT -> sem suivant
from app.scodoc import sco_etud
from app.scodoc import sco_formsemestre
from app.scodoc import pe_tagtable
from app.scodoc import pe_tools
from app.scodoc import pe_semestretag
from app.scodoc import pe_settag
from app.pe import pe_tagtable
from app.pe import pe_tools
from app.pe import pe_semestretag
from app.pe import pe_settag
# ----------------------------------------------------------------------------------------
def comp_nom_semestre_dans_parcours(sem):
@ -946,7 +946,7 @@ class JuryPE(object):
return list(taglist)
def get_allTagInSyntheseJury(self):
"""Extrait tous les tags du dictionnaire syntheseJury trié par ordre alphabétique. [] si aucun tag """
"""Extrait tous les tags du dictionnaire syntheseJury trié par ordre alphabétique. [] si aucun tag"""
allTags = set()
for nom in JuryPE.PARCOURS.keys():
allTags = allTags.union(set(self.get_allTagForAggregat(nom)))

View File

@ -40,7 +40,7 @@ from app import log
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_cache
from app.scodoc import sco_tag_module
from app.scodoc import pe_tagtable
from app.pe import pe_tagtable
class SemestreTag(pe_tagtable.TableTag):

View File

@ -36,8 +36,8 @@ Created on Fri Sep 9 09:15:05 2016
@author: barasc
"""
from app.scodoc.pe_tools import pe_print, PE_DEBUG
from app.scodoc import pe_tagtable
from app.pe.pe_tools import pe_print, PE_DEBUG
from app.pe import pe_tagtable
class SetTag(pe_tagtable.TableTag):

View File

@ -167,8 +167,19 @@ def list_directory_filenames(path):
def add_local_file_to_zip(zipfile, ziproot, pathname, path_in_zip):
"""Read pathname server file and add content to zip under path_in_zip"""
rooted_path_in_zip = os.path.join(ziproot, path_in_zip)
data = open(pathname).read()
zipfile.writestr(rooted_path_in_zip, data)
zipfile.write(filename=pathname, arcname=rooted_path_in_zip)
# data = open(pathname).read()
# zipfile.writestr(rooted_path_in_zip, data)
def add_refs_to_register(register, directory):
"""Ajoute les fichiers trouvés dans directory au registre (dictionaire) sous la forme
filename => pathname
"""
length = len(directory)
for pathname in list_directory_filenames(directory):
filename = pathname[length + 1 :]
register[filename] = pathname
def add_pe_stuff_to_zip(zipfile, ziproot):
@ -179,30 +190,16 @@ def add_pe_stuff_to_zip(zipfile, ziproot):
Also copy logos
"""
register = {}
# first add standard (distrib references)
distrib_dir = os.path.join(REP_DEFAULT_AVIS, "distrib")
distrib_pathnames = list_directory_filenames(
distrib_dir
) # eg /opt/scodoc/tools/doc_poursuites_etudes/distrib/modeles/toto.tex
l = len(distrib_dir)
distrib_filenames = {x[l + 1 :] for x in distrib_pathnames} # eg modeles/toto.tex
add_refs_to_register(register=register, directory=distrib_dir)
# then add local references (some oh them may overwrite distrib refs)
local_dir = os.path.join(REP_LOCAL_AVIS, "local")
local_pathnames = list_directory_filenames(local_dir)
l = len(local_dir)
local_filenames = {x[l + 1 :] for x in local_pathnames}
for filename in distrib_filenames | local_filenames:
if filename in local_filenames:
add_local_file_to_zip(
zipfile, ziproot, os.path.join(local_dir, filename), "avis/" + filename
)
else:
add_local_file_to_zip(
zipfile,
ziproot,
os.path.join(distrib_dir, filename),
"avis/" + filename,
)
add_refs_to_register(register=register, directory=local_dir)
# at this point register contains all refs (filename, pathname) to be saved
for filename, pathname in register.items():
add_local_file_to_zip(zipfile, ziproot, pathname, "avis/" + filename)
# Logos: (add to logos/ directory in zip)
logos_names = ["logo_header.jpg", "logo_footer.jpg"]

View File

@ -42,10 +42,9 @@ from app.scodoc import sco_formsemestre
from app.scodoc import html_sco_header
from app.scodoc import sco_preferences
from app.scodoc import pe_tools
from app.scodoc.pe_tools import PE_LATEX_ENCODING
from app.scodoc import pe_jurype
from app.scodoc import pe_avislatex
from app.pe import pe_tools
from app.pe import pe_jurype
from app.pe import pe_avislatex
def _pe_view_sem_recap_form(formsemestre_id):
@ -90,7 +89,6 @@ def pe_view_sem_recap(
semBase = sco_formsemestre.get_formsemestre(formsemestre_id)
jury = pe_jurype.JuryPE(semBase)
# Ajout avis LaTeX au même zip:
etudids = list(jury.syntheseJury.keys())
@ -150,18 +148,14 @@ def pe_view_sem_recap(
footer_latex,
prefs,
)
jury.add_file_to_zip(
("avis/" + nom_fichier + ".tex").encode(PE_LATEX_ENCODING),
contenu_latex.encode(PE_LATEX_ENCODING),
)
jury.add_file_to_zip("avis/" + nom_fichier + ".tex", contenu_latex)
latex_pages[nom_fichier] = contenu_latex # Sauvegarde dans un dico
# Nouvelle version : 1 fichier par étudiant avec 1 fichier appelant créée ci-dessous
doc_latex = "\n% -----\n".join(
["\\include{" + nom + "}" for nom in sorted(latex_pages.keys())]
)
jury.add_file_to_zip("avis/avis_poursuite.tex", doc_latex.encode(PE_LATEX_ENCODING))
jury.add_file_to_zip("avis/avis_poursuite.tex", doc_latex)
# Ajoute image, LaTeX class file(s) and modeles
pe_tools.add_pe_stuff_to_zip(jury.zipfile, jury.NOM_EXPORT_ZIP)

View File

@ -8,6 +8,7 @@
v 1.3 (python3)
"""
import html
def TrivialFormulator(
@ -134,7 +135,7 @@ class TF(object):
is_submitted=False,
):
self.form_url = form_url
self.values = values
self.values = values.copy()
self.formdescription = list(formdescription)
self.initvalues = initvalues
self.method = method
@ -722,7 +723,9 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
if str(descr["allowed_values"][i]) == str(self.values[field]):
R.append('<span class="tf-ro-value">%s</span>' % labels[i])
elif input_type == "textarea":
R.append('<div class="tf-ro-textarea">%s</div>' % self.values[field])
R.append(
'<div class="tf-ro-textarea">%s</div>' % html.escape(self.values[field])
)
elif input_type == "separator" or input_type == "hidden":
pass
elif input_type == "file":

View File

@ -379,6 +379,25 @@ def bonus_iutbethune(notes_sport, coefs, infos=None):
return bonus
def bonus_iutbeziers(notes_sport, coefs, infos=None):
"""Calcul bonus modules optionels (sport, culture), regle IUT BEZIERS
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
sport , etc) non rattaches à une unité d'enseignement. Les points
au-dessus de 10 sur 20 obtenus dans chacune des matières
optionnelles sont cumulés et 3% de ces points cumulés s'ajoutent à
la moyenne générale du semestre déjà obtenue par l'étudiant.
"""
sumc = sum(coefs) # assumes sum. coefs > 0
# note_sport = sum(map(mul, notes_sport, coefs)) / sumc # moyenne pondérée
bonus = sum([(x - 10) * 0.03 for x in notes_sport if x > 10])
# le total du bonus ne doit pas dépasser 0.3 - Fred, 28/01/2020
if bonus > 0.3:
bonus = 0.3
return bonus
def bonus_demo(notes_sport, coefs, infos=None):
"""Fausse fonction "bonus" pour afficher les informations disponibles
et aider les développeurs.
@ -386,8 +405,8 @@ def bonus_demo(notes_sport, coefs, infos=None):
qui est ECRASE à chaque appel.
*** Ne pas utiliser en production !!! ***
"""
f = open("/tmp/scodoc_bonus.log", "w") # mettre 'a' pour ajouter en fin
f.write("\n---------------\n" + pprint.pformat(infos) + "\n")
with open("/tmp/scodoc_bonus.log", "w") as f: # mettre 'a' pour ajouter en fin
f.write("\n---------------\n" + pprint.pformat(infos) + "\n")
# Statut de chaque UE
# for ue_id in infos['moy_ues']:
# ue_status = infos['moy_ues'][ue_id]

View File

@ -185,6 +185,9 @@ class GenTable(object):
else:
self.preferences = DEFAULT_TABLE_PREFERENCES()
def __repr__(self):
return f"<gen_table( nrows={self.get_nb_rows()}, ncols={self.get_nb_cols()} )>"
def get_nb_cols(self):
return len(self.columns_ids)
@ -468,7 +471,10 @@ class GenTable(object):
def excel(self, wb=None):
"""Simple Excel representation of the table"""
ses = sco_excel.ScoExcelSheet(sheet_name=self.xls_sheet_name, wb=wb)
if wb is None:
ses = sco_excel.ScoExcelSheet(sheet_name=self.xls_sheet_name, wb=wb)
else:
ses = wb.create_sheet(sheet_name=self.xls_sheet_name)
ses.rows += self.xls_before_table
style_bold = sco_excel.excel_make_style(bold=True)
style_base = sco_excel.excel_make_style()
@ -482,9 +488,7 @@ class GenTable(object):
ses.append_blank_row() # empty line
ses.append_single_cell_row(self.origin, style_base)
if wb is None:
return ses.generate_standalone()
else:
ses.generate_embeded()
return ses.generate()
def text(self):
"raw text representation of the table"
@ -573,7 +577,7 @@ class GenTable(object):
"""
doc = ElementTree.Element(
self.xml_outer_tag,
id=self.table_id,
id=str(self.table_id),
origin=self.origin or "",
caption=self.caption or "",
)
@ -587,7 +591,7 @@ class GenTable(object):
v = row.get(cid, "")
if v is None:
v = ""
x_cell = ElementTree.Element(cid, value=str(v))
x_cell = ElementTree.Element(str(cid), value=str(v))
x_row.append(x_cell)
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
@ -610,7 +614,6 @@ class GenTable(object):
format="html",
page_title="",
filename=None,
REQUEST=None,
javascripts=[],
with_html_headers=True,
publish=True,
@ -643,35 +646,53 @@ class GenTable(object):
H.append(html_sco_header.sco_footer())
return "\n".join(H)
elif format == "pdf":
objects = self.pdf()
doc = sco_pdf.pdf_basic_page(
objects, title=title, preferences=self.preferences
pdf_objs = self.pdf()
pdf_doc = sco_pdf.pdf_basic_page(
pdf_objs, title=title, preferences=self.preferences
)
if publish:
return scu.sendPDFFile(REQUEST, doc, filename + ".pdf")
return scu.send_file(
pdf_doc,
filename,
suffix=".pdf",
mime=scu.PDF_MIMETYPE,
)
else:
return doc
elif format == "xls" or format == "xlsx":
return pdf_doc
elif format == "xls" or format == "xlsx": # dans les 2 cas retourne du xlsx
xls = self.excel()
if publish:
return sco_excel.send_excel_file(
REQUEST, xls, filename + scu.XLSX_SUFFIX
return scu.send_file(
xls,
filename,
suffix=scu.XLSX_SUFFIX,
mime=scu.XLSX_MIMETYPE,
)
else:
return xls
elif format == "text":
return self.text()
elif format == "csv":
return scu.sendCSVFile(REQUEST, self.text(), filename + ".csv")
return scu.send_file(
self.text(),
filename,
suffix=".csv",
mime=scu.CSV_MIMETYPE,
attached=True,
)
elif format == "xml":
xml = self.xml()
if REQUEST and publish:
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
if publish:
return scu.send_file(
xml, filename, suffix=".xml", mime=scu.XML_MIMETYPE
)
return xml
elif format == "json":
js = self.json()
if REQUEST and publish:
REQUEST.RESPONSE.setHeader("content-type", scu.JSON_MIMETYPE)
if publish:
return scu.send_file(
js, filename, suffix=".json", mime=scu.JSON_MIMETYPE
)
return js
else:
log("make_page: format=%s" % format)
@ -732,5 +753,5 @@ if __name__ == "__main__":
document.build(objects)
data = doc.getvalue()
open("/tmp/gen_table.pdf", "wb").write(data)
p = T.make_page(format="pdf", REQUEST=None)
p = T.make_page(format="pdf")
open("toto.pdf", "wb").write(p)

View File

@ -87,10 +87,6 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
)
_TOP_LEVEL_CSS = """
<style type="text/css">
</style>"""
_HTML_BEGIN = """<?xml version="1.0" encoding="%(encoding)s"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
@ -105,31 +101,30 @@ _HTML_BEGIN = """<?xml version="1.0" encoding="%(encoding)s"?>
<link href="/ScoDoc/static/css/scodoc.css" rel="stylesheet" type="text/css" />
<link href="/ScoDoc/static/css/menu.css" rel="stylesheet" type="text/css" />
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/menu.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/sorttable.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/bubble.js"></script>
<script type="text/javascript">
<script src="/ScoDoc/static/libjs/menu.js"></script>
<script src="/ScoDoc/static/libjs/sorttable.js"></script>
<script src="/ScoDoc/static/libjs/bubble.js"></script>
<script>
window.onload=function(){enableTooltips("gtrcontent")};
</script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery-migrate-1.2.0.min.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery.field.min.js"></script>
<script src="/ScoDoc/static/jQuery/jquery.js"></script>
<script src="/ScoDoc/static/jQuery/jquery-migrate-1.2.0.min.js"></script>
<script src="/ScoDoc/static/libjs/jquery.field.min.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
<script src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
<script src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.css" />
<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/scodoc.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/etud_info.js"></script>
<script src="/ScoDoc/static/js/scodoc.js"></script>
<script src="/ScoDoc/static/js/etud_info.js"></script>
"""
def scodoc_top_html_header(page_title="ScoDoc: bienvenue"):
H = [
_HTML_BEGIN % {"page_title": page_title, "encoding": scu.SCO_ENCODING},
_TOP_LEVEL_CSS,
"""</head><body class="gtrcontent" id="gtrcontent">""",
scu.CUSTOM_HTML_HEADER_CNX,
]
@ -185,13 +180,10 @@ def sco_header(
init_jquery = True
H = [
"""<?xml version="1.0" encoding="%(encoding)s"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
"""<!DOCTYPE html><html lang="fr">
<head>
<meta charset="utf-8"/>
<title>%(page_title)s</title>
<meta http-equiv="Content-Type" content="text/html; charset=%(encoding)s" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="LANG" content="fr" />
<meta name="DESCRIPTION" content="ScoDoc" />
@ -206,9 +198,7 @@ def sco_header(
)
if init_google_maps:
# It may be necessary to add an API key:
H.append(
'<script type="text/javascript" src="https://maps.google.com/maps/api/js"></script>'
)
H.append('<script src="https://maps.google.com/maps/api/js"></script>')
# Feuilles de style additionnelles:
for cssstyle in cssstyles:
@ -223,9 +213,9 @@ def sco_header(
<link href="/ScoDoc/static/css/menu.css" rel="stylesheet" type="text/css" />
<link href="/ScoDoc/static/css/gt_table.css" rel="stylesheet" type="text/css" />
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/menu.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/bubble.js"></script>
<script type="text/javascript">
<script src="/ScoDoc/static/libjs/menu.js"></script>
<script src="/ScoDoc/static/libjs/bubble.js"></script>
<script>
window.onload=function(){enableTooltips("gtrcontent")};
var SCO_URL="%(ScoURL)s";
@ -236,16 +226,14 @@ def sco_header(
# jQuery
if init_jquery:
H.append(
"""<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery.js"></script>
"""<script src="/ScoDoc/static/jQuery/jquery.js"></script>
"""
)
H.append(
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery.field.min.js"></script>'
)
H.append('<script src="/ScoDoc/static/libjs/jquery.field.min.js"></script>')
# qTip
if init_qtip:
H.append(
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>'
'<script src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>'
)
H.append(
'<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.css" />'
@ -253,32 +241,25 @@ def sco_header(
if init_jquery_ui:
H.append(
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>'
)
# H.append('<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery-ui/js/jquery-ui-i18n.js"></script>')
H.append(
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/scodoc.js"></script>'
'<script src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>'
)
# H.append('<script src="/ScoDoc/static/libjs/jquery-ui/js/jquery-ui-i18n.js"></script>')
H.append('<script src="/ScoDoc/static/js/scodoc.js"></script>')
if init_google_maps:
H.append(
'<script type="text/javascript" src="/ScoDoc/static/libjs/jquery.ui.map.full.min.js"></script>'
'<script src="/ScoDoc/static/libjs/jquery.ui.map.full.min.js"></script>'
)
if init_datatables:
H.append(
'<link rel="stylesheet" type="text/css" href="/ScoDoc/static/DataTables/datatables.min.css"/>'
)
H.append(
'<script type="text/javascript" src="/ScoDoc/static/DataTables/datatables.min.js"></script>'
)
H.append('<script src="/ScoDoc/static/DataTables/datatables.min.js"></script>')
# JS additionels
for js in javascripts:
H.append(
"""<script language="javascript" type="text/javascript" src="/ScoDoc/static/%s"></script>\n"""
% js
)
H.append("""<script src="/ScoDoc/static/%s"></script>\n""" % js)
H.append(
"""<style type="text/css">
"""<style>
.gtrcontent {
margin-left: %(margin_left)s;
height: 100%%;
@ -290,7 +271,7 @@ def sco_header(
)
# Scripts de la page:
if scripts:
H.append("""<script language="javascript" type="text/javascript">""")
H.append("""<script>""")
for script in scripts:
H.append(script)
H.append("""</script>""")
@ -337,13 +318,7 @@ def sco_footer():
def html_sem_header(
REQUEST,
title,
sem=None,
with_page_header=True,
with_h2=True,
page_title=None,
**args
title, sem=None, with_page_header=True, with_h2=True, page_title=None, **args
):
"Titre d'une page semestre avec lien vers tableau de bord"
# sem now unused and thus optional...

View File

@ -28,9 +28,8 @@
"""
Génération de la "sidebar" (marge gauche des pages HTML)
"""
from flask import url_for
from flask import g
from flask import request
from flask import render_template, url_for
from flask import g, request
from flask_login import current_user
import app.scodoc.sco_utils as scu
@ -40,17 +39,11 @@ from app.scodoc.sco_permissions import Permission
def sidebar_common():
"partie commune à toutes les sidebar"
params = {
"ScoURL": scu.ScoURL(),
"UsersURL": scu.UsersURL(),
"NotesURL": scu.NotesURL(),
"AbsencesURL": scu.AbsencesURL(),
"authuser": current_user.user_name,
}
H = [
f"""<a class="scodoc_title" href="about">ScoDoc 9</a>
<div id="authuser"><a id="authuserlink" href="{
url_for("users.user_info_page", scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
url_for("users.user_info_page",
scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
}">{current_user.user_name}</a>
<br/><a id="deconnectlink" href="{url_for("auth.logout")}">déconnexion</a>
</div>
@ -71,7 +64,8 @@ def sidebar_common():
if current_user.has_permission(Permission.ScoChangePreferences):
H.append(
f"""<a href="{url_for("scolar.edit_preferences", scodoc_dept=g.scodoc_dept)}" class="sidebar">Paramétrage</a> <br/>"""
f"""<a href="{url_for("scolar.edit_preferences", scodoc_dept=g.scodoc_dept)}"
class="sidebar">Paramétrage</a> <br/>"""
)
return "".join(H)
@ -97,11 +91,12 @@ def sidebar():
"""
]
# ---- Il y-a-t-il un etudiant selectionné ?
etudid = None
if request.method == "GET":
etudid = request.args.get("etudid", None)
elif request.method == "POST":
etudid = request.form.get("etudid", None)
etudid = g.get("etudid", None)
if not etudid:
if request.method == "GET":
etudid = request.args.get("etudid", None)
elif request.method == "POST":
etudid = request.form.get("etudid", None)
if etudid:
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
@ -155,8 +150,9 @@ def sidebar():
<div class="sidebar-bottom"><a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }" class="sidebar">À propos</a><br/>
<a href="{ scu.SCO_USER_MANUAL }" target="_blank" class="sidebar">Aide</a>
</div></div>
<div class="logo-logo"><a href= { url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }
">{ scu.icontag("scologo_img", no_size=True) }</a>
<div class="logo-logo">
<a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }">
{ scu.icontag("scologo_img", no_size=True) }</a>
</div>
</div>
<!-- end of sidebar -->
@ -167,19 +163,7 @@ def sidebar():
def sidebar_dept():
"""Partie supérieure de la marge de gauche"""
H = [
f"""<h2 class="insidebar">Dépt. {sco_preferences.get_preference("DeptName")}</h2>
<a href="{url_for("scodoc.index")}" class="sidebar">Accueil</a> <br/> """
]
dept_intranet_url = sco_preferences.get_preference("DeptIntranetURL")
if dept_intranet_url:
H.append(
f"""<a href="{dept_intranet_url}" class="sidebar">{
sco_preferences.get_preference("DeptIntranetTitle")}</a> <br/>"""
)
# Entreprises pas encore supporté en ScoDoc8
# H.append(
# """<br/><a href="%(ScoURL)s/Entreprises" class="sidebar">Entreprises</a> <br/>"""
# % infos
# )
return "\n".join(H)
return render_template(
"sidebar_dept.html",
prefs=sco_preferences.SemPreferences(),
)

View File

@ -186,6 +186,8 @@ class NotesTable(object):
self.use_ue_coefs = sco_preferences.get_preference(
"use_ue_coefs", formsemestre_id
)
# si vrai, bloque calcul des moy gen. et d'UE.:
self.block_moyennes = self.sem["block_moyennes"]
# Infos sur les etudiants
self.inscrlist = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
args={"formsemestre_id": formsemestre_id}
@ -738,6 +740,7 @@ class NotesTable(object):
block_computation = (
self.inscrdict[etudid]["etat"] == "D"
or self.inscrdict[etudid]["etat"] == DEF
or self.block_moyennes
)
moy_ues = {}

View File

@ -88,7 +88,15 @@ def SimpleDictFetch(query, args, cursor=None):
return cursor.dictfetchall()
def DBInsertDict(cnx, table, vals, commit=0, convert_empty_to_nulls=1, return_id=True):
def DBInsertDict(
cnx,
table,
vals,
commit=0,
convert_empty_to_nulls=1,
return_id=True,
ignore_conflicts=False,
):
"""insert into table values in dict 'vals'
Return: id de l'object créé
"""
@ -103,13 +111,18 @@ def DBInsertDict(cnx, table, vals, commit=0, convert_empty_to_nulls=1, return_id
fmt = ",".join(["%%(%s)s" % col for col in cols])
# print 'insert into %s (%s) values (%s)' % (table,colnames,fmt)
oid = None
if ignore_conflicts:
ignore = " ON CONFLICT DO NOTHING"
else:
ignore = ""
try:
if vals:
cursor.execute(
"insert into %s (%s) values (%s)" % (table, colnames, fmt), vals
"insert into %s (%s) values (%s)%s" % (table, colnames, fmt, ignore),
vals,
)
else:
cursor.execute("insert into %s default values" % table)
cursor.execute("insert into %s default values%s" % (table, ignore))
if return_id:
cursor.execute(f"SELECT CURRVAL('{table}_id_seq')") # id créé
oid = cursor.fetchone()[0]
@ -291,6 +304,7 @@ class EditableTable(object):
fields_creators={}, # { field : [ sql_command_to_create_it ] }
filter_nulls=True, # dont allow to set fields to null
filter_dept=False, # ajoute selection sur g.scodoc_dept_id
insert_ignore_conflicts=False,
):
self.table_name = table_name
self.id_name = id_name
@ -311,6 +325,7 @@ class EditableTable(object):
self.filter_nulls = filter_nulls
self.filter_dept = filter_dept
self.sql_default_values = None
self.insert_ignore_conflicts = insert_ignore_conflicts
def create(self, cnx, args):
"create object in table"
@ -336,6 +351,7 @@ class EditableTable(object):
vals,
commit=True,
return_id=(self.id_name is not None),
ignore_conflicts=self.insert_ignore_conflicts,
)
return new_id

View File

@ -626,7 +626,6 @@ def add_absence(
jour,
matin,
estjust,
REQUEST,
description=None,
moduleimpl_id=None,
):
@ -656,7 +655,7 @@ def add_absence(
sco_abs_notification.abs_notify(etudid, jour)
def add_justif(etudid, jour, matin, REQUEST, description=None):
def add_justif(etudid, jour, matin, description=None):
"Ajoute un justificatif dans la base"
# unpublished
if _isFarFutur(jour):
@ -665,7 +664,9 @@ def add_justif(etudid, jour, matin, REQUEST, description=None):
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"insert into absences (etudid,jour,estabs,estjust,matin, description) values (%(etudid)s,%(jour)s, FALSE, TRUE, %(matin)s, %(description)s )",
"""INSERT INTO absences (etudid, jour, estabs, estjust, matin, description)
VALUES (%(etudid)s, %(jour)s, FALSE, TRUE, %(matin)s, %(description)s)
""",
vars(),
)
logdb(
@ -678,7 +679,7 @@ def add_justif(etudid, jour, matin, REQUEST, description=None):
invalidate_abs_etud_date(etudid, jour)
def _add_abslist(abslist, REQUEST, moduleimpl_id=None):
def add_abslist(abslist, moduleimpl_id=None):
for a in abslist:
etudid, jour, ampm = a.split(":")
if ampm == "am":
@ -689,7 +690,7 @@ def _add_abslist(abslist, REQUEST, moduleimpl_id=None):
raise ValueError("invalid ampm !")
# ajoute abs si pas deja absent
if count_abs(etudid, jour, jour, matin, moduleimpl_id) == 0:
add_absence(etudid, jour, matin, 0, REQUEST, "", moduleimpl_id)
add_absence(etudid, jour, matin, 0, "", moduleimpl_id)
def annule_absence(etudid, jour, matin, moduleimpl_id=None):
@ -721,7 +722,7 @@ def annule_absence(etudid, jour, matin, moduleimpl_id=None):
invalidate_abs_etud_date(etudid, jour)
def annule_justif(etudid, jour, matin, REQUEST=None):
def annule_justif(etudid, jour, matin):
"Annule un justificatif"
# unpublished
matin = _toboolean(matin)

View File

@ -30,7 +30,7 @@
"""
import datetime
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.sco_utils as scu
from app.scodoc import notesdb as ndb
@ -58,7 +58,6 @@ def doSignaleAbsence(
estjust=False,
description=None,
etudid=False,
REQUEST=None,
): # etudid implied
"""Signalement d'une absence.
@ -69,7 +68,8 @@ def doSignaleAbsence(
demijournee: 2 si journée complète, 1 matin, 0 après-midi
estjust: absence justifiée
description: str
etudid: etudiant concerné. Si non spécifié, cherche dans REQUEST.form
etudid: etudiant concerné. Si non spécifié, cherche dans
les paramètres de la requête courante.
"""
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
etudid = etud["etudid"]
@ -86,7 +86,6 @@ def doSignaleAbsence(
jour,
False,
estjust,
REQUEST,
description_abs,
moduleimpl_id,
)
@ -95,7 +94,6 @@ def doSignaleAbsence(
jour,
True,
estjust,
REQUEST,
description_abs,
moduleimpl_id,
)
@ -106,7 +104,6 @@ def doSignaleAbsence(
jour,
demijournee,
estjust,
REQUEST,
description_abs,
moduleimpl_id,
)
@ -156,7 +153,7 @@ def doSignaleAbsence(
return "\n".join(H)
def SignaleAbsenceEtud(REQUEST=None): # etudid implied
def SignaleAbsenceEtud(): # etudid implied
"""Formulaire individuel simple de signalement d'une absence"""
# brute-force portage from very old dtml code ...
etud = sco_etud.get_etud_info(filled=True)[0]
@ -228,7 +225,6 @@ def SignaleAbsenceEtud(REQUEST=None): # etudid implied
sco_photos.etud_photo_html(
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
),
"""</a></td></tr></table>""",
"""
@ -281,7 +277,6 @@ def doJustifAbsence(
demijournee,
description=None,
etudid=False,
REQUEST=None,
): # etudid implied
"""Justification d'une absence
@ -291,7 +286,8 @@ def doJustifAbsence(
demijournee: 2 si journée complète, 1 matin, 0 après-midi
estjust: absence justifiée
description: str
etudid: etudiant concerné. Si non spécifié, cherche dans REQUEST.form
etudid: etudiant concerné. Si non spécifié, cherche dans les
paramètres de la requête.
"""
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
etudid = etud["etudid"]
@ -305,14 +301,12 @@ def doJustifAbsence(
etudid=etudid,
jour=jour,
matin=False,
REQUEST=REQUEST,
description=description_abs,
)
sco_abs.add_justif(
etudid=etudid,
jour=jour,
matin=True,
REQUEST=REQUEST,
description=description_abs,
)
nbadded += 2
@ -321,7 +315,6 @@ def doJustifAbsence(
etudid=etudid,
jour=jour,
matin=demijournee,
REQUEST=REQUEST,
description=description_abs,
)
nbadded += 1
@ -357,7 +350,7 @@ def doJustifAbsence(
return "\n".join(H)
def JustifAbsenceEtud(REQUEST=None): # etudid implied
def JustifAbsenceEtud(): # etudid implied
"""Formulaire individuel simple de justification d'une absence"""
# brute-force portage from very old dtml code ...
etud = sco_etud.get_etud_info(filled=True)[0]
@ -376,7 +369,6 @@ def JustifAbsenceEtud(REQUEST=None): # etudid implied
sco_photos.etud_photo_html(
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
),
"""</a></td></tr></table>""",
"""
@ -412,9 +404,7 @@ Raison: <input type="text" name="description" size="42"/> (optionnel)
return "\n".join(H)
def doAnnuleAbsence(
datedebut, datefin, demijournee, etudid=False, REQUEST=None
): # etudid implied
def doAnnuleAbsence(datedebut, datefin, demijournee, etudid=False): # etudid implied
"""Annulation des absences pour une demi journée"""
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
etudid = etud["etudid"]
@ -462,7 +452,7 @@ autre absence pour <b>%(nomprenom)s</b></a></li>
return "\n".join(H)
def AnnuleAbsenceEtud(REQUEST=None): # etudid implied
def AnnuleAbsenceEtud(): # etudid implied
"""Formulaire individuel simple d'annulation d'une absence"""
# brute-force portage from very old dtml code ...
etud = sco_etud.get_etud_info(filled=True)[0]
@ -482,7 +472,6 @@ def AnnuleAbsenceEtud(REQUEST=None): # etudid implied
sco_photos.etud_photo_html(
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
),
"""</a></td></tr></table>""",
"""<p>A n'utiliser que suite à une erreur de saisie ou lorsqu'il s'avère que l'étudiant était en fait présent. </p>
@ -548,7 +537,7 @@ def AnnuleAbsenceEtud(REQUEST=None): # etudid implied
return "\n".join(H)
def doAnnuleJustif(datedebut0, datefin0, demijournee, REQUEST=None): # etudid implied
def doAnnuleJustif(datedebut0, datefin0, demijournee): # etudid implied
"""Annulation d'une justification"""
etud = sco_etud.get_etud_info(filled=True)[0]
etudid = etud["etudid"]
@ -558,11 +547,11 @@ def doAnnuleJustif(datedebut0, datefin0, demijournee, REQUEST=None): # etudid i
for jour in dates:
# Attention: supprime matin et après-midi
if demijournee == 2:
sco_abs.annule_justif(etudid, jour, False, REQUEST=REQUEST)
sco_abs.annule_justif(etudid, jour, True, REQUEST=REQUEST)
sco_abs.annule_justif(etudid, jour, False)
sco_abs.annule_justif(etudid, jour, True)
nbadded += 2
else:
sco_abs.annule_justif(etudid, jour, demijournee, REQUEST=REQUEST)
sco_abs.annule_justif(etudid, jour, demijournee)
nbadded += 1
#
H = [
@ -716,7 +705,6 @@ def formChoixSemestreGroupe(all=False):
def CalAbs(etudid, sco_year=None):
"""Calendrier des absences d'un etudiant"""
# crude portage from 1999 DTML
REQUEST = None # XXX
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
etudid = etud["etudid"]
anneescolaire = int(scu.AnneeScolaire(sco_year))
@ -766,7 +754,6 @@ def CalAbs(etudid, sco_year=None):
sco_photos.etud_photo_html(
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
),
),
CalHTML,
@ -791,7 +778,6 @@ def ListeAbsEtud(
format="html",
absjust_only=0,
sco_year=None,
REQUEST=None,
):
"""Liste des absences d'un étudiant sur l'année en cours
En format 'html': page avec deux tableaux (non justifiées et justifiées).
@ -810,12 +796,12 @@ def ListeAbsEtud(
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
# Liste des absences et titres colonnes tables:
titles, columns_ids, absnonjust, absjust = _TablesAbsEtud(
titles, columns_ids, absnonjust, absjust = _tables_abs_etud(
etudid, datedebut, with_evals=with_evals, format=format
)
if REQUEST:
base_url_nj = "%s?etudid=%s&absjust_only=0" % (REQUEST.URL0, etudid)
base_url_j = "%s?etudid=%s&absjust_only=1" % (REQUEST.URL0, etudid)
if request.base_url:
base_url_nj = "%s?etudid=%s&absjust_only=0" % (request.base_url, etudid)
base_url_j = "%s?etudid=%s&absjust_only=1" % (request.base_url, etudid)
else:
base_url_nj = base_url_j = ""
tab_absnonjust = GenTable(
@ -844,9 +830,9 @@ def ListeAbsEtud(
# Formats non HTML et demande d'une seule table:
if format != "html" and format != "text":
if absjust_only == 1:
return tab_absjust.make_page(format=format, REQUEST=REQUEST)
return tab_absjust.make_page(format=format)
else:
return tab_absnonjust.make_page(format=format, REQUEST=REQUEST)
return tab_absnonjust.make_page(format=format)
if format == "html":
# Mise en forme HTML:
@ -896,13 +882,12 @@ def ListeAbsEtud(
raise ValueError("Invalid format !")
def _TablesAbsEtud(
def _tables_abs_etud(
etudid,
datedebut,
with_evals=True,
format="html",
absjust_only=0,
REQUEST=None,
):
"""Tables des absences justifiees et non justifiees d'un étudiant
sur l'année en cours
@ -928,11 +913,11 @@ def _TablesAbsEtud(
cursor.execute(
"""SELECT mi.moduleimpl_id
FROM absences abs, notes_moduleimpl_inscription mi, notes_moduleimpl m
WHERE abs.matin = %(matin)s
and abs.jour = %(jour)s
and abs.etudid = %(etudid)s
and abs.moduleimpl_id = mi.moduleimpl_id
and mi.moduleimpl_id = m.id
WHERE abs.matin = %(matin)s
and abs.jour = %(jour)s
and abs.etudid = %(etudid)s
and abs.moduleimpl_id = mi.moduleimpl_id
and mi.moduleimpl_id = m.id
and mi.etudid = %(etudid)s
""",
{
@ -959,8 +944,9 @@ def _TablesAbsEtud(
)[0]
if format == "html":
ex.append(
'<a href="Notes/moduleimpl_status?moduleimpl_id=%s">%s</a>'
% (mod["moduleimpl_id"], mod["module"]["code"])
f"""<a href="{url_for('notes.moduleimpl_status',
scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])}
">{mod["module"]["code"]}</a>"""
)
else:
ex.append(mod["module"]["code"])
@ -976,8 +962,9 @@ def _TablesAbsEtud(
)[0]
if format == "html":
ex.append(
'<a href="Notes/moduleimpl_status?moduleimpl_id=%s">%s</a>'
% (mod["moduleimpl_id"], mod["module"]["code"])
f"""<a href="{url_for('notes.moduleimpl_status',
scodoc_dept=g.scodoc_dept, moduleimpl_id=mod["moduleimpl_id"])}
">{mod["module"]["code"]}</a>"""
)
else:
ex.append(mod["module"]["code"])

View File

@ -29,37 +29,40 @@
Archives are plain files, stored in
<SCODOC_VAR_DIR>/archives/<deptid>
(where <SCODOC_VAR_DIR> is usually /opt/scodoc-data, and <deptid> a departement id)
<SCODOC_VAR_DIR>/archives/<dept_id>
(where <SCODOC_VAR_DIR> is usually /opt/scodoc-data, and <dept_id> a departement id (int))
Les PV de jurys et documents associés sont stockées dans un sous-repertoire de la forme
<archivedir>/<dept>/<formsemestre_id>/<YYYY-MM-DD-HH-MM-SS>
(formsemestre_id est ici FormSemestre.scodoc7_id ou à défaut FormSemestre.id)
(formsemestre_id est ici FormSemestre.id)
Les documents liés à l'étudiant sont dans
<archivedir>/docetuds/<dept>/<etudid>/<YYYY-MM-DD-HH-MM-SS>
(etudid est ici soit Identite.scodoc7id, soit à défaut Identite.id)
<archivedir>/docetuds/<dept_id>/<etudid>/<YYYY-MM-DD-HH-MM-SS>
(etudid est ici Identite.id)
Les maquettes Apogée pour l'export des notes sont dans
<archivedir>/apo_csv/<dept>/<annee_scolaire>-<sem_id>/<YYYY-MM-DD-HH-MM-SS>/<code_etape>.csv
<archivedir>/apo_csv/<dept_id>/<annee_scolaire>-<sem_id>/<YYYY-MM-DD-HH-MM-SS>/<code_etape>.csv
Un répertoire d'archive contient des fichiers quelconques, et un fichier texte nommé _description.txt
qui est une description (humaine, format libre) de l'archive.
"""
import os
import time
import datetime
import glob
import mimetypes
import os
import re
import shutil
import glob
import time
import flask
from flask import g
from flask import g, request
from flask_login import current_user
import app.scodoc.sco_utils as scu
from config import Config
from app import log
from app.models import Departement
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_exceptions import (
AccessDenied,
@ -108,7 +111,8 @@ class BaseArchiver(object):
If directory does not yet exist, create it.
"""
self.initialize()
dept_dir = os.path.join(self.root, g.scodoc_dept)
dept = Departement.query.filter_by(acronym=g.scodoc_dept).first()
dept_dir = os.path.join(self.root, str(dept.id))
try:
scu.GSL.acquire()
if not os.path.isdir(dept_dir):
@ -127,7 +131,8 @@ class BaseArchiver(object):
:return: list of archive oids
"""
self.initialize()
base = os.path.join(self.root, g.scodoc_dept) + os.path.sep
dept = Departement.query.filter_by(acronym=g.scodoc_dept).first()
base = os.path.join(self.root, str(dept.id)) + os.path.sep
dirs = glob.glob(base + "*")
return [os.path.split(x)[1] for x in dirs]
@ -244,31 +249,15 @@ class BaseArchiver(object):
log("reading archive file %s" % fname)
return open(fname, "rb").read()
def get_archived_file(self, REQUEST, oid, archive_name, filename):
def get_archived_file(self, oid, archive_name, filename):
"""Recupere donnees du fichier indiqué et envoie au client"""
# XXX très incomplet: devrait inférer et assigner un type MIME
archive_id = self.get_id_from_name(oid, archive_name)
data = self.get(archive_id, filename)
ext = os.path.splitext(filename.lower())[1]
if ext == ".html" or ext == ".htm":
return data
elif ext == ".xml":
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
return data
elif ext == ".xls":
return sco_excel.send_excel_file(
REQUEST, data, filename, mime=scu.XLS_MIMETYPE
)
elif ext == ".xlsx":
return sco_excel.send_excel_file(
REQUEST, data, filename, mime=scu.XLSX_MIMETYPE
)
elif ext == ".csv":
return scu.sendCSVFile(REQUEST, data, filename)
elif ext == ".pdf":
return scu.sendPDFFile(REQUEST, data, filename)
REQUEST.RESPONSE.setHeader("content-type", "application/octet-stream")
return data # should set mimetype for known files like images
mime = mimetypes.guess_type(filename)[0]
if mime is None:
mime = "application/octet-stream"
return scu.send_file(data, filename, mime=mime)
class SemsArchiver(BaseArchiver):
@ -283,7 +272,6 @@ PVArchive = SemsArchiver()
def do_formsemestre_archive(
REQUEST,
formsemestre_id,
group_ids=[], # si indiqué, ne prend que ces groupes
description="",
@ -305,7 +293,7 @@ def do_formsemestre_archive(
from app.scodoc.sco_recapcomplet import make_formsemestre_recapcomplet
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
sem_archive_id = formsemestre_id
archive_id = PVArchive.create_obj_archive(sem_archive_id, description)
date = PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M")
@ -351,14 +339,12 @@ def do_formsemestre_archive(
data = data.encode(scu.SCO_ENCODING)
PVArchive.store(archive_id, "Bulletins.xml", data)
# Decisions de jury, en XLS
data = sco_pvjury.formsemestre_pvjury(
formsemestre_id, format="xls", REQUEST=REQUEST, publish=False
)
data = sco_pvjury.formsemestre_pvjury(formsemestre_id, format="xls", publish=False)
if data:
PVArchive.store(archive_id, "Decisions_Jury" + scu.XLSX_SUFFIX, data)
# Classeur bulletins (PDF)
data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
formsemestre_id, REQUEST, version=bulVersion
formsemestre_id, version=bulVersion
)
if data:
PVArchive.store(archive_id, "Bulletins.pdf", data)
@ -389,14 +375,12 @@ def do_formsemestre_archive(
PVArchive.store(archive_id, "PV_Jury%s.pdf" % groups_filename, data)
def formsemestre_archive(REQUEST, formsemestre_id, group_ids=[]):
def formsemestre_archive(formsemestre_id, group_ids=[]):
"""Make and store new archive for this formsemestre.
(all students or only selected groups)
"""
if not sco_permissions_check.can_edit_pv(formsemestre_id):
raise AccessDenied(
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
)
raise AccessDenied("opération non autorisée pour %s" % str(current_user))
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if not group_ids:
@ -408,7 +392,6 @@ def formsemestre_archive(REQUEST, formsemestre_id, group_ids=[]):
H = [
html_sco_header.html_sem_header(
REQUEST,
"Archiver les PV et résultats du semestre",
sem=sem,
javascripts=sco_groups_view.JAVASCRIPTS,
@ -469,8 +452,8 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
)
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
descr,
cancelbutton="Annuler",
method="POST",
@ -492,7 +475,6 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
else:
tf[2]["anonymous"] = False
do_formsemestre_archive(
REQUEST,
formsemestre_id,
group_ids=group_ids,
description=tf[2]["description"],
@ -516,10 +498,10 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
)
def formsemestre_list_archives(REQUEST, formsemestre_id):
def formsemestre_list_archives(formsemestre_id):
"""Page listing archives"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
sem_archive_id = formsemestre_id
L = []
for archive_id in PVArchive.list_obj_archives(sem_archive_id):
a = {
@ -530,7 +512,7 @@ def formsemestre_list_archives(REQUEST, formsemestre_id):
}
L.append(a)
H = [html_sco_header.html_sem_header(REQUEST, "Archive des PV et résultats ", sem)]
H = [html_sco_header.html_sem_header("Archive des PV et résultats ", sem)]
if not L:
H.append("<p>aucune archive enregistrée</p>")
else:
@ -559,23 +541,19 @@ def formsemestre_list_archives(REQUEST, formsemestre_id):
return "\n".join(H) + html_sco_header.sco_footer()
def formsemestre_get_archived_file(REQUEST, formsemestre_id, archive_name, filename):
def formsemestre_get_archived_file(formsemestre_id, archive_name, filename):
"""Send file to client."""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
return PVArchive.get_archived_file(REQUEST, sem_archive_id, archive_name, filename)
sem_archive_id = formsemestre_id
return PVArchive.get_archived_file(sem_archive_id, archive_name, filename)
def formsemestre_delete_archive(
REQUEST, formsemestre_id, archive_name, dialog_confirmed=False
):
def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=False):
"""Delete an archive"""
if not sco_permissions_check.can_edit_pv(formsemestre_id):
raise AccessDenied(
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
)
raise AccessDenied("opération non autorisée pour %s" % str(current_user))
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
sem_archive_id = formsemestre_id
archive_id = PVArchive.get_id_from_name(sem_archive_id, archive_name)
dest_url = "formsemestre_list_archives?formsemestre_id=%s" % (formsemestre_id)

View File

@ -30,7 +30,8 @@
les dossiers d'admission et autres pièces utiles.
"""
import flask
from flask import url_for, g
from flask import url_for, g, request
from flask_login import current_user
import app.scodoc.sco_utils as scu
from app.scodoc import sco_import_etuds
@ -58,14 +59,14 @@ def can_edit_etud_archive(authuser):
return authuser.has_permission(Permission.ScoEtudAddAnnotations)
def etud_list_archives_html(REQUEST, etudid):
def etud_list_archives_html(etudid):
"""HTML snippet listing archives"""
can_edit = can_edit_etud_archive(REQUEST.AUTHENTICATED_USER)
can_edit = can_edit_etud_archive(current_user)
etuds = sco_etud.get_etud_info(etudid=etudid)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
etud_archive_id = etud["scodoc7_id"] or etudid
etud_archive_id = etudid
L = []
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
a = {
@ -118,7 +119,7 @@ def add_archives_info_to_etud_list(etuds):
"""
for etud in etuds:
l = []
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
etud_archive_id = etud["etudid"]
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
l.append(
"%s (%s)"
@ -130,13 +131,11 @@ def add_archives_info_to_etud_list(etuds):
etud["etudarchive"] = ", ".join(l)
def etud_upload_file_form(REQUEST, etudid):
def etud_upload_file_form(etudid):
"""Page with a form to choose and upload a file, with a description."""
# check permission
if not can_edit_etud_archive(REQUEST.AUTHENTICATED_USER):
raise AccessDenied(
"opération non autorisée pour %s" % REQUEST.AUTHENTICATED_USER
)
if not can_edit_etud_archive(current_user):
raise AccessDenied("opération non autorisée pour %s" % current_user)
etuds = sco_etud.get_etud_info(filled=True)
if not etuds:
raise ScoValueError("étudiant inexistant")
@ -153,8 +152,8 @@ def etud_upload_file_form(REQUEST, etudid):
% (scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)),
]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(
("etudid", {"default": etudid, "input_type": "hidden"}),
("datafile", {"input_type": "file", "title": "Fichier", "size": 30}),
@ -181,7 +180,7 @@ def etud_upload_file_form(REQUEST, etudid):
data = tf[2]["datafile"].read()
descr = tf[2]["description"]
filename = tf[2]["datafile"].filename
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
etud_archive_id = etud["etudid"]
_store_etud_file_to_new_archive(
etud_archive_id, data, filename, description=descr
)
@ -199,18 +198,16 @@ def _store_etud_file_to_new_archive(etud_archive_id, data, filename, description
EtudsArchive.store(archive_id, filename, data)
def etud_delete_archive(REQUEST, etudid, archive_name, dialog_confirmed=False):
def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
"""Delete an archive"""
# check permission
if not can_edit_etud_archive(REQUEST.AUTHENTICATED_USER):
raise AccessDenied(
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
)
if not can_edit_etud_archive(current_user):
raise AccessDenied("opération non autorisée pour %s" % str(current_user))
etuds = sco_etud.get_etud_info(filled=True)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
etud_archive_id = etud["etudid"]
archive_id = EtudsArchive.get_id_from_name(etud_archive_id, archive_name)
if not dialog_confirmed:
return scu.confirm_dialog(
@ -242,20 +239,18 @@ def etud_delete_archive(REQUEST, etudid, archive_name, dialog_confirmed=False):
)
def etud_get_archived_file(REQUEST, etudid, archive_name, filename):
def etud_get_archived_file(etudid, archive_name, filename):
"""Send file to client."""
etuds = sco_etud.get_etud_info(filled=True)
etuds = sco_etud.get_etud_info(etudid=etudid, filled=True)
if not etuds:
raise ScoValueError("étudiant inexistant")
etud = etuds[0]
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
return EtudsArchive.get_archived_file(
REQUEST, etud_archive_id, archive_name, filename
)
etud_archive_id = etud["etudid"]
return EtudsArchive.get_archived_file(etud_archive_id, archive_name, filename)
# --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants)
def etudarchive_generate_excel_sample(group_id=None, REQUEST=None):
def etudarchive_generate_excel_sample(group_id=None):
"""Feuille excel pour import fichiers etudiants (utilisé pour admissions)"""
fmt = sco_import_etuds.sco_import_format()
data = sco_import_etuds.sco_import_generate_excel_sample(
@ -271,12 +266,15 @@ def etudarchive_generate_excel_sample(group_id=None, REQUEST=None):
],
extra_cols=["fichier_a_charger"],
)
return sco_excel.send_excel_file(
REQUEST, data, "ImportFichiersEtudiants" + scu.XLSX_SUFFIX
return scu.send_file(
data,
"ImportFichiersEtudiants",
suffix=scu.XLSX_SUFFIX,
mime=scu.XLSX_MIMETYPE,
)
def etudarchive_import_files_form(group_id, REQUEST=None):
def etudarchive_import_files_form(group_id):
"""Formulaire pour importation fichiers d'un groupe"""
H = [
html_sco_header.sco_header(
@ -310,8 +308,8 @@ def etudarchive_import_files_form(group_id, REQUEST=None):
]
F = html_sco_header.sco_footer()
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(
("xlsfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}),
("zipfile", {"title": "Fichier zip:", "input_type": "file", "size": 40}),

View File

@ -28,6 +28,7 @@
"""Génération des bulletins de notes
"""
from app.models import formsemestre
import time
import pprint
import email
@ -35,11 +36,10 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.header import Header
from reportlab.lib.colors import Color
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
import urllib
from flask import g
from flask import g, request
from flask import url_for
from flask_login import current_user
from flask_mail import Message
@ -48,7 +48,7 @@ import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import AccessDenied
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc import html_sco_header
from app.scodoc import htmlutils
from app.scodoc import sco_abs
@ -121,9 +121,7 @@ def make_context_dict(sem, etud):
return C
def formsemestre_bulletinetud_dict(
formsemestre_id, etudid, version="long", REQUEST=None
):
def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
"""Collecte informations pour bulletin de notes
Retourne un dictionnaire (avec valeur par défaut chaine vide).
Le contenu du dictionnaire dépend des options (rangs, ...)
@ -143,10 +141,7 @@ def formsemestre_bulletinetud_dict(
I["etudid"] = etudid
I["formsemestre_id"] = formsemestre_id
I["sem"] = nt.sem
if REQUEST:
I["server_name"] = REQUEST.BASE0
else:
I["server_name"] = ""
I["server_name"] = request.url_root
# Formation et parcours
I["formation"] = sco_formations.formation_list(
@ -771,14 +766,16 @@ def formsemestre_bulletinetud(
xml_with_decisions=False,
force_publishing=False, # force publication meme si semestre non publie sur "portail"
prefer_mail_perso=False,
REQUEST=None,
):
"page bulletin de notes"
try:
etud = sco_etud.get_etud_info(filled=True)[0]
etudid = etud["etudid"]
except:
return scu.log_unknown_etud(REQUEST, format=format)
sco_etud.log_unknown_etud()
raise ScoValueError("étudiant inconnu")
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
bulletin = do_formsemestre_bulletinetud(
formsemestre_id,
@ -788,15 +785,15 @@ def formsemestre_bulletinetud(
xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing,
prefer_mail_perso=prefer_mail_perso,
REQUEST=REQUEST,
)[0]
if format not in {"html", "pdfmail"}:
return bulletin
filename = scu.bul_filename(sem, etud, format)
return scu.send_file(bulletin, filename, mime=scu.get_mime_suffix(format)[0])
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [
_formsemestre_bulletinetud_header_html(
etud, etudid, sem, formsemestre_id, format, version, REQUEST
etud, etudid, sem, formsemestre_id, format, version
),
bulletin,
]
@ -854,7 +851,6 @@ def do_formsemestre_bulletinetud(
etudid,
version="long", # short, long, selectedevals
format="html",
REQUEST=None,
nohtml=False,
xml_with_decisions=False, # force decisions dans XML
force_publishing=False, # force publication meme si semestre non publie sur "portail"
@ -862,14 +858,13 @@ def do_formsemestre_bulletinetud(
):
"""Génère le bulletin au format demandé.
Retourne: (bul, filigranne)
bul est au format demandé (html, pdf, pdfmail, pdfpart, xml)
bul est str ou bytes au format demandé (html, pdf, pdfmail, pdfpart, xml, json)
et filigranne est un message à placer en "filigranne" (eg "Provisoire").
"""
if format == "xml":
bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud(
formsemestre_id,
etudid,
REQUEST=REQUEST,
xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing,
version=version,
@ -881,19 +876,18 @@ def do_formsemestre_bulletinetud(
bul = sco_bulletins_json.make_json_formsemestre_bulletinetud(
formsemestre_id,
etudid,
REQUEST=REQUEST,
xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing,
version=version,
)
return bul, ""
I = formsemestre_bulletinetud_dict(formsemestre_id, etudid, REQUEST=REQUEST)
I = formsemestre_bulletinetud_dict(formsemestre_id, etudid)
etud = I["etud"]
if format == "html":
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud(
I, version=version, format="html", REQUEST=REQUEST
I, version=version, format="html"
)
return htm, I["filigranne"]
@ -903,11 +897,10 @@ def do_formsemestre_bulletinetud(
version=version,
format="pdf",
stand_alone=(format != "pdfpart"),
REQUEST=REQUEST,
)
if format == "pdf":
return (
scu.sendPDFFile(REQUEST, bul, filename),
scu.sendPDFFile(bul, filename),
I["filigranne"],
) # unused ret. value
else:
@ -923,11 +916,11 @@ def do_formsemestre_bulletinetud(
htm = "" # speed up if html version not needed
else:
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud(
I, version=version, format="html", REQUEST=REQUEST
I, version=version, format="html"
)
pdfdata, filename = sco_bulletins_generator.make_formsemestre_bulletinetud(
I, version=version, format="pdf", REQUEST=REQUEST
I, version=version, format="pdf"
)
if prefer_mail_perso:
@ -998,7 +991,6 @@ def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr):
# Attach pdf
msg.attach(filename, scu.PDF_MIMETYPE, pdfdata)
log("mail bulletin a %s" % recipient_addr)
email.send_message(msg)
@ -1010,7 +1002,6 @@ def _formsemestre_bulletinetud_header_html(
formsemestre_id=None,
format=None,
version=None,
REQUEST=None,
):
H = [
html_sco_header.sco_header(
@ -1033,7 +1024,7 @@ def _formsemestre_bulletinetud_header_html(
),
"""
<form name="f" method="GET" action="%s">"""
% REQUEST.URL0,
% request.base_url,
f"""Bulletin <span class="bull_liensemestre"><a href="{
url_for("notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
@ -1063,14 +1054,20 @@ def _formsemestre_bulletinetud_header_html(
H.append("""</select></td>""")
# Menu
endpoint = "notes.formsemestre_bulletinetud"
url = REQUEST.URL0
qurl = six.moves.urllib.parse.quote_plus(url + "?" + REQUEST.QUERY_STRING)
menuBul = [
{
"title": "Réglages bulletins",
"endpoint": "notes.formsemestre_edit_options",
"args": {"formsemestre_id": formsemestre_id, "target_url": qurl},
"args": {
"formsemestre_id": formsemestre_id,
# "target_url": url_for(
# "notes.formsemestre_bulletinetud",
# scodoc_dept=g.scodoc_dept,
# formsemestre_id=formsemestre_id,
# etudid=etudid,
# ),
},
"enabled": (current_user.id in sem["responsables"])
or current_user.has_permission(Permission.ScoImplement),
},
@ -1113,6 +1110,16 @@ def _formsemestre_bulletinetud_header_html(
"enabled": etud["emailperso"]
and can_send_bulletin_by_mail(formsemestre_id),
},
{
"title": "Version json",
"endpoint": endpoint,
"args": {
"formsemestre_id": formsemestre_id,
"etudid": etudid,
"version": version,
"format": "json",
},
},
{
"title": "Version XML",
"endpoint": endpoint,
@ -1188,9 +1195,14 @@ def _formsemestre_bulletinetud_header_html(
H.append(
'<td> <a href="%s">%s</a></td>'
% (
url
+ "?formsemestre_id=%s&etudid=%s&format=pdf&version=%s"
% (formsemestre_id, etudid, version),
url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
etudid=etudid,
format="pdf",
version=version,
),
scu.ICON_PDF,
)
)
@ -1201,9 +1213,7 @@ def _formsemestre_bulletinetud_header_html(
"""
% (
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
sco_photos.etud_photo_html(
etud, title="fiche de " + etud["nom"], REQUEST=REQUEST
),
sco_photos.etud_photo_html(etud, title="fiche de " + etud["nom"]),
)
)
H.append(

View File

@ -52,6 +52,9 @@ import reportlab
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
from flask import request
from flask_login import current_user
from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import NoteProcessError
from app import log
@ -148,14 +151,7 @@ class BulletinGenerator(object):
def get_filename(self):
"""Build a filename to be proposed to the web client"""
sem = sco_formsemestre.get_formsemestre(self.infos["formsemestre_id"])
dt = time.strftime("%Y-%m-%d")
filename = "bul-%s-%s-%s.pdf" % (
sem["titre_num"],
dt,
self.infos["etud"]["nom"],
)
filename = scu.unescape_html(filename).replace(" ", "_").replace("&", "")
return filename
return scu.bul_filename(sem, self.infos["etud"], "pdf")
def generate(self, format="", stand_alone=True):
"""Return bulletin in specified format"""
@ -260,7 +256,6 @@ def make_formsemestre_bulletinetud(
version="long", # short, long, selectedevals
format="pdf", # html, pdf
stand_alone=True,
REQUEST=None,
):
"""Bulletin de notes
@ -286,10 +281,10 @@ def make_formsemestre_bulletinetud(
PDFLOCK.acquire()
bul_generator = gen_class(
infos,
authuser=REQUEST.AUTHENTICATED_USER,
authuser=current_user,
version=version,
filigranne=infos["filigranne"],
server_name=REQUEST.BASE0,
server_name=request.url_root,
)
if format not in bul_generator.supported_formats:
# use standard generator
@ -301,10 +296,10 @@ def make_formsemestre_bulletinetud(
gen_class = bulletin_get_class(bul_class_name)
bul_generator = gen_class(
infos,
authuser=REQUEST.AUTHENTICATED_USER,
authuser=current_user,
version=version,
filigranne=infos["filigranne"],
server_name=REQUEST.BASE0,
server_name=request.url_root,
)
data = bul_generator.generate(format=format, stand_alone=stand_alone)

View File

@ -47,27 +47,22 @@ from app.scodoc import sco_etud
def make_json_formsemestre_bulletinetud(
formsemestre_id,
etudid,
REQUEST=None,
formsemestre_id: int,
etudid: int,
xml_with_decisions=False,
version="long",
force_publishing=False, # force publication meme si semestre non publie sur "portail"
):
) -> str:
"""Renvoie bulletin en chaine JSON"""
d = formsemestre_bulletinetud_published_dict(
formsemestre_id,
etudid,
force_publishing=force_publishing,
REQUEST=REQUEST,
xml_with_decisions=xml_with_decisions,
version=version,
)
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", scu.JSON_MIMETYPE)
return json.dumps(d, cls=scu.ScoDocJSONEncoder)
@ -79,7 +74,6 @@ def formsemestre_bulletinetud_published_dict(
etudid,
force_publishing=False,
xml_nodate=False,
REQUEST=None,
xml_with_decisions=False, # inclue les decisions même si non publiées
version="long",
):

View File

@ -58,7 +58,7 @@ import traceback
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
from flask import g, url_for
from flask import g, url_for, request
import app.scodoc.sco_utils as scu
from app import log
@ -164,7 +164,7 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"):
return sco_pdf.makeParas(text, style, suppress_empty=suppress_empty_pars)
def get_formsemestre_bulletins_pdf(formsemestre_id, REQUEST, version="selectedevals"):
def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
"document pdf et filename"
from app.scodoc import sco_bulletins
@ -184,7 +184,6 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, REQUEST, version="selectedev
etudid,
format="pdfpart",
version=version,
REQUEST=REQUEST,
)
fragments += frag
filigrannes[i] = filigranne
@ -192,8 +191,8 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, REQUEST, version="selectedev
i = i + 1
#
infos = {"DeptName": sco_preferences.get_preference("DeptName", formsemestre_id)}
if REQUEST:
server_name = REQUEST.BASE0
if request:
server_name = request.url_root
else:
server_name = ""
try:
@ -220,7 +219,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, REQUEST, version="selectedev
return pdfdoc, filename
def get_etud_bulletins_pdf(etudid, REQUEST, version="selectedevals"):
def get_etud_bulletins_pdf(etudid, version="selectedevals"):
"Bulletins pdf de tous les semestres de l'étudiant, et filename"
from app.scodoc import sco_bulletins
@ -235,15 +234,14 @@ def get_etud_bulletins_pdf(etudid, REQUEST, version="selectedevals"):
etudid,
format="pdfpart",
version=version,
REQUEST=REQUEST,
)
fragments += frag
filigrannes[i] = filigranne
bookmarks[i] = sem["session_id"] # eg RT-DUT-FI-S1-2015
i = i + 1
infos = {"DeptName": sco_preferences.get_preference("DeptName")}
if REQUEST:
server_name = REQUEST.BASE0
if request:
server_name = request.url_root
else:
server_name = ""
try:

View File

@ -56,7 +56,7 @@ et sur page "réglages bulletin" (avec formsemestre_id)
# import os
# def form_change_bul_sig(side, formsemestre_id=None, REQUEST=None):
# def form_change_bul_sig(side, formsemestre_id=None):
# """Change pdf signature"""
# filename = _get_sig_existing_filename(
# side, formsemestre_id=formsemestre_id
@ -69,7 +69,7 @@ et sur page "réglages bulletin" (avec formsemestre_id)
# raise ValueError("invalid value for 'side' parameter")
# signatureloc = get_bul_sig_img()
# H = [
# self.sco_header(REQUEST, page_title="Changement de signature"),
# self.sco_header(page_title="Changement de signature"),
# """<h2>Changement de la signature bulletin de %(sidetxt)s</h2>
# """
# % (sidetxt,),

View File

@ -69,16 +69,13 @@ def make_xml_formsemestre_bulletinetud(
doc=None, # XML document
force_publishing=False,
xml_nodate=False,
REQUEST=None,
xml_with_decisions=False, # inclue les decisions même si non publiées
version="long",
):
) -> str:
"bulletin au format XML"
from app.scodoc import sco_bulletins
log("xml_bulletin( formsemestre_id=%s, etudid=%s )" % (formsemestre_id, etudid))
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if (not sem["bul_hide_xml"]) or force_publishing:

View File

@ -157,7 +157,7 @@ class EvaluationCache(ScoDocCache):
class AbsSemEtudCache(ScoDocCache):
"""Cache pour les comptes d'absences d'un étudiant dans un semestre.
Ce cache étant indépendant des semestre, le compte peut être faux lorsqu'on
Ce cache étant indépendant des semestres, le compte peut être faux lorsqu'on
change les dates début/fin d'un semestre.
C'est pourquoi il expire après timeout secondes.
Le timeout evite aussi d'éliminer explicitement ces éléments cachés lors

View File

@ -30,6 +30,8 @@
(coût théorique en heures équivalent TD)
"""
from flask import request
import app.scodoc.sco_utils as scu
from app.scodoc.gen_tables import GenTable
from app.scodoc import sco_formsemestre
@ -45,7 +47,6 @@ def formsemestre_table_estim_cost(
n_group_tp=1,
coef_tp=1,
coef_cours=1.5,
REQUEST=None,
):
"""
Rapports estimation coût de formation basé sur le programme pédagogique
@ -156,7 +157,6 @@ def formsemestre_estim_cost(
coef_tp=1,
coef_cours=1.5,
format="html",
REQUEST=None,
):
"""Page (formulaire) estimation coûts"""
@ -171,7 +171,6 @@ def formsemestre_estim_cost(
n_group_tp=n_group_tp,
coef_tp=coef_tp,
coef_cours=coef_cours,
REQUEST=REQUEST,
)
h = """
<form name="f" method="get" action="%s">
@ -182,7 +181,7 @@ def formsemestre_estim_cost(
<br/>
</form>
""" % (
REQUEST.URL0,
request.base_url,
formsemestre_id,
n_group_td,
n_group_tp,
@ -190,11 +189,11 @@ def formsemestre_estim_cost(
)
tab.html_before_table = h
tab.base_url = "%s?formsemestre_id=%s&n_group_td=%s&n_group_tp=%s&coef_tp=%s" % (
REQUEST.URL0,
request.base_url,
formsemestre_id,
n_group_td,
n_group_tp,
coef_tp,
)
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)

View File

@ -29,7 +29,7 @@
Rapport (table) avec dernier semestre fréquenté et débouché de chaque étudiant
"""
import http
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -47,10 +47,10 @@ from app.scodoc import sco_etud
import sco_version
def report_debouche_date(start_year=None, format="html", REQUEST=None):
def report_debouche_date(start_year=None, format="html"):
"""Rapport (table) pour les débouchés des étudiants sortis à partir de l'année indiquée."""
if not start_year:
return report_debouche_ask_date(REQUEST=REQUEST)
return report_debouche_ask_date()
if format == "xls":
keep_numeric = True # pas de conversion des notes en strings
else:
@ -64,13 +64,12 @@ def report_debouche_date(start_year=None, format="html", REQUEST=None):
"Généré par %s le " % sco_version.SCONAME + scu.timedate_human_repr() + ""
)
tab.caption = "Récapitulatif débouchés à partir du 1/1/%s." % start_year
tab.base_url = "%s?start_year=%s" % (REQUEST.URL0, start_year)
tab.base_url = "%s?start_year=%s" % (request.base_url, start_year)
return tab.make_page(
title="""<h2 class="formsemestre">Débouchés étudiants </h2>""",
init_qtip=True,
javascripts=["js/etud_info.js"],
format=format,
REQUEST=REQUEST,
with_html_headers=True,
)
@ -194,7 +193,7 @@ def table_debouche_etudids(etudids, keep_numeric=True):
return tab
def report_debouche_ask_date(REQUEST=None):
def report_debouche_ask_date():
"""Formulaire demande date départ"""
return (
html_sco_header.sco_header()
@ -249,7 +248,7 @@ def itemsuivi_get(cnx, itemsuivi_id, ignore_errors=False):
return None
def itemsuivi_suppress(itemsuivi_id, REQUEST=None):
def itemsuivi_suppress(itemsuivi_id):
"""Suppression d'un item"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
@ -259,9 +258,10 @@ def itemsuivi_suppress(itemsuivi_id, REQUEST=None):
_itemsuivi_delete(cnx, itemsuivi_id)
logdb(cnx, method="itemsuivi_suppress", etudid=item["etudid"])
log("suppressed itemsuivi %s" % (itemsuivi_id,))
return ("", 204)
def itemsuivi_create(etudid, item_date=None, situation="", REQUEST=None, format=None):
def itemsuivi_create(etudid, item_date=None, situation="", format=None):
"""Creation d'un item"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
@ -273,11 +273,11 @@ def itemsuivi_create(etudid, item_date=None, situation="", REQUEST=None, format=
log("created itemsuivi %s for %s" % (itemsuivi_id, etudid))
item = itemsuivi_get(cnx, itemsuivi_id)
if format == "json":
return scu.sendJSON(REQUEST, item)
return scu.sendJSON(item)
return item
def itemsuivi_set_date(itemsuivi_id, item_date, REQUEST=None):
def itemsuivi_set_date(itemsuivi_id, item_date):
"""set item date
item_date is a string dd/mm/yyyy
"""
@ -288,9 +288,10 @@ def itemsuivi_set_date(itemsuivi_id, item_date, REQUEST=None):
item = itemsuivi_get(cnx, itemsuivi_id)
item["item_date"] = item_date
_itemsuivi_edit(cnx, item)
return ("", 204)
def itemsuivi_set_situation(object, value, REQUEST=None):
def itemsuivi_set_situation(object, value):
"""set situation"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
@ -304,14 +305,14 @@ def itemsuivi_set_situation(object, value, REQUEST=None):
return situation or scu.IT_SITUATION_MISSING_STR
def itemsuivi_list_etud(etudid, format=None, REQUEST=None):
def itemsuivi_list_etud(etudid, format=None):
"""Liste des items pour cet étudiant, avec tags"""
cnx = ndb.GetDBConnexion()
items = _itemsuivi_list(cnx, {"etudid": etudid})
for it in items:
it["tags"] = ", ".join(itemsuivi_tag_list(it["itemsuivi_id"]))
if format == "json":
return scu.sendJSON(REQUEST, items)
return scu.sendJSON(items)
return items
@ -328,7 +329,7 @@ def itemsuivi_tag_list(itemsuivi_id):
return [x["title"] for x in r]
def itemsuivi_tag_search(term, REQUEST=None):
def itemsuivi_tag_search(term):
"""List all used tag names (for auto-completion)"""
# restrict charset to avoid injections
if not scu.ALPHANUM_EXP.match(term):
@ -343,10 +344,10 @@ def itemsuivi_tag_search(term, REQUEST=None):
)
data = [x["title"] for x in r]
return scu.sendJSON(REQUEST, data)
return scu.sendJSON(data)
def itemsuivi_tag_set(itemsuivi_id="", taglist=[], REQUEST=None):
def itemsuivi_tag_set(itemsuivi_id="", taglist=None):
"""taglist may either be:
a string with tag names separated by commas ("un;deux")
or a list of strings (["un", "deux"])

View File

@ -28,7 +28,7 @@
"""Page accueil département (liste des semestres, etc)
"""
from flask import g
from flask import g, request
from flask_login import current_user
import app
@ -46,7 +46,7 @@ from app.scodoc import sco_up_to_date
from app.scodoc import sco_users
def index_html(REQUEST=None, showcodes=0, showsemtable=0):
def index_html(showcodes=0, showsemtable=0):
"Page accueil département (liste des semestres)"
showsemtable = int(showsemtable)
H = []
@ -131,7 +131,7 @@ def index_html(REQUEST=None, showcodes=0, showsemtable=0):
if not showsemtable:
H.append(
'<hr/><p><a href="%s?showsemtable=1">Voir tous les semestres</a></p>'
% REQUEST.URL0
% request.base_url
)
H.append(
@ -242,7 +242,7 @@ def _sem_table_gt(sems, showcodes=False):
rows=sems,
html_class="table_leftalign semlist",
html_sortable=True,
# base_url = '%s?formsemestre_id=%s' % (REQUEST.URL0, formsemestre_id),
# base_url = '%s?formsemestre_id=%s' % (request.base_url, formsemestre_id),
# caption='Maquettes enregistrées',
preferences=sco_preferences.SemPreferences(),
)

View File

@ -51,6 +51,7 @@ import fcntl
import subprocess
import requests
from flask_login import current_user
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -64,7 +65,7 @@ from app.scodoc.sco_exceptions import ScoValueError
SCO_DUMP_LOCK = "/tmp/scodump.lock"
def sco_dump_and_send_db(REQUEST=None):
def sco_dump_and_send_db():
"""Dump base de données et l'envoie anonymisée pour debug"""
H = [html_sco_header.sco_header(page_title="Assistance technique")]
# get currect (dept) DB name:
@ -93,7 +94,7 @@ def sco_dump_and_send_db(REQUEST=None):
_anonymize_db(ano_db_name)
# Send
r = _send_db(REQUEST, ano_db_name)
r = _send_db(ano_db_name)
if (
r.status_code
== requests.codes.INSUFFICIENT_STORAGE # pylint: disable=no-member
@ -171,29 +172,27 @@ def _get_scodoc_serial():
return 0
def _send_db(REQUEST, ano_db_name):
def _send_db(ano_db_name):
"""Dump this (anonymized) database and send it to tech support"""
log("dumping anonymized database {}".format(ano_db_name))
log(f"dumping anonymized database {ano_db_name}")
try:
data = subprocess.check_output("pg_dump {} | gzip".format(ano_db_name), shell=1)
except subprocess.CalledProcessError as e:
log("sco_dump_and_send_db: exception in anonymisation: {}".format(e))
raise ScoValueError(
"erreur lors de l'anonymisation de la base {}".format(ano_db_name)
dump = subprocess.check_output(
f"pg_dump --format=custom {ano_db_name}", shell=1
)
except subprocess.CalledProcessError as e:
log(f"sco_dump_and_send_db: exception in anonymisation: {e}")
raise ScoValueError(f"erreur lors de l'anonymisation de la base {ano_db_name}")
log("uploading anonymized dump...")
files = {"file": (ano_db_name + ".gz", data)}
files = {"file": (ano_db_name + ".dump", dump)}
r = requests.post(
scu.SCO_DUMP_UP_URL,
files=files,
data={
"dept_name": sco_preferences.get_preference("DeptName"),
"serial": _get_scodoc_serial(),
"sco_user": str(REQUEST.AUTHENTICATED_USER),
"sent_by": sco_users.user_info(str(REQUEST.AUTHENTICATED_USER))[
"nomcomplet"
],
"sco_user": str(current_user),
"sent_by": sco_users.user_info(str(current_user))["nomcomplet"],
"sco_version": sco_version.SCOVERSION,
"sco_fullversion": scu.get_scodoc_version(),
},

View File

@ -29,7 +29,7 @@
(portage from DTML)
"""
import flask
from flask import g, url_for
from flask import g, url_for, request
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -47,7 +47,7 @@ from app.scodoc import sco_formsemestre
from app.scodoc import sco_news
def formation_delete(formation_id=None, dialog_confirmed=False, REQUEST=None):
def formation_delete(formation_id=None, dialog_confirmed=False):
"""Delete a formation"""
F = sco_formations.formation_list(args={"formation_id": formation_id})
if not F:
@ -119,12 +119,12 @@ def do_formation_delete(oid):
)
def formation_create(REQUEST=None):
def formation_create():
"""Creation d'une formation"""
return formation_edit(create=True, REQUEST=REQUEST)
return formation_edit(create=True)
def formation_edit(formation_id=None, create=False, REQUEST=None):
def formation_edit(formation_id=None, create=False):
"""Edit or create a formation"""
if create:
H = [
@ -159,8 +159,8 @@ def formation_edit(formation_id=None, create=False, REQUEST=None):
)
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(
("formation_id", {"default": formation_id, "input_type": "hidden"}),
(
@ -311,7 +311,7 @@ def invalidate_sems_in_formation(formation_id):
) # > formation modif.
def module_move(module_id, after=0, REQUEST=None, redirect=1):
def module_move(module_id, after=0, redirect=1):
"""Move before/after previous one (decrement/increment numero)"""
module = sco_edit_module.do_module_list({"module_id": module_id})[0]
redirect = int(redirect)

View File

@ -29,7 +29,7 @@
(portage from DTML)
"""
import flask
from flask import g, url_for
from flask import g, url_for, request
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -92,7 +92,7 @@ def do_matiere_create(args):
return r
def matiere_create(ue_id=None, REQUEST=None):
def matiere_create(ue_id=None):
"""Creation d'une matiere"""
from app.scodoc import sco_edit_ue
@ -116,8 +116,8 @@ associé.
</p>""",
]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(
("ue_id", {"input_type": "hidden", "default": ue_id}),
("titre", {"size": 30, "explanation": "nom de la matière."}),
@ -189,7 +189,7 @@ def do_matiere_delete(oid):
)
def matiere_delete(matiere_id=None, REQUEST=None):
def matiere_delete(matiere_id=None):
"""Delete an UE"""
from app.scodoc import sco_edit_ue
@ -202,8 +202,8 @@ def matiere_delete(matiere_id=None, REQUEST=None):
]
dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(UE["formation_id"])
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(("matiere_id", {"input_type": "hidden"}),),
initvalues=M,
submitlabel="Confirmer la suppression",
@ -218,7 +218,7 @@ def matiere_delete(matiere_id=None, REQUEST=None):
return flask.redirect(dest_url)
def matiere_edit(matiere_id=None, REQUEST=None):
def matiere_edit(matiere_id=None):
"""Edit matiere"""
from app.scodoc import sco_formations
from app.scodoc import sco_edit_ue
@ -256,8 +256,8 @@ des notes.</em>
associé.
</p>"""
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(
("matiere_id", {"input_type": "hidden"}),
(
@ -323,4 +323,4 @@ def matiere_is_locked(matiere_id):
""",
{"matiere_id": matiere_id},
)
return len(r) > 0
return len(r) > 0

View File

@ -29,7 +29,8 @@
(portage from DTML)
"""
import flask
from flask import url_for, g
from flask import url_for, g, request
from flask_login import current_user
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -118,7 +119,7 @@ def do_module_create(args) -> int:
return r
def module_create(matiere_id=None, REQUEST=None):
def module_create(matiere_id=None):
"""Creation d'un module"""
from app.scodoc import sco_formations
from app.scodoc import sco_edit_ue
@ -143,8 +144,8 @@ def module_create(matiere_id=None, REQUEST=None):
else:
default_num = 10
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(
(
"code",
@ -258,12 +259,13 @@ def do_module_delete(oid):
# S'il y a des moduleimpls, on ne peut pas detruire le module !
mods = sco_moduleimpl.do_moduleimpl_list(module_id=oid)
if mods:
err_page = scu.confirm_dialog(
message="""<h3>Destruction du module impossible car il est utilisé dans des semestres existants !</h3>""",
helpmsg="""Il faut d'abord supprimer le semestre. Mais il est peut être préférable de laisser ce programme intact et d'en créer une nouvelle version pour la modifier.""",
dest_url="ue_list",
parameters={"formation_id": mod["formation_id"]},
)
err_page = f"""<h3>Destruction du module impossible car il est utilisé dans des semestres existants !</h3>
<p class="help">Il faut d'abord supprimer le semestre. Mais il est peut être préférable de
laisser ce programme intact et d'en créer une nouvelle version pour la modifier.
</p>
<a href="{url_for('notes.ue_list', scodoc_dept=g.scodoc_dept,
formation_id=mod["formation_id"])}">reprendre</a>
"""
raise ScoGenError(err_page)
# delete
cnx = ndb.GetDBConnexion()
@ -279,7 +281,7 @@ def do_module_delete(oid):
)
def module_delete(module_id=None, REQUEST=None):
def module_delete(module_id=None):
"""Delete a module"""
if not module_id:
raise ScoValueError("invalid module !")
@ -294,8 +296,8 @@ def module_delete(module_id=None, REQUEST=None):
dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(Mod["formation_id"])
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(("module_id", {"input_type": "hidden"}),),
initvalues=Mod,
submitlabel="Confirmer la suppression",
@ -337,7 +339,7 @@ def check_module_code_unicity(code, field, formation_id, module_id=None):
return len(Mods) == 0
def module_edit(module_id=None, REQUEST=None):
def module_edit(module_id=None):
"""Edit a module"""
from app.scodoc import sco_formations
from app.scodoc import sco_tag_module
@ -388,8 +390,8 @@ def module_edit(module_id=None, REQUEST=None):
)
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(
(
"code",
@ -513,7 +515,7 @@ def module_edit(module_id=None, REQUEST=None):
# Edition en ligne du code Apogee
def edit_module_set_code_apogee(id=None, value=None, REQUEST=None):
def edit_module_set_code_apogee(id=None, value=None):
"Set UE code apogee"
module_id = id
value = value.strip("-_ \t")
@ -529,7 +531,7 @@ def edit_module_set_code_apogee(id=None, value=None, REQUEST=None):
return value
def module_list(formation_id, REQUEST=None):
def module_list(formation_id):
"""Liste des modules de la formation
(XXX inutile ou a revoir)
"""
@ -544,7 +546,7 @@ def module_list(formation_id, REQUEST=None):
% F,
'<ul class="notes_module_list">',
]
editable = REQUEST.AUTHENTICATED_USER.has_permission(Permission.ScoChangeFormation)
editable = current_user.has_permission(Permission.ScoChangeFormation)
for Mod in do_module_list(args={"formation_id": formation_id}):
H.append('<li class="notes_module_list">%s' % Mod)
@ -582,7 +584,7 @@ def module_count_moduleimpls(module_id):
return len(mods)
def formation_add_malus_modules(formation_id, titre=None, REQUEST=None):
def formation_add_malus_modules(formation_id, titre=None, redirect=True):
"""Création d'un module de "malus" dans chaque UE d'une formation"""
from app.scodoc import sco_edit_ue
@ -598,13 +600,13 @@ def formation_add_malus_modules(formation_id, titre=None, REQUEST=None):
]
)
if nb_mod_malus == 0:
ue_add_malus_module(ue["ue_id"], titre=titre, REQUEST=REQUEST)
ue_add_malus_module(ue["ue_id"], titre=titre)
if REQUEST:
if redirect:
return flask.redirect("ue_list?formation_id=" + str(formation_id))
def ue_add_malus_module(ue_id, titre=None, code=None, REQUEST=None):
def ue_add_malus_module(ue_id, titre=None, code=None):
"""Add a malus module in this ue"""
from app.scodoc import sco_edit_ue

View File

@ -29,9 +29,10 @@
"""
import flask
from flask import g, url_for
from flask import g, url_for, request
from flask_login import current_user
from app.models.formations import NotesUE
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app import log
@ -185,12 +186,12 @@ def do_ue_delete(ue_id, delete_validations=False, force=False):
return None
def ue_create(formation_id=None, REQUEST=None):
def ue_create(formation_id=None):
"""Creation d'une UE"""
return ue_edit(create=True, formation_id=formation_id, REQUEST=REQUEST)
return ue_edit(create=True, formation_id=formation_id)
def ue_edit(ue_id=None, create=False, formation_id=None, REQUEST=None):
def ue_edit(ue_id=None, create=False, formation_id=None):
"""Modification ou creation d'une UE"""
from app.scodoc import sco_formations
@ -326,7 +327,11 @@ def ue_edit(ue_id=None, create=False, formation_id=None, REQUEST=None):
)
)
tf = TrivialFormulator(
REQUEST.URL0, REQUEST.form, fw, initvalues=initvalues, submitlabel=submitlabel
request.base_url,
scu.get_request_args(),
fw,
initvalues=initvalues,
submitlabel=submitlabel,
)
if tf[0] == 0:
X = """<div id="ue_list_code"></div>
@ -846,25 +851,25 @@ def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None):
ue_code = ue["ue_code"]
F = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0]
formation_code = F["formation_code"]
ue_list_all = do_ue_list(args={"ue_code": ue_code})
if ue_id:
# retire les UE d'autres formations:
# log('checking ucode %s formation %s' % (ue_code, formation_code))
ue_list = []
for ue in ue_list_all:
F = sco_formations.formation_list(
args={"formation_id": ue["formation_id"]}
)[0]
if formation_code == F["formation_code"]:
ue_list.append(ue)
# UE du même code, code formation et departement:
q_ues = (
NotesUE.query.filter_by(ue_code=ue_code)
.join(NotesUE.formation, aliased=True)
.filter_by(dept_id=g.scodoc_dept_id, formation_code=formation_code)
)
else:
ue_list = ue_list_all
# Toutes les UE du departement avec ce code:
q_ues = (
NotesUE.query.filter_by(ue_code=ue_code)
.join(NotesUE.formation, aliased=True)
.filter_by(dept_id=g.scodoc_dept_id)
)
if hide_ue_id: # enlève l'ue de depart
ue_list = [ue for ue in ue_list if ue["ue_id"] != hide_ue_id]
q_ues = q_ues.filter(NotesUE.id != hide_ue_id)
if not ue_list:
ues = q_ues.all()
if not ues:
if ue_id:
return """<span class="ue_share">Seule UE avec code %s</span>""" % ue_code
else:
@ -875,18 +880,13 @@ def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None):
else:
H.append('<span class="ue_share">UE avec le code %s:</span>' % ue_code)
H.append("<ul>")
for ue in ue_list:
F = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0]
for ue in ues:
H.append(
'<li>%s (%s) dans <a class="stdlink" href="ue_list?formation_id=%s">%s (%s)</a>, version %s</li>'
% (
ue["acronyme"],
ue["titre"],
F["formation_id"],
F["acronyme"],
F["titre"],
F["version"],
)
f"""<li>{ue.acronyme} ({ue.titre}) dans <a class="stdlink"
href="{url_for("notes.ue_list", scodoc_dept=g.scodoc_dept, formation_id=ue.formation.id)}"
>{ue.formation.acronyme} ({ue.formation.titre})</a>, version {ue.formation.version}
</li>
"""
)
H.append("</ul>")
return "\n".join(H)
@ -956,7 +956,7 @@ def ue_is_locked(ue_id):
# ---- Table recap formation
def formation_table_recap(formation_id, format="html", REQUEST=None):
def formation_table_recap(formation_id, format="html"):
"""Table recapitulant formation."""
from app.scodoc import sco_formations
@ -1033,13 +1033,13 @@ def formation_table_recap(formation_id, format="html", REQUEST=None):
caption=title,
html_caption=title,
html_class="table_leftalign",
base_url="%s?formation_id=%s" % (REQUEST.URL0, formation_id),
base_url="%s?formation_id=%s" % (request.base_url, formation_id),
page_title=title,
html_title="<h2>" + title + "</h2>",
pdf_title=title,
preferences=sco_preferences.SemPreferences(),
)
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
def ue_list_semestre_ids(ue):

View File

@ -123,7 +123,7 @@ def get_edt_transcodage_groups(formsemestre_id):
return edt2sco, sco2edt, msg
def group_edt_json(group_id, start="", end="", REQUEST=None): # actuellement inutilisé
def group_edt_json(group_id, start="", end=""): # actuellement inutilisé
"""EDT complet du semestre, au format JSON
TODO: indiquer un groupe
TODO: utiliser start et end (2 dates au format ISO YYYY-MM-DD)
@ -149,7 +149,7 @@ def group_edt_json(group_id, start="", end="", REQUEST=None): # actuellement in
}
J.append(d)
return scu.sendJSON(REQUEST, J)
return scu.sendJSON(J)
"""XXX
@ -159,9 +159,7 @@ for e in events:
"""
def experimental_calendar(
group_id=None, formsemestre_id=None, REQUEST=None
): # inutilisé
def experimental_calendar(group_id=None, formsemestre_id=None): # inutilisé
"""experimental page"""
return "\n".join(
[

View File

@ -32,11 +32,11 @@
Voir sco_apogee_csv.py pour la structure du fichier Apogée.
Stockage: utilise sco_archive.py
=> /opt/scodoc/var/scodoc/archives/apo_csv/RT/2016-1/2016-07-03-16-12-19/V3ASR.csv
=> /opt/scodoc/var/scodoc/archives/apo_csv/<dept_id>/2016-1/2016-07-03-16-12-19/V3ASR.csv
pour une maquette de l'année scolaire 2016, semestre 1, etape V3ASR
ou bien (à partir de ScoDoc 1678) :
/opt/scodoc/var/scodoc/archives/apo_csv/RT/2016-1/2016-07-03-16-12-19/V3ASR!111.csv
/opt/scodoc/var/scodoc/archives/apo_csv/<dept_id>/2016-1/2016-07-03-16-12-19/V3ASR!111.csv
pour une maquette de l'étape V3ASR version VDI 111.
La version VDI sera ignorée sauf si elle est indiquée dans l'étape du semestre.

View File

@ -32,7 +32,7 @@ import io
from zipfile import ZipFile
import flask
from flask import url_for, g, send_file
from flask import url_for, g, send_file, request
# from werkzeug.utils import send_file
@ -62,7 +62,6 @@ def apo_semset_maq_status(
block_export_res_ues=False,
block_export_res_modules=False,
block_export_res_sdj=True,
REQUEST=None,
):
"""Page statut / tableau de bord"""
if not semset_id:
@ -83,7 +82,7 @@ def apo_semset_maq_status(
prefs = sco_preferences.SemPreferences()
tab_archives = table_apo_csv_list(semset, REQUEST=REQUEST)
tab_archives = table_apo_csv_list(semset)
(
ok_for_export,
@ -250,7 +249,7 @@ def apo_semset_maq_status(
"""<form name="f" method="get" action="%s">
<input type="hidden" name="semset_id" value="%s"></input>
<div><input type="checkbox" name="allow_missing_apo" value="1" onchange="document.f.submit()" """
% (REQUEST.URL0, semset_id)
% (request.base_url, semset_id)
)
if allow_missing_apo:
H.append("checked")
@ -430,7 +429,7 @@ def apo_semset_maq_status(
return "\n".join(H)
def table_apo_csv_list(semset, REQUEST=None):
def table_apo_csv_list(semset):
"""Table des archives (triée par date d'archivage)"""
annee_scolaire = semset["annee_scolaire"]
sem_id = semset["sem_id"]
@ -476,7 +475,7 @@ def table_apo_csv_list(semset, REQUEST=None):
rows=T,
html_class="table_leftalign apo_maq_list",
html_sortable=True,
# base_url = '%s?formsemestre_id=%s' % (REQUEST.URL0, formsemestre_id),
# base_url = '%s?formsemestre_id=%s' % (request.base_url, formsemestre_id),
# caption='Maquettes enregistrées',
preferences=sco_preferences.SemPreferences(),
)
@ -484,7 +483,7 @@ def table_apo_csv_list(semset, REQUEST=None):
return tab
def view_apo_etuds(semset_id, title="", nip_list="", format="html", REQUEST=None):
def view_apo_etuds(semset_id, title="", nip_list="", format="html"):
"""Table des étudiants Apogée par nips
nip_list est une chaine, codes nip séparés par des ,
"""
@ -517,11 +516,10 @@ def view_apo_etuds(semset_id, title="", nip_list="", format="html", REQUEST=None
etuds=list(etuds.values()),
keys=("nip", "etape_apo", "nom", "prenom", "inscriptions_scodoc"),
format=format,
REQUEST=REQUEST,
)
def view_scodoc_etuds(semset_id, title="", nip_list="", format="html", REQUEST=None):
def view_scodoc_etuds(semset_id, title="", nip_list="", format="html"):
"""Table des étudiants ScoDoc par nips ou etudids"""
if not isinstance(nip_list, str):
nip_list = str(nip_list)
@ -541,13 +539,10 @@ def view_scodoc_etuds(semset_id, title="", nip_list="", format="html", REQUEST=N
etuds=etuds,
keys=("code_nip", "nom", "prenom"),
format=format,
REQUEST=REQUEST,
)
def _view_etuds_page(
semset_id, title="", etuds=[], keys=(), format="html", REQUEST=None
):
def _view_etuds_page(semset_id, title="", etuds=[], keys=(), format="html"):
# Tri les étudiants par nom:
if etuds:
etuds.sort(key=lambda x: (x["nom"], x["prenom"]))
@ -578,7 +573,7 @@ def _view_etuds_page(
preferences=sco_preferences.SemPreferences(),
)
if format != "html":
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
H.append(tab.html())
@ -590,9 +585,7 @@ def _view_etuds_page(
return "\n".join(H) + html_sco_header.sco_footer()
def view_apo_csv_store(
semset_id="", csvfile=None, data="", autodetect=False, REQUEST=None
):
def view_apo_csv_store(semset_id="", csvfile=None, data="", autodetect=False):
"""Store CSV data
Le semset identifie l'annee scolaire et le semestre
Si csvfile, lit depuis FILE, sinon utilise data
@ -627,7 +620,7 @@ def view_apo_csv_store(
return flask.redirect("apo_semset_maq_status?semset_id=" + semset_id)
def view_apo_csv_download_and_store(etape_apo="", semset_id="", REQUEST=None):
def view_apo_csv_download_and_store(etape_apo="", semset_id=""):
"""Download maquette and store it"""
if not semset_id:
raise ValueError("invalid null semset_id")
@ -639,12 +632,10 @@ def view_apo_csv_download_and_store(etape_apo="", semset_id="", REQUEST=None):
# here, data is utf8
# but we store and generate latin1 files, to ease further import in Apogée
data = data.decode(APO_PORTAL_ENCODING).encode(APO_INPUT_ENCODING) # XXX #py3
return view_apo_csv_store(semset_id, data=data, autodetect=False, REQUEST=REQUEST)
return view_apo_csv_store(semset_id, data=data, autodetect=False)
def view_apo_csv_delete(
etape_apo="", semset_id="", dialog_confirmed=False, REQUEST=None
):
def view_apo_csv_delete(etape_apo="", semset_id="", dialog_confirmed=False):
"""Delete CSV file"""
if not semset_id:
raise ValueError("invalid null semset_id")
@ -667,7 +658,7 @@ def view_apo_csv_delete(
return flask.redirect(dest_url + "&head_message=Archive%20supprimée")
def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
def view_apo_csv(etape_apo="", semset_id="", format="html"):
"""Visualise une maquette stockée
Si format="raw", renvoie le fichier maquette tel quel
"""
@ -678,7 +669,8 @@ def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
sem_id = semset["sem_id"]
csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id)
if format == "raw":
return scu.sendCSVFile(REQUEST, csv_data, etape_apo + ".txt")
scu.send_file(csv_data, etape_apo, suffix=".txt", mime=scu.CSV_MIMETYPE)
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
(
@ -746,14 +738,15 @@ def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
rows=etuds,
html_sortable=True,
html_class="table_leftalign apo_maq_table",
base_url="%s?etape_apo=%s&semset_id=%s" % (REQUEST.URL0, etape_apo, semset_id),
base_url="%s?etape_apo=%s&semset_id=%s"
% (request.base_url, etape_apo, semset_id),
filename="students_" + etape_apo,
caption="Etudiants Apogée en " + etape_apo,
preferences=sco_preferences.SemPreferences(),
)
if format != "html":
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
H += [
tab.html(),
@ -768,7 +761,7 @@ def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
return "\n".join(H)
# called from Web
# called from Web (GET)
def apo_csv_export_results(
semset_id,
block_export_res_etape=False,

View File

@ -31,27 +31,21 @@
# Ancien module "scolars"
import os
import time
from flask import url_for, g, request
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.header import Header
from email.mime.base import MIMEBase
from operator import itemgetter
from flask import url_for, g, request
from flask_mail import Message
from app import email
from app import log
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import SCO_ENCODING
import app.scodoc.notesdb as ndb
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
from app import log
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc import safehtml
from app.scodoc import sco_preferences
from app.scodoc.scolog import logdb
from flask_mail import Message
from app import mail
from app.scodoc.TrivialFormulator import TrivialFormulator
MONTH_NAMES_ABBREV = [
"Jan ",
@ -256,7 +250,6 @@ _identiteEditor = ndb.EditableTable(
"photo_filename",
"code_ine",
"code_nip",
"scodoc7_id",
),
filter_dept=True,
sortkey="nom",
@ -321,9 +314,7 @@ def check_nom_prenom(cnx, nom="", prenom="", etudid=None):
return True, len(res)
def _check_duplicate_code(
cnx, args, code_name, disable_notify=False, edit=True, REQUEST=None
):
def _check_duplicate_code(cnx, args, code_name, disable_notify=False, edit=True):
etudid = args.get("etudid", None)
if args.get(code_name, None):
etuds = identite_list(cnx, {code_name: str(args[code_name])})
@ -345,31 +336,33 @@ def _check_duplicate_code(
)
if etudid:
OK = "retour à la fiche étudiant"
dest_url = "ficheEtud"
dest_endpoint = "scolar.ficheEtud"
parameters = {"etudid": etudid}
else:
if "tf_submitted" in args:
del args["tf_submitted"]
OK = "Continuer"
dest_url = "etudident_create_form"
dest_endpoint = "scolar.etudident_create_form"
parameters = args
else:
OK = "Annuler"
dest_url = ""
dest_endpoint = "notes.index_html"
parameters = {}
if not disable_notify:
err_page = scu.confirm_dialog(
message="""<h3>Code étudiant (%s) dupliqué !</h3>""" % code_name,
helpmsg="""Le %s %s est déjà utilisé: un seul étudiant peut avoir ce code. Vérifier votre valeur ou supprimer l'autre étudiant avec cette valeur.<p><ul><li>"""
% (code_name, args[code_name])
+ "</li><li>".join(listh)
+ "</li></ul><p>",
OK=OK,
dest_url=dest_url,
parameters=parameters,
)
err_page = f"""<h3><h3>Code étudiant ({code_name}) dupliqué !</h3>
<p class="help">Le {code_name} {args[code_name]} est déjà utilisé: un seul étudiant peut avoir
ce code. Vérifier votre valeur ou supprimer l'autre étudiant avec cette valeur.
</p>
<ul><li>
{ '</li><li>'.join(listh) }
</li></ul>
<p>
<a href="{ url_for(dest_endpoint, scodoc_dept=g.scodoc_dept, **parameters) }
">{OK}</a>
</p>
"""
else:
err_page = """<h3>Code étudiant (%s) dupliqué !</h3>""" % code_name
err_page = f"""<h3>Code étudiant ({code_name}) dupliqué !</h3>"""
log("*** error: code %s duplique: %s" % (code_name, args[code_name]))
raise ScoGenError(err_page)
@ -379,15 +372,15 @@ def _check_civilite(args):
args["civilite"] = input_civilite(civilite) # TODO: A faire valider
def identite_edit(cnx, args, disable_notify=False, REQUEST=None):
def identite_edit(cnx, args, disable_notify=False):
"""Modifie l'identite d'un étudiant.
Si pref notification et difference, envoie message notification, sauf si disable_notify
"""
_check_duplicate_code(
cnx, args, "code_nip", disable_notify=disable_notify, edit=True, REQUEST=REQUEST
cnx, args, "code_nip", disable_notify=disable_notify, edit=True
)
_check_duplicate_code(
cnx, args, "code_ine", disable_notify=disable_notify, edit=True, REQUEST=REQUEST
cnx, args, "code_ine", disable_notify=disable_notify, edit=True
)
notify_to = None
if not disable_notify:
@ -415,10 +408,10 @@ def identite_edit(cnx, args, disable_notify=False, REQUEST=None):
)
def identite_create(cnx, args, REQUEST=None):
def identite_create(cnx, args):
"check unique etudid, then create"
_check_duplicate_code(cnx, args, "code_nip", edit=False, REQUEST=REQUEST)
_check_duplicate_code(cnx, args, "code_ine", edit=False, REQUEST=REQUEST)
_check_duplicate_code(cnx, args, "code_nip", edit=False)
_check_duplicate_code(cnx, args, "code_ine", edit=False)
_check_civilite(args)
if "etudid" in args:
@ -456,7 +449,7 @@ def notify_etud_change(email_addr, etud, before, after, subject):
log("notify_etud_change: sending notification to %s" % email_addr)
log("notify_etud_change: subject: %s" % subject)
log(txt)
mail.send_email(
email.send_email(
subject, sco_preferences.get_preference("email_from_addr"), [email_addr], txt
)
return txt
@ -559,7 +552,6 @@ _admissionEditor = ndb.EditableTable(
"villelycee",
"codepostallycee",
"codelycee",
"debouche",
"type_admission",
"boursier_prec",
),
@ -583,8 +575,8 @@ admission_edit = _admissionEditor.edit
# Edition simultanee de identite et admission
class EtudIdentEditor(object):
def create(self, cnx, args, REQUEST=None):
etudid = identite_create(cnx, args, REQUEST)
def create(self, cnx, args):
etudid = identite_create(cnx, args)
args["etudid"] = etudid
admission_create(cnx, args)
return etudid
@ -615,8 +607,8 @@ class EtudIdentEditor(object):
res.sort(key=itemgetter("nom", "prenom"))
return res
def edit(self, cnx, args, disable_notify=False, REQUEST=None):
identite_edit(cnx, args, disable_notify=disable_notify, REQUEST=REQUEST)
def edit(self, cnx, args, disable_notify=False):
identite_edit(cnx, args, disable_notify=disable_notify)
if "adm_id" in args: # safety net
admission_edit(cnx, args)
@ -656,11 +648,17 @@ def make_etud_args(etudid=None, code_nip=None, use_request=True, raise_exc=True)
return args
def log_unknown_etud():
"""Log request: cas ou getEtudInfo n'a pas ramene de resultat"""
etud_args = make_etud_args(raise_exc=False)
log(f"unknown student: args={etud_args}")
def get_etud_info(etudid=False, code_nip=False, filled=False) -> list:
"""infos sur un etudiant (API). If not foud, returns empty list.
On peut specifier etudid ou code_nip
ou bien cherche dans REQUEST.form: etudid, code_nip, code_ine
(dans cet ordre).
ou bien cherche dans les argumenst de la requête courante:
etudid, code_nip, code_ine (dans cet ordre).
"""
if etudid is None:
return []
@ -673,7 +671,7 @@ def get_etud_info(etudid=False, code_nip=False, filled=False) -> list:
return etud
def create_etud(cnx, args={}, REQUEST=None):
def create_etud(cnx, args={}):
"""Creation d'un étudiant. génère aussi évenement et "news".
Args:
@ -685,7 +683,7 @@ def create_etud(cnx, args={}, REQUEST=None):
from app.scodoc import sco_news
# creation d'un etudiant
etudid = etudident_create(cnx, args, REQUEST=REQUEST)
etudid = etudident_create(cnx, args)
# crée une adresse vide (chaque etudiant doit etre dans la table "adresse" !)
_ = adresse_create(
cnx,

View File

@ -31,7 +31,7 @@ import datetime
import operator
import pprint
import time
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
import urllib
import flask
from flask import url_for
@ -250,7 +250,6 @@ def do_evaluation_create(
publish_incomplete=None,
evaluation_type=None,
numero=None,
REQUEST=None,
**kw, # ceci pour absorber les arguments excedentaires de tf #sco8
):
"""Create an evaluation"""
@ -274,13 +273,13 @@ def do_evaluation_create(
if args["jour"]:
next_eval = None
t = (
ndb.DateDMYtoISO(args["jour"]),
ndb.TimetoISO8601(args["heure_debut"]),
ndb.DateDMYtoISO(args["jour"], null_is_empty=True),
ndb.TimetoISO8601(args["heure_debut"], null_is_empty=True),
)
for e in ModEvals:
if (
ndb.DateDMYtoISO(e["jour"]),
ndb.TimetoISO8601(e["heure_debut"]),
ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
ndb.TimetoISO8601(e["heure_debut"], null_is_empty=True),
) > t:
next_eval = e
break
@ -713,7 +712,7 @@ def do_evaluation_etat_in_mod(nt, moduleimpl_id):
return etat
def formsemestre_evaluations_cal(formsemestre_id, REQUEST=None):
def formsemestre_evaluations_cal(formsemestre_id):
"""Page avec calendrier de toutes les evaluations de ce semestre"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > liste evaluations
@ -780,7 +779,6 @@ def formsemestre_evaluations_cal(formsemestre_id, REQUEST=None):
H = [
html_sco_header.html_sem_header(
REQUEST,
"Evaluations du semestre",
sem,
cssstyles=["css/calabs.css"],
@ -844,9 +842,7 @@ def evaluation_date_first_completion(evaluation_id):
return max(date_premiere_note.values())
def formsemestre_evaluations_delai_correction(
formsemestre_id, format="html", REQUEST=None
):
def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
"""Experimental: un tableau indiquant pour chaque évaluation
le nombre de jours avant la publication des notes.
@ -915,13 +911,13 @@ def formsemestre_evaluations_delai_correction(
html_title="<h2>Correction des évaluations du semestre</h2>",
caption="Correction des évaluations du semestre",
preferences=sco_preferences.SemPreferences(formsemestre_id),
base_url="%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id),
base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id),
origin="Généré par %s le " % sco_version.SCONAME
+ scu.timedate_human_repr()
+ "",
filename=scu.make_filename("evaluations_delais_" + sem["titreannee"]),
)
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
def module_evaluation_insert_before(ModEvals, next_eval):
@ -1089,7 +1085,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
% (
scu.ScoURL(),
group_id,
six.moves.urllib.parse.quote(E["jour"], safe=""),
urllib.parse.quote(E["jour"], safe=""),
)
)
H.append(
@ -1110,7 +1106,6 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
def evaluation_create_form(
moduleimpl_id=None,
evaluation_id=None,
REQUEST=None,
edit=False,
readonly=False,
page_title="Evaluation",
@ -1232,11 +1227,9 @@ def evaluation_create_form(
initvalues["visibulletinlist"] = ["X"]
else:
initvalues["visibulletinlist"] = []
if (
REQUEST.form.get("tf_submitted", False)
and "visibulletinlist" not in REQUEST.form
):
REQUEST.form["visibulletinlist"] = []
vals = scu.get_request_args()
if vals.get("tf_submitted", False) and "visibulletinlist" not in vals:
vals["visibulletinlist"] = []
#
form = [
("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
@ -1346,8 +1339,8 @@ def evaluation_create_form(
),
]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
vals,
form,
cancelbutton="Annuler",
submitlabel=submitlabel,
@ -1369,7 +1362,7 @@ def evaluation_create_form(
tf[2]["visibulletin"] = False
if not edit:
# creation d'une evaluation
evaluation_id = do_evaluation_create(REQUEST=REQUEST, **tf[2])
evaluation_id = do_evaluation_create(**tf[2])
return flask.redirect(dest_url)
else:
do_evaluation_edit(tf[2])

View File

@ -35,11 +35,11 @@ from enum import Enum
from tempfile import NamedTemporaryFile
import openpyxl.utils.datetime
from openpyxl.styles.numbers import FORMAT_NUMBER_00, FORMAT_GENERAL
from openpyxl.comments import Comment
from openpyxl import Workbook, load_workbook
from openpyxl.cell import WriteOnlyCell
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
from openpyxl.styles.numbers import FORMAT_NUMBER_00, FORMAT_GENERAL
from openpyxl.comments import Comment
import app.scodoc.sco_utils as scu
from app.scodoc import notesdb
@ -59,24 +59,9 @@ class COLORS(Enum):
LIGHT_YELLOW = "FFFFFF99"
def send_excel_file(request, data, filename, mime=scu.XLSX_MIMETYPE):
"""publication fichier.
(on ne doit rien avoir émis avant, car ici sont générés les entetes)
"""
filename = (
scu.unescape_html(scu.suppress_accents(filename))
.replace("&", "")
.replace(" ", "_")
)
request.RESPONSE.setHeader("content-type", mime)
request.RESPONSE.setHeader(
"content-disposition", 'attachment; filename="%s"' % filename
)
return data
# Un style est enregistré comme un dictionnaire qui précise la valeur d'un attributdans la liste suivante:
# font, border, number_format, fill, .. (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles)
# font, border, number_format, fill,...
# (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles)
def xldate_as_datetime(xldate, datemode=0):
@ -86,6 +71,17 @@ def xldate_as_datetime(xldate, datemode=0):
return openpyxl.utils.datetime.from_ISO8601(xldate)
def adjust_sheetname(sheet_name):
"""Renvoie un nom convenable pour une feuille excel: < 31 cars, sans caractères spéciaux
Le / n'est pas autorisé par exemple.
Voir https://xlsxwriter.readthedocs.io/workbook.html#add_worksheet
"""
sheet_name = scu.make_filename(sheet_name)
# Le nom de la feuille ne peut faire plus de 31 caractères.
# si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)
return sheet_name[:31]
class ScoExcelBook:
"""Permet la génération d'un classeur xlsx composé de plusieurs feuilles.
usage:
@ -98,13 +94,16 @@ class ScoExcelBook:
def __init__(self):
self.sheets = [] # list of sheets
self.wb = Workbook(write_only=True)
def create_sheet(self, sheet_name="feuille", default_style=None):
"""Crée une nouvelle feuille dans ce classeur
sheet_name -- le nom de la feuille
default_style -- le style par défaut
"""
sheet = ScoExcelSheet(sheet_name, default_style)
sheet_name = adjust_sheetname(sheet_name)
ws = self.wb.create_sheet(sheet_name)
sheet = ScoExcelSheet(sheet_name, default_style, ws)
self.sheets.append(sheet)
return sheet
@ -112,12 +111,12 @@ class ScoExcelBook:
"""génération d'un stream binaire représentant la totalité du classeur.
retourne le flux
"""
wb = Workbook(write_only=True)
for sheet in self.sheets:
sheet.generate(self)
# construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
sheet.prepare()
# construction d'un flux
# (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
with NamedTemporaryFile() as tmp:
wb.save(tmp.name)
self.wb.save(tmp.name)
tmp.seek(0)
return tmp.read()
@ -125,6 +124,7 @@ class ScoExcelBook:
def excel_make_style(
bold=False,
italic=False,
outline=False,
color: COLORS = COLORS.BLACK,
bgcolor: COLORS = None,
halign=None,
@ -145,7 +145,14 @@ def excel_make_style(
size -- taille de police
"""
style = {}
font = Font(name=font_name, bold=bold, italic=italic, color=color.value, size=size)
font = Font(
name=font_name,
bold=bold,
italic=italic,
outline=outline,
color=color.value,
size=size,
)
style["font"] = font
if bgcolor:
style["fill"] = PatternFill(fill_type="solid", fgColor=bgcolor.value)
@ -182,41 +189,93 @@ class ScoExcelSheet:
"""
def __init__(self, sheet_name="feuille", default_style=None, wb=None):
"""Création de la feuille.
sheet_name -- le nom de la feuille
default_style -- le style par défaut des cellules
wb -- le WorkBook dans laquelle se trouve la feuille. Si wb est None (cas d'un classeur mono-feuille),
un workbook est crée et associé à cette feuille.
"""Création de la feuille. sheet_name
-- le nom de la feuille default_style
-- le style par défaut des cellules ws
-- None si la feuille est autonome (dans ce cas ell crée son propre wb), sinon c'est la worksheet
créée par le workbook propriétaire un workbook est crée et associé à cette feuille.
"""
# Le nom de la feuille ne peut faire plus de 31 caractères.
# si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)
self.sheet_name = sheet_name[
:31
] # if len(sheet_name) > 31: sheet_name = 'Feuille' ?
self.rows = [] # list of list of cells
# self.cells_styles_lico = {} # { (li,co) : style }
# self.cells_styles_li = {} # { li : style }
# self.cells_styles_co = {} # { co : style }
self.sheet_name = adjust_sheetname(sheet_name)
if default_style is None:
default_style = excel_make_style()
self.default_style = default_style
self.wb = wb or Workbook(write_only=True) # Création de workbook si nécessaire
self.ws = self.wb.create_sheet(title=self.sheet_name)
if wb is None:
self.wb = Workbook()
self.ws = self.wb.active
self.ws.title = self.sheet_name
else:
self.wb = None
self.ws = wb
# internal data
self.rows = [] # list of list of cells
self.column_dimensions = {}
self.row_dimensions = {}
def set_column_dimension_width(self, cle, value):
"""Détermine la largeur d'une colonne.
cle -- identifie la colonne ("A"n "B", ...)
value -- la dimension (unité : 7 pixels comme affiché dans Excel)
def excel_make_composite_style(
self,
alignment=None,
border=None,
fill=None,
number_format=None,
font=None,
):
style = {}
if font is not None:
style["font"] = font
if alignment is not None:
style["alignment"] = alignment
if border is not None:
style["border"] = border
if fill is not None:
style["fill"] = fill
if number_format is None:
style["number_format"] = FORMAT_GENERAL
else:
style["number_format"] = number_format
return style
@staticmethod
def i2col(idx):
if idx < 26: # one letter key
return chr(idx + 65)
else: # two letters AA..ZZ
first = (idx // 26) + 66
second = (idx % 26) + 65
return "" + chr(first) + chr(second)
def set_column_dimension_width(self, cle=None, value=21):
"""Détermine la largeur d'une colonne. cle -- identifie la colonne ("A" "B", ... ou 0, 1, 2, ...) si None,
value donne la liste des largeurs de colonnes depuis A, B, C, ... value -- la dimension (unité : 7 pixels
comme affiché dans Excel)
"""
self.ws.column_dimensions[cle].width = value
if cle is None:
for i, val in enumerate(value):
self.ws.column_dimensions[self.i2col(i)].width = val
# No keys: value is a list of widths
elif type(cle) == str: # accepts set_column_with("D", ...)
self.ws.column_dimensions[cle].width = value
else:
self.ws.column_dimensions[self.i2col(cle)].width = value
def set_column_dimension_hidden(self, cle, value):
"""Masque ou affiche une colonne.
cle -- identifie la colonne ("A"n "B", ...)
def set_row_dimension_height(self, cle=None, value=21):
"""Détermine la hauteur d'une ligne. cle -- identifie la ligne (1, 2, ...) si None,
value donne la liste des hauteurs de colonnes depuis 1, 2, 3, ... value -- la dimension
"""
if cle is None:
for i, val in enumerate(value, start=1):
self.ws.row_dimensions[i].height = val
# No keys: value is a list of widths
else:
self.ws.row_dimensions[cle].height = value
def set_row_dimension_hidden(self, cle, value):
"""Masque ou affiche une ligne.
cle -- identifie la colonne (1...)
value -- boolean (vrai = colonne cachée)
"""
self.ws.column_dimensions[cle].hidden = value
self.ws.row_dimensions[cle].hidden = value
def make_cell(self, value: any = None, style=None, comment=None):
"""Construit une cellule.
@ -232,8 +291,12 @@ class ScoExcelSheet:
style = self.default_style
if "font" in style:
cell.font = style["font"]
if "alignment" in style:
cell.alignment = style["alignment"]
if "border" in style:
cell.border = style["border"]
if "fill" in style:
cell.fill = style["fill"]
if "number_format" in style:
cell.number_format = style["number_format"]
if "fill" in style:
@ -272,73 +335,31 @@ class ScoExcelSheet:
"""ajoute une ligne déjà construite à la feuille."""
self.rows.append(row)
# def set_style(self, style=None, li=None, co=None):
# if li is not None and co is not None:
# self.cells_styles_lico[(li, co)] = style
# elif li is None:
# self.cells_styles_li[li] = style
# elif co is None:
# self.cells_styles_co[co] = style
#
# def get_cell_style(self, li, co):
# """Get style for specified cell"""
# return (
# self.cells_styles_lico.get((li, co), None)
# or self.cells_styles_li.get(li, None)
# or self.cells_styles_co.get(co, None)
# or self.default_style
# )
def _generate_ws(self):
def prepare(self):
"""génére un flux décrivant la feuille.
Ce flux pourra ensuite être repris dans send_excel_file (classeur mono feille)
ou pour la génération d'un classeur multi-feuilles
"""
for col in self.column_dimensions.keys():
self.ws.column_dimensions[col] = self.column_dimensions[col]
for row in self.column_dimensions.keys():
self.ws.column_dimensions[row] = self.column_dimensions[row]
for row in self.row_dimensions.keys():
self.ws.row_dimensions[row] = self.row_dimensions[row]
for row in self.rows:
self.ws.append(row)
def generate_standalone(self):
def generate(self):
"""génération d'un classeur mono-feuille"""
self._generate_ws()
# this method makes sense only if it is a standalone worksheet (else call workbook.generate()
if self.wb is None: # embeded sheet
raise ScoValueError("can't generate a single sheet from a ScoWorkbook")
# construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
self.prepare()
with NamedTemporaryFile() as tmp:
self.wb.save(tmp.name)
tmp.seek(0)
return tmp.read()
def generate_embeded(self):
"""generation d'une feuille include dans un classeur multi-feuilles"""
self._generate_ws()
def gen_workbook(self, wb=None):
"""TODO: à remplacer"""
"""Generates and returns a workbook from stored data.
If wb, add a sheet (tab) to the existing workbook (in this case, returns None).
"""
if wb is None:
wb = Workbook() # Création du fichier
sauvegarde = True
else:
sauvegarde = False
ws0 = wb.add_sheet(self.sheet_name)
li = 0
for row in self.rows:
co = 0
for c in row:
# safety net: allow only str, int and float
# #py3 #sco8 A revoir lors de la ré-écriture de ce module
# XXX if type(c) not in (IntType, FloatType):
# c = str(c).decode(scu.SCO_ENCODING)
ws0.write(li, co, c, self.get_cell_style(li, co))
co += 1
li += 1
if sauvegarde:
return wb.savetostr()
else:
return None
def excel_simple_table(
titles=None, lines=None, sheet_name=b"feuille", titles_styles=None, comments=None
@ -377,7 +398,7 @@ def excel_simple_table(
cell_style = text_style
cells.append(ws.make_cell(it, cell_style))
ws.append_row(cells)
return ws.generate_standalone()
return ws.generate()
def excel_feuille_saisie(e, titreannee, description, lines):
@ -538,19 +559,35 @@ def excel_feuille_saisie(e, titreannee, description, lines):
ws.make_cell("cellule vide -> note non modifiée", style_expl),
]
)
return ws.generate_standalone()
return ws.generate()
def excel_bytes_to_list(bytes_content):
filelike = io.BytesIO(bytes_content)
return _excel_to_list(filelike)
try:
filelike = io.BytesIO(bytes_content)
return _excel_to_list(filelike)
except:
raise ScoValueError(
"""
scolars_import_excel_file: un contenu xlsx semble corrompu!
peut-être avez vous fourni un fichier au mauvais format (txt, xls, ..)
"""
)
def excel_file_to_list(filename):
return _excel_to_list(filename)
try:
return _excel_to_list(filename)
except:
raise ScoValueError(
"""scolars_import_excel_file: un contenu xlsx
semble corrompu !
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ...)
"""
)
def _excel_to_list(filelike): # we may need 'encoding' argument ?
def _excel_to_list(filelike):
"""returns list of list
convert_to_string is a conversion function applied to all non-string values (ie numbers)
"""
@ -558,7 +595,8 @@ def _excel_to_list(filelike): # we may need 'encoding' argument ?
wb = load_workbook(filename=filelike, read_only=True, data_only=True)
except:
log("Excel_to_list: failure to import document")
open("/tmp/last_scodoc_import_failure" + scu.XLSX_SUFFIX, "wb").write(filelike)
with open("/tmp/last_scodoc_import_failure" + scu.XLSX_SUFFIX, "wb") as f:
f.write(filelike)
raise ScoValueError(
"Fichier illisible: assurez-vous qu'il s'agit bien d'un document Excel !"
)
@ -758,4 +796,4 @@ def excel_feuille_listeappel(
cell_2 = ws.make_cell(("Liste éditée le " + dt), style1i)
ws.append_row([None, cell_2])
return ws.generate_standalone()
return ws.generate()

View File

@ -55,11 +55,9 @@ class InvalidNoteValue(ScoException):
# Exception qui stoque dest_url, utilisee dans Zope standard_error_message
class ScoValueError(ScoException):
def __init__(self, msg, dest_url=None, REQUEST=None):
def __init__(self, msg, dest_url=None):
ScoException.__init__(self, msg)
self.dest_url = dest_url
if REQUEST and dest_url:
REQUEST.set("dest_url", dest_url)
class FormatError(ScoValueError):
@ -79,7 +77,7 @@ class ScoConfigurationError(ScoValueError):
class ScoLockedFormError(ScoException):
def __init__(self, msg="", REQUEST=None):
def __init__(self, msg=""):
msg = (
"Cette formation est verrouillée (car il y a un semestre verrouillé qui s'y réfère). "
+ str(msg)
@ -90,7 +88,7 @@ class ScoLockedFormError(ScoException):
class ScoGenError(ScoException):
"exception avec affichage d'une page explicative ad-hoc"
def __init__(self, msg="", REQUEST=None):
def __init__(self, msg=""):
ScoException.__init__(self, msg)

View File

@ -27,7 +27,7 @@
"""Export d'une table avec les résultats de tous les étudiants
"""
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -216,9 +216,7 @@ def get_set_formsemestre_id_dates(start_date, end_date):
return {x["id"] for x in s}
def scodoc_table_results(
start_date="", end_date="", types_parcours=[], format="html", REQUEST=None
):
def scodoc_table_results(start_date="", end_date="", types_parcours=[], format="html"):
"""Page affichant la table des résultats
Les dates sont en dd/mm/yyyy (datepicker javascript)
types_parcours est la liste des types de parcours à afficher
@ -240,15 +238,13 @@ def scodoc_table_results(
start_date_iso, end_date_iso, types_parcours
)
tab.base_url = "%s?start_date=%s&end_date=%s&types_parcours=%s" % (
REQUEST.URL0,
request.base_url,
start_date,
end_date,
"&types_parcours=".join([str(x) for x in types_parcours]),
)
if format != "html":
return tab.make_page(
format=format, with_html_headers=False, REQUEST=REQUEST
)
return tab.make_page(format=format, with_html_headers=False)
tab_html = tab.html()
nb_rows = tab.get_nb_rows()
else:

View File

@ -136,11 +136,11 @@ def search_etud_in_dept(expnom=""):
vals = {}
url_args = {"scodoc_dept": g.scodoc_dept}
if "dest_url" in request.form:
endpoint = request.form["dest_url"]
if "dest_url" in vals:
endpoint = vals["dest_url"]
else:
endpoint = "scolar.ficheEtud"
if "parameters_keys" in request.form:
if "parameters_keys" in vals:
for key in vals["parameters_keys"].split(","):
url_args[key] = vals[key]
@ -362,7 +362,7 @@ def table_etud_in_accessible_depts(expnom=None):
)
def search_inscr_etud_by_nip(code_nip, REQUEST=None, format="json"):
def search_inscr_etud_by_nip(code_nip, format="json"):
"""Recherche multi-departement d'un étudiant par son code NIP
Seuls les départements accessibles par l'utilisateur sont cherchés.
@ -404,6 +404,4 @@ def search_inscr_etud_by_nip(code_nip, REQUEST=None, format="json"):
)
tab = GenTable(columns_ids=columns_ids, rows=T)
return tab.make_page(
format=format, with_html_headers=False, REQUEST=REQUEST, publish=True
)
return tab.make_page(format=format, with_html_headers=False, publish=True)

View File

@ -31,7 +31,8 @@ from operator import itemgetter
import xml.dom.minidom
import flask
from flask import g, url_for
from flask import g, url_for, request
from flask_login import current_user
import app.scodoc.sco_utils as scu
@ -92,9 +93,7 @@ def formation_has_locked_sems(formation_id):
return sems
def formation_export(
formation_id, export_ids=False, export_tags=True, format=None, REQUEST=None
):
def formation_export(formation_id, export_ids=False, export_tags=True, format=None):
"""Get a formation, with UE, matieres, modules
in desired format
"""
@ -131,9 +130,7 @@ def formation_export(
if mod["ects"] is None:
del mod["ects"]
return scu.sendResult(
REQUEST, F, name="formation", format=format, force_outer_xml_tag=False
)
return scu.sendResult(F, name="formation", format=format, force_outer_xml_tag=False, attached=True)
def formation_import_xml(doc: str, import_tags=True):
@ -162,20 +159,18 @@ def formation_import_xml(doc: str, import_tags=True):
D = sco_xml.xml_to_dicts(f)
assert D[0] == "formation"
F = D[1]
F_quoted = F.copy()
log("F=%s" % F)
ndb.quote_dict(F_quoted)
log("F_quoted=%s" % F_quoted)
# F_quoted = F.copy()
# ndb.quote_dict(F_quoted)
F["dept_id"] = g.scodoc_dept_id
# find new version number
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
log(
"select max(version) from notes_formations where acronyme=%(acronyme)s and titre=%(titre)s"
% F_quoted
)
cursor.execute(
"select max(version) from notes_formations where acronyme=%(acronyme)s and titre=%(titre)s",
F_quoted,
"""SELECT max(version)
FROM notes_formations
WHERE acronyme=%(acronyme)s and titre=%(titre)s and dept_id=%(dept_id)s
""",
F,
)
res = cursor.fetchall()
try:
@ -196,7 +191,7 @@ def formation_import_xml(doc: str, import_tags=True):
assert ue_info[0] == "ue"
ue_info[1]["formation_id"] = formation_id
if "ue_id" in ue_info[1]:
xml_ue_id = ue_info[1]["ue_id"]
xml_ue_id = int(ue_info[1]["ue_id"])
del ue_info[1]["ue_id"]
else:
xml_ue_id = None
@ -212,7 +207,7 @@ def formation_import_xml(doc: str, import_tags=True):
for mod_info in mat_info[2]:
assert mod_info[0] == "module"
if "module_id" in mod_info[1]:
xml_module_id = mod_info[1]["module_id"]
xml_module_id = int(mod_info[1]["module_id"])
del mod_info[1]["module_id"]
else:
xml_module_id = None
@ -230,7 +225,7 @@ def formation_import_xml(doc: str, import_tags=True):
return formation_id, modules_old2new, ues_old2new
def formation_list_table(formation_id=None, args={}, REQUEST=None):
def formation_list_table(formation_id=None, args={}):
"""List formation, grouped by titre and sorted by versions
and listing associated semestres
returns a table
@ -247,7 +242,7 @@ def formation_list_table(formation_id=None, args={}, REQUEST=None):
"edit_img", border="0", alt="modifier", title="Modifier titres et code"
)
editable = REQUEST.AUTHENTICATED_USER.has_permission(Permission.ScoChangeFormation)
editable = current_user.has_permission(Permission.ScoChangeFormation)
# Traduit/ajoute des champs à afficher:
for f in formations:
@ -347,17 +342,18 @@ def formation_list_table(formation_id=None, args={}, REQUEST=None):
html_class="formation_list_table table_leftalign",
html_with_td_classes=True,
html_sortable=True,
base_url="%s?formation_id=%s" % (REQUEST.URL0, formation_id),
base_url="%s?formation_id=%s" % (request.base_url, formation_id),
page_title=title,
pdf_title=title,
preferences=sco_preferences.SemPreferences(),
)
def formation_create_new_version(formation_id, redirect=True, REQUEST=None):
def formation_create_new_version(formation_id, redirect=True):
"duplicate formation, with new version number"
xml = formation_export(formation_id, export_ids=True, format="xml")
new_id, modules_old2new, ues_old2new = formation_import_xml(xml)
resp = formation_export(formation_id, export_ids=True, format="xml")
xml_data = resp.get_data(as_text=True)
new_id, modules_old2new, ues_old2new = formation_import_xml(xml_data)
# news
F = formation_list(args={"formation_id": new_id})[0]
sco_news.add(

View File

@ -31,7 +31,7 @@ from app.scodoc.sco_exceptions import ScoValueError
import time
from operator import itemgetter
from flask import g
from flask import g, request
import app
from app.models import Departement
@ -61,6 +61,7 @@ _formsemestreEditor = ndb.EditableTable(
"gestion_semestrielle",
"etat",
"bul_hide_xml",
"block_moyennes",
"bul_bgcolor",
"modalite",
"resp_can_edit",
@ -68,7 +69,6 @@ _formsemestreEditor = ndb.EditableTable(
"ens_can_edit_eval",
"elt_sem_apo",
"elt_annee_apo",
"scodoc7_id",
),
filter_dept=True,
sortkey="date_debut",
@ -82,6 +82,7 @@ _formsemestreEditor = ndb.EditableTable(
"etat": bool,
"gestion_compensation": bool,
"bul_hide_xml": bool,
"block_moyennes": bool,
"gestion_semestrielle": bool,
"gestion_compensation": bool,
"gestion_semestrielle": bool,
@ -95,9 +96,7 @@ _formsemestreEditor = ndb.EditableTable(
def get_formsemestre(formsemestre_id):
"list ONE formsemestre"
if not isinstance(formsemestre_id, int):
raise ScoValueError(
"""Semestre invalide, reprenez l'opération au départ ou si le problème persiste signalez l'erreur sur scodoc-devel@listes.univ-paris13.fr"""
)
raise ValueError("formsemestre_id must be an integer !")
try:
sem = do_formsemestre_list(args={"formsemestre_id": formsemestre_id})[0]
return sem
@ -565,7 +564,7 @@ def list_formsemestre_by_etape(etape_apo=False, annee_scolaire=False):
return sems
def view_formsemestre_by_etape(etape_apo=None, format="html", REQUEST=None):
def view_formsemestre_by_etape(etape_apo=None, format="html"):
"""Affiche table des semestres correspondants à l'étape"""
if etape_apo:
html_title = (
@ -582,8 +581,8 @@ def view_formsemestre_by_etape(etape_apo=None, format="html", REQUEST=None):
Etape: <input name="etape_apo" type="text" size="8"></input>
</form>""",
)
tab.base_url = "%s?etape_apo=%s" % (REQUEST.URL0, etape_apo or "")
return tab.make_page(format=format, REQUEST=REQUEST)
tab.base_url = "%s?etape_apo=%s" % (request.base_url, etape_apo or "")
return tab.make_page(format=format)
def sem_has_etape(sem, code_etape):

View File

@ -28,7 +28,7 @@
"""Menu "custom" (défini par l'utilisateur) dans les semestres
"""
import flask
from flask import g, url_for
from flask import g, url_for, request
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -77,16 +77,14 @@ def formsemestre_custommenu_html(formsemestre_id):
return htmlutils.make_menu("Liens", menu)
def formsemestre_custommenu_edit(formsemestre_id, REQUEST=None):
def formsemestre_custommenu_edit(formsemestre_id):
"""Dialog to edit the custom menu"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
dest_url = (
scu.NotesURL() + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id
)
H = [
html_sco_header.html_sem_header(
REQUEST, "Modification du menu du semestre ", sem
),
html_sco_header.html_sem_header("Modification du menu du semestre ", sem),
"""<p class="help">Ce menu, spécifique à chaque semestre, peut être utilisé pour placer des liens vers vos applications préférées.</p>
<p class="help">Procédez en plusieurs fois si vous voulez ajouter plusieurs items.</p>""",
]
@ -119,8 +117,8 @@ def formsemestre_custommenu_edit(formsemestre_id, REQUEST=None):
initvalues["title_" + str(item["custommenu_id"])] = item["title"]
initvalues["url_" + str(item["custommenu_id"])] = item["url"]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
descr,
initvalues=initvalues,
cancelbutton="Annuler",

View File

@ -28,7 +28,7 @@
"""Form choix modules / responsables et creation formsemestre
"""
import flask
from flask import url_for, g
from flask import url_for, g, request
from flask_login import current_user
from app.auth.models import User
@ -66,7 +66,7 @@ def _default_sem_title(F):
return F["titre"]
def formsemestre_createwithmodules(REQUEST=None):
def formsemestre_createwithmodules():
"""Page création d'un semestre"""
H = [
html_sco_header.sco_header(
@ -77,7 +77,7 @@ def formsemestre_createwithmodules(REQUEST=None):
),
"""<h2>Mise en place d'un semestre de formation</h2>""",
]
r = do_formsemestre_createwithmodules(REQUEST=REQUEST)
r = do_formsemestre_createwithmodules()
if isinstance(r, str):
H.append(r)
else:
@ -85,13 +85,12 @@ def formsemestre_createwithmodules(REQUEST=None):
return "\n".join(H) + html_sco_header.sco_footer()
def formsemestre_editwithmodules(REQUEST, formsemestre_id):
def formsemestre_editwithmodules(formsemestre_id):
"""Page modification semestre"""
# portage from dtml
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [
html_sco_header.html_sem_header(
REQUEST,
"Modification du semestre",
sem,
javascripts=["libjs/AutoSuggest.js"],
@ -105,12 +104,13 @@ def formsemestre_editwithmodules(REQUEST, formsemestre_id):
% scu.icontag("lock_img", border="0", title="Semestre verrouillé")
)
else:
r = do_formsemestre_createwithmodules(REQUEST=REQUEST, edit=1)
r = do_formsemestre_createwithmodules(edit=1)
if isinstance(r, str):
H.append(r)
else:
return r # response redirect
if not REQUEST.form.get("tf_submitted", False):
vals = scu.get_request_args()
if not vals.get("tf_submitted", False):
H.append(
"""<p class="help">Seuls les modules cochés font partie de ce semestre. Pour les retirer, les décocher et appuyer sur le bouton "modifier".
</p>
@ -121,7 +121,7 @@ def formsemestre_editwithmodules(REQUEST, formsemestre_id):
return "\n".join(H) + html_sco_header.sco_footer()
def can_edit_sem(REQUEST, formsemestre_id="", sem=None):
def can_edit_sem(formsemestre_id="", sem=None):
"""Return sem if user can edit it, False otherwise"""
sem = sem or sco_formsemestre.get_formsemestre(formsemestre_id)
if not current_user.has_permission(Permission.ScoImplement): # pas chef
@ -130,11 +130,12 @@ def can_edit_sem(REQUEST, formsemestre_id="", sem=None):
return sem
def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
def do_formsemestre_createwithmodules(edit=False):
"Form choix modules / responsables et creation formsemestre"
# Fonction accessible à tous, controle acces à la main:
vals = scu.get_request_args()
if edit:
formsemestre_id = int(REQUEST.form["formsemestre_id"])
formsemestre_id = int(vals["formsemestre_id"])
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if not current_user.has_permission(Permission.ScoImplement):
if not edit:
@ -156,14 +157,14 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
uid2display[u.id] = u.get_nomplogin()
allowed_user_names = list(uid2display.values()) + [""]
#
formation_id = int(REQUEST.form["formation_id"])
formation_id = int(vals["formation_id"])
F = sco_formations.formation_list(args={"formation_id": formation_id})
if not F:
raise ScoValueError("Formation inexistante !")
F = F[0]
if not edit:
initvalues = {"titre": _default_sem_title(F)}
semestre_id = int(REQUEST.form["semestre_id"])
semestre_id = int(vals["semestre_id"])
sem_module_ids = set()
else:
# setup form init values
@ -309,7 +310,9 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
{
"size": 40,
"title": "Nom de ce semestre",
"explanation": """n'indiquez pas les dates, ni le semestre, ni la modalité dans le titre: ils seront automatiquement ajoutés <input type="button" value="remettre titre par défaut" onClick="document.tf.titre.value='%s';"/>"""
"explanation": """n'indiquez pas les dates, ni le semestre, ni la modalité dans
le titre: ils seront automatiquement ajoutés <input type="button"
value="remettre titre par défaut" onClick="document.tf.titre.value='%s';"/>"""
% _default_sem_title(F),
},
),
@ -501,6 +504,14 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
"labels": [""],
},
),
(
"block_moyennes",
{
"input_type": "boolcheckbox",
"title": "Bloquer moyennes",
"explanation": "empêcher le calcul des moyennes d'UE et générale.",
},
),
(
"sep",
{
@ -534,7 +545,7 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
select_name = "%s!group_id" % mod["module_id"]
def opt_selected(gid):
if gid == REQUEST.form.get(select_name):
if gid == vals.get(select_name):
return "selected"
else:
return ""
@ -623,38 +634,29 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
initvalues["gestion_compensation_lst"] = ["X"]
else:
initvalues["gestion_compensation_lst"] = []
if (
REQUEST.form.get("tf_submitted", False)
and "gestion_compensation_lst" not in REQUEST.form
):
REQUEST.form["gestion_compensation_lst"] = []
if vals.get("tf_submitted", False) and "gestion_compensation_lst" not in vals:
vals["gestion_compensation_lst"] = []
initvalues["gestion_semestrielle"] = initvalues.get("gestion_semestrielle", False)
if initvalues["gestion_semestrielle"]:
initvalues["gestion_semestrielle_lst"] = ["X"]
else:
initvalues["gestion_semestrielle_lst"] = []
if (
REQUEST.form.get("tf_submitted", False)
and "gestion_semestrielle_lst" not in REQUEST.form
):
REQUEST.form["gestion_semestrielle_lst"] = []
if vals.get("tf_submitted", False) and "gestion_semestrielle_lst" not in vals:
vals["gestion_semestrielle_lst"] = []
initvalues["bul_hide_xml"] = initvalues.get("bul_hide_xml", False)
if not initvalues["bul_hide_xml"]:
initvalues["bul_publish_xml_lst"] = ["X"]
else:
initvalues["bul_publish_xml_lst"] = []
if (
REQUEST.form.get("tf_submitted", False)
and "bul_publish_xml_lst" not in REQUEST.form
):
REQUEST.form["bul_publish_xml_lst"] = []
if vals.get("tf_submitted", False) and "bul_publish_xml_lst" not in vals:
vals["bul_publish_xml_lst"] = []
#
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
vals,
modform,
submitlabel=submitlabel,
cancelbutton="Annuler",
@ -693,7 +695,6 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
tf[2]["bul_hide_xml"] = False
else:
tf[2]["bul_hide_xml"] = True
# remap les identifiants de responsables:
tf[2]["responsable_id"] = User.get_user_id_from_nomplogin(
tf[2]["responsable_id"]
@ -786,7 +787,6 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
moduleimpl_id,
formsemestre_id,
etudids,
REQUEST=REQUEST,
)
msg += [
"inscription de %d étudiants au module %s"
@ -867,7 +867,7 @@ def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
return ok, msg
def formsemestre_clone(formsemestre_id, REQUEST=None):
def formsemestre_clone(formsemestre_id):
"""
Formulaire clonage d'un semestre
"""
@ -888,7 +888,6 @@ def formsemestre_clone(formsemestre_id, REQUEST=None):
H = [
html_sco_header.html_sem_header(
REQUEST,
"Copie du semestre",
sem,
javascripts=["libjs/AutoSuggest.js"],
@ -959,8 +958,8 @@ def formsemestre_clone(formsemestre_id, REQUEST=None):
),
]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
descr,
submitlabel="Dupliquer ce semestre",
cancelbutton="Annuler",
@ -985,7 +984,6 @@ def formsemestre_clone(formsemestre_id, REQUEST=None):
tf[2]["date_fin"],
clone_evaluations=tf[2]["clone_evaluations"],
clone_partitions=tf[2]["clone_partitions"],
REQUEST=REQUEST,
)
return flask.redirect(
"formsemestre_status?formsemestre_id=%s&head_message=Nouveau%%20semestre%%20créé"
@ -1000,7 +998,6 @@ def do_formsemestre_clone(
date_fin, # 'dd/mm/yyyy'
clone_evaluations=False,
clone_partitions=False,
REQUEST=None,
):
"""Clone a semestre: make copy, same modules, same options, same resps, same partitions.
New dates, responsable_id
@ -1040,7 +1037,7 @@ def do_formsemestre_clone(
args = e.copy()
del args["jour"] # erase date
args["moduleimpl_id"] = mid
_ = sco_evaluations.do_evaluation_create(REQUEST=REQUEST, **args)
_ = sco_evaluations.do_evaluation_create(**args)
# 3- copy uecoefs
objs = sco_formsemestre.formsemestre_uecoef_list(
@ -1113,10 +1110,11 @@ def do_formsemestre_clone(
def formsemestre_associate_new_version(
formsemestre_id,
other_formsemestre_ids=[],
REQUEST=None,
dialog_confirmed=False,
):
"""Formulaire changement formation d'un semestre"""
formsemestre_id = int(formsemestre_id)
other_formsemestre_ids = [int(x) for x in other_formsemestre_ids]
if not dialog_confirmed:
# dresse le liste des semestres de la meme formation et version
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
@ -1161,15 +1159,19 @@ def formsemestre_associate_new_version(
)
else:
do_formsemestres_associate_new_version(
[formsemestre_id] + other_formsemestre_ids, REQUEST=REQUEST
[formsemestre_id] + other_formsemestre_ids
)
return flask.redirect(
"formsemestre_status?formsemestre_id=%s&head_message=Formation%%20dupliquée"
% formsemestre_id
url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
head_message="Formation dupliquée",
)
)
def do_formsemestres_associate_new_version(formsemestre_ids, REQUEST=None):
def do_formsemestres_associate_new_version(formsemestre_ids):
"""Cree une nouvelle version de la formation du semestre, et y rattache les semestres.
Tous les moduleimpl sont -associés à la nouvelle formation, ainsi que les decisions de jury
si elles existent (codes d'UE validées).
@ -1179,9 +1181,11 @@ def do_formsemestres_associate_new_version(formsemestre_ids, REQUEST=None):
if not formsemestre_ids:
return
# Check: tous de la même formation
assert isinstance(formsemestre_ids[0], int)
sem = sco_formsemestre.get_formsemestre(formsemestre_ids[0])
formation_id = sem["formation_id"]
for formsemestre_id in formsemestre_ids[1:]:
assert isinstance(formsemestre_id, int)
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if formation_id != sem["formation_id"]:
raise ScoValueError("les semestres ne sont pas tous de la même formation !")
@ -1192,9 +1196,7 @@ def do_formsemestres_associate_new_version(formsemestre_ids, REQUEST=None):
formation_id,
modules_old2new,
ues_old2new,
) = sco_formations.formation_create_new_version(
formation_id, redirect=False, REQUEST=REQUEST
)
) = sco_formations.formation_create_new_version(formation_id, redirect=False)
for formsemestre_id in formsemestre_ids:
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
@ -1230,12 +1232,12 @@ def _reassociate_moduleimpls(cnx, formsemestre_id, ues_old2new, modules_old2new)
sco_parcours_dut.scolar_formsemestre_validation_edit(cnx, e)
def formsemestre_delete(formsemestre_id, REQUEST=None):
def formsemestre_delete(formsemestre_id):
"""Delete a formsemestre (affiche avertissements)"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
H = [
html_sco_header.html_sem_header(REQUEST, "Suppression du semestre", sem),
html_sco_header.html_sem_header("Suppression du semestre", sem),
"""<div class="ue_warning"><span>Attention !</span>
<p class="help">A n'utiliser qu'en cas d'erreur lors de la saisie d'une formation. Normalement,
<b>un semestre ne doit jamais être supprimé</b> (on perd la mémoire des notes et de tous les événements liés à ce semestre !).</p>
@ -1261,8 +1263,8 @@ def formsemestre_delete(formsemestre_id, REQUEST=None):
else:
submit_label = "Confirmer la suppression du semestre"
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(("formsemestre_id", {"input_type": "hidden"}),),
initvalues=F,
submitlabel=submit_label,
@ -1421,7 +1423,7 @@ def do_formsemestre_delete(formsemestre_id):
# ---------------------------------------------------------------------------------------
def formsemestre_edit_options(formsemestre_id, target_url=None, REQUEST=None):
def formsemestre_edit_options(formsemestre_id):
"""dialog to change formsemestre options
(accessible par ScoImplement ou dir. etudes)
"""
@ -1429,12 +1431,10 @@ def formsemestre_edit_options(formsemestre_id, target_url=None, REQUEST=None):
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
if not ok:
return err
return sco_preferences.SemPreferences(formsemestre_id).edit(
REQUEST=REQUEST, categories=["bul"]
)
return sco_preferences.SemPreferences(formsemestre_id).edit(categories=["bul"])
def formsemestre_change_lock(formsemestre_id, REQUEST=None, dialog_confirmed=False):
def formsemestre_change_lock(formsemestre_id) -> None:
"""Change etat (verrouille si ouvert, déverrouille si fermé)
nota: etat (1 ouvert, 0 fermé)
"""
@ -1444,34 +1444,12 @@ def formsemestre_change_lock(formsemestre_id, REQUEST=None, dialog_confirmed=Fal
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
etat = not sem["etat"]
if REQUEST and not dialog_confirmed:
if etat:
msg = "déverrouillage"
else:
msg = "verrouillage"
return scu.confirm_dialog(
"<h2>Confirmer le %s du semestre ?</h2>" % msg,
helpmsg="""Les notes d'un semestre verrouillé ne peuvent plus être modifiées.
Un semestre verrouillé peut cependant être déverrouillé facilement à tout moment
(par son responsable ou un administrateur).
<br/>
Le programme d'une formation qui a un semestre verrouillé ne peut plus être modifié.
""",
dest_url="",
cancel_url="formsemestre_status?formsemestre_id=%s" % formsemestre_id,
parameters={"formsemestre_id": formsemestre_id},
)
args = {"formsemestre_id": formsemestre_id, "etat": etat}
sco_formsemestre.do_formsemestre_edit(args)
if REQUEST:
return flask.redirect(
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
)
def formsemestre_change_publication_bul(
formsemestre_id, REQUEST=None, dialog_confirmed=False
formsemestre_id, dialog_confirmed=False, redirect=True
):
"""Change etat publication bulletins sur portail"""
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
@ -1480,7 +1458,7 @@ def formsemestre_change_publication_bul(
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
etat = not sem["bul_hide_xml"]
if REQUEST and not dialog_confirmed:
if not dialog_confirmed:
if etat:
msg = "non"
else:
@ -1499,14 +1477,14 @@ def formsemestre_change_publication_bul(
args = {"formsemestre_id": formsemestre_id, "bul_hide_xml": etat}
sco_formsemestre.do_formsemestre_edit(args)
if REQUEST:
if redirect:
return flask.redirect(
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
)
return None
def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None, REQUEST=None):
def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
"""Changement manuel des coefficients des UE capitalisées."""
from app.scodoc import notes_table
@ -1538,9 +1516,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None, REQUEST=None):
</p>
"""
H = [
html_sco_header.html_sem_header(
REQUEST, "Coefficients des UE du semestre", sem
),
html_sco_header.html_sem_header("Coefficients des UE du semestre", sem),
help,
]
#
@ -1576,8 +1552,8 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None, REQUEST=None):
form.append(("ue_" + str(ue["ue_id"]), descr))
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
form,
submitlabel="Changer les coefficients",
cancelbutton="Annuler",
@ -1652,9 +1628,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None, REQUEST=None):
formsemestre_id=formsemestre_id
) # > modif coef UE cap (modifs notes de _certains_ etudiants)
header = html_sco_header.html_sem_header(
REQUEST, "Coefficients des UE du semestre", sem
)
header = html_sco_header.html_sem_header("Coefficients des UE du semestre", sem)
return (
header
+ "\n".join(z)

View File

@ -34,7 +34,7 @@ Ces semestres n'auront qu'un seul inscrit !
import time
import flask
from flask import url_for, g
from flask import url_for, g, request
from flask_login import current_user
import app.scodoc.sco_utils as scu
@ -52,7 +52,7 @@ from app.scodoc import sco_parcours_dut
from app.scodoc import sco_etud
def formsemestre_ext_create(etudid, sem_params, REQUEST=None):
def formsemestre_ext_create(etudid, sem_params):
"""Crée un formsemestre exterieur et y inscrit l'étudiant.
sem_params: dict nécessaire à la création du formsemestre
"""
@ -79,7 +79,7 @@ def formsemestre_ext_create(etudid, sem_params, REQUEST=None):
return formsemestre_id
def formsemestre_ext_create_form(etudid, formsemestre_id, REQUEST=None):
def formsemestre_ext_create_form(etudid, formsemestre_id):
"""Formulaire creation/inscription à un semestre extérieur"""
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
H = [
@ -181,8 +181,8 @@ def formsemestre_ext_create_form(etudid, formsemestre_id, REQUEST=None):
]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
descr,
cancelbutton="Annuler",
method="post",
@ -204,13 +204,13 @@ def formsemestre_ext_create_form(etudid, formsemestre_id, REQUEST=None):
)
else:
tf[2]["formation_id"] = orig_sem["formation_id"]
formsemestre_ext_create(etudid, tf[2], REQUEST=REQUEST)
formsemestre_ext_create(etudid, tf[2])
return flask.redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
)
def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid, REQUEST=None):
def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid):
"""Edition des validations d'UE et de semestre (jury)
pour un semestre extérieur.
On peut saisir pour chaque UE du programme de formation
@ -222,8 +222,8 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid, REQUEST=None):
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
ue_list = _list_ue_with_coef_and_validations(sem, etudid)
descr = _ue_form_description(ue_list, REQUEST.form)
if REQUEST and REQUEST.method == "GET":
descr = _ue_form_description(ue_list, scu.get_request_args())
if request.method == "GET":
initvalues = {
"note_" + str(ue["ue_id"]): ue["validation"].get("moy_ue", "")
for ue in ue_list
@ -231,8 +231,8 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid, REQUEST=None):
else:
initvalues = {}
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
descr,
cssclass="tf_ext_edit_ue_validations",
submitlabel="Enregistrer ces validations",
@ -242,19 +242,19 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid, REQUEST=None):
if tf[0] == -1:
return "<h4>annulation</h4>"
else:
H = _make_page(etud, sem, tf, REQUEST=REQUEST)
H = _make_page(etud, sem, tf)
if tf[0] == 0: # premier affichage
return "\n".join(H)
else: # soumission
# simule erreur
ok, message = _check_values(ue_list, tf[2])
if not ok:
H = _make_page(etud, sem, tf, message=message, REQUEST=REQUEST)
H = _make_page(etud, sem, tf, message=message)
return "\n".join(H)
else:
# Submit
_record_ue_validations_and_coefs(
formsemestre_id, etudid, ue_list, tf[2], REQUEST=REQUEST
formsemestre_id, etudid, ue_list, tf[2]
)
return flask.redirect(
"formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s"
@ -262,7 +262,7 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid, REQUEST=None):
)
def _make_page(etud, sem, tf, message="", REQUEST=None):
def _make_page(etud, sem, tf, message=""):
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
moy_gen = nt.get_etud_moy_gen(etud["etudid"])
H = [
@ -465,9 +465,7 @@ def _list_ue_with_coef_and_validations(sem, etudid):
return ue_list
def _record_ue_validations_and_coefs(
formsemestre_id, etudid, ue_list, values, REQUEST=None
):
def _record_ue_validations_and_coefs(formsemestre_id, etudid, ue_list, values):
for ue in ue_list:
code = values.get("valid_" + str(ue["ue_id"]), False)
if code == "None":
@ -492,5 +490,4 @@ def _record_ue_validations_and_coefs(
now_dmy,
code=code,
ue_coefficient=coef,
REQUEST=REQUEST,
)

View File

@ -30,7 +30,7 @@
import time
import flask
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.sco_utils as scu
from app import log
@ -55,6 +55,7 @@ _formsemestre_inscriptionEditor = ndb.EditableTable(
"formsemestre_inscription_id",
("formsemestre_inscription_id", "etudid", "formsemestre_id", "etat", "etape"),
sortkey="formsemestre_id",
insert_ignore_conflicts=True,
)
@ -248,7 +249,7 @@ def do_formsemestre_inscription_with_modules(
def formsemestre_inscription_with_modules_etud(
formsemestre_id, etudid=None, group_ids=None, REQUEST=None
formsemestre_id, etudid=None, group_ids=None
):
"""Form. inscription d'un étudiant au semestre.
Si etudid n'est pas specifié, form. choix etudiant.
@ -263,7 +264,7 @@ def formsemestre_inscription_with_modules_etud(
)
return formsemestre_inscription_with_modules(
etudid, formsemestre_id, REQUEST=REQUEST, group_ids=group_ids
etudid, formsemestre_id, group_ids=group_ids
)
@ -318,7 +319,7 @@ def formsemestre_inscription_with_modules_form(etudid, only_ext=False):
def formsemestre_inscription_with_modules(
etudid, formsemestre_id, group_ids=None, multiple_ok=False, REQUEST=None
etudid, formsemestre_id, group_ids=None, multiple_ok=False
):
"""
Inscription de l'etud dans ce semestre.
@ -334,7 +335,6 @@ def formsemestre_inscription_with_modules(
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
H = [
html_sco_header.html_sem_header(
REQUEST,
"Inscription de %s dans ce semestre" % etud["nomprenom"],
sem,
)
@ -415,7 +415,7 @@ def formsemestre_inscription_with_modules(
<input type="hidden" name="etudid" value="%s">
<input type="hidden" name="formsemestre_id" value="%s">
"""
% (REQUEST.URL0, etudid, formsemestre_id)
% (request.base_url, etudid, formsemestre_id)
)
H.append(sco_groups.form_group_choice(formsemestre_id, allow_none=True))
@ -431,7 +431,7 @@ def formsemestre_inscription_with_modules(
return "\n".join(H) + F
def formsemestre_inscription_option(etudid, formsemestre_id, REQUEST=None):
def formsemestre_inscription_option(etudid, formsemestre_id):
"""Dialogue pour (dés)inscription à des modules optionnels."""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if not sem["etat"]:
@ -468,7 +468,8 @@ def formsemestre_inscription_option(etudid, formsemestre_id, REQUEST=None):
modimpls_by_ue_names[ue_id].append(
"%s %s" % (mod["module"]["code"], mod["module"]["titre"])
)
if not REQUEST.form.get("tf_submitted", False):
vals = scu.get_request_args()
if not vals.get("tf_submitted", False):
# inscrit ?
for ins in inscr:
if ins["moduleimpl_id"] == mod["moduleimpl_id"]:
@ -533,8 +534,8 @@ function chkbx_select(field_id, state) {
"""
)
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
descr,
initvalues,
cancelbutton="Annuler",
@ -659,7 +660,7 @@ function chkbx_select(field_id, state) {
def do_moduleimpl_incription_options(
etudid, modulesimpls_ainscrire, modulesimpls_adesinscrire, REQUEST=None
etudid, modulesimpls_ainscrire, modulesimpls_adesinscrire
):
"""
Effectue l'inscription et la description aux modules optionnels
@ -711,17 +712,16 @@ def do_moduleimpl_incription_options(
oid, formsemestre_id=mod["formsemestre_id"]
)
if REQUEST:
H = [
html_sco_header.sco_header(),
"""<h3>Modifications effectuées</h3>
<p><a class="stdlink" href="%s">
Retour à la fiche étudiant</a></p>
"""
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
html_sco_header.sco_footer(),
]
return "\n".join(H)
H = [
html_sco_header.sco_header(),
"""<h3>Modifications effectuées</h3>
<p><a class="stdlink" href="%s">
Retour à la fiche étudiant</a></p>
"""
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
html_sco_header.sco_footer(),
]
return "\n".join(H)
def est_inscrit_ailleurs(etudid, formsemestre_id):
@ -756,14 +756,13 @@ def list_inscrits_ailleurs(formsemestre_id):
return d
def formsemestre_inscrits_ailleurs(formsemestre_id, REQUEST=None):
def formsemestre_inscrits_ailleurs(formsemestre_id):
"""Page listant les étudiants inscrits dans un autre semestre
dont les dates recouvrent le semestre indiqué.
"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [
html_sco_header.html_sem_header(
REQUEST,
"Inscriptions multiples parmi les étudiants du semestre ",
sem,
)

View File

@ -105,7 +105,7 @@ def _build_menu_stats(formsemestre_id):
"title": "Documents Avis Poursuite Etudes",
"endpoint": "notes.pe_view_sem_recap",
"args": {"formsemestre_id": formsemestre_id},
"enabled": True,
"enabled": current_app.config["TESTING"] or current_app.config["DEBUG"],
},
{
"title": 'Table "débouchés"',
@ -436,7 +436,7 @@ def formsemestre_status_menubar(sem):
return "\n".join(H)
def retreive_formsemestre_from_request():
def retreive_formsemestre_from_request() -> int:
"""Cherche si on a de quoi déduire le semestre affiché à partir des
arguments de la requête:
formsemestre_id ou moduleimpl ou evaluation ou group_id ou partition_id
@ -447,6 +447,7 @@ def retreive_formsemestre_from_request():
args = request.form
else:
return None
formsemestre_id = None
# Search formsemestre
group_ids = args.get("group_ids", [])
if "formsemestre_id" in args:
@ -479,16 +480,17 @@ def retreive_formsemestre_from_request():
elif "partition_id" in args:
partition = sco_groups.get_partition(args["partition_id"])
formsemestre_id = partition["formsemestre_id"]
else:
if not formsemestre_id:
return None # no current formsemestre
return formsemestre_id
return int(formsemestre_id)
# Element HTML decrivant un semestre (barre de menu et infos)
def formsemestre_page_title():
"""Element HTML decrivant un semestre (barre de menu et infos)
Cherche dans REQUEST si un semestre est défini (formsemestre_id ou moduleimpl ou evaluation ou group)
Cherche dans la requete si un semestre est défini (formsemestre_id ou moduleimpl ou evaluation ou group)
"""
formsemestre_id = retreive_formsemestre_from_request()
#
@ -568,7 +570,7 @@ def fill_formsemestre(sem):
# Description du semestre sous forme de table exportable
def formsemestre_description_table(formsemestre_id, REQUEST=None, with_evals=False):
def formsemestre_description_table(formsemestre_id, with_evals=False):
"""Description du semestre sous forme de table exportable
Liste des modules et de leurs coefficients
"""
@ -610,7 +612,7 @@ def formsemestre_description_table(formsemestre_id, REQUEST=None, with_evals=Fal
moduleimpl_id=M["moduleimpl_id"]
)
enseignants = ", ".join(
[sco_users.user_info(m["ens_id"], REQUEST)["nomprenom"] for m in M["ens"]]
[sco_users.user_info(m["ens_id"])["nomprenom"] for m in M["ens"]]
)
l = {
"UE": M["ue"]["acronyme"],
@ -698,41 +700,37 @@ def formsemestre_description_table(formsemestre_id, REQUEST=None, with_evals=Fal
html_caption=title,
html_class="table_leftalign formsemestre_description",
base_url="%s?formsemestre_id=%s&with_evals=%s"
% (REQUEST.URL0, formsemestre_id, with_evals),
% (request.base_url, formsemestre_id, with_evals),
page_title=title,
html_title=html_sco_header.html_sem_header(
REQUEST, "Description du semestre", sem, with_page_header=False
"Description du semestre", sem, with_page_header=False
),
pdf_title=title,
preferences=sco_preferences.SemPreferences(formsemestre_id),
)
def formsemestre_description(
formsemestre_id, format="html", with_evals=False, REQUEST=None
):
def formsemestre_description(formsemestre_id, format="html", with_evals=False):
"""Description du semestre sous forme de table exportable
Liste des modules et de leurs coefficients
"""
with_evals = int(with_evals)
tab = formsemestre_description_table(
formsemestre_id, REQUEST, with_evals=with_evals
)
tab = formsemestre_description_table(formsemestre_id, with_evals=with_evals)
tab.html_before_table = """<form name="f" method="get" action="%s">
<input type="hidden" name="formsemestre_id" value="%s"></input>
<input type="checkbox" name="with_evals" value="1" onchange="document.f.submit()" """ % (
REQUEST.URL0,
request.base_url,
formsemestre_id,
)
if with_evals:
tab.html_before_table += "checked"
tab.html_before_table += ">indiquer les évaluations</input></form>"
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
# genere liste html pour accès aux groupes de ce semestre
def _make_listes_sem(sem, REQUEST=None, with_absences=True):
def _make_listes_sem(sem, with_absences=True):
# construit l'URL "destination"
# (a laquelle on revient apres saisie absences)
destination = url_for(
@ -897,7 +895,7 @@ def html_expr_diagnostic(diagnostics):
return "".join(H)
def formsemestre_status_head(formsemestre_id=None, REQUEST=None, page_title=None):
def formsemestre_status_head(formsemestre_id=None, page_title=None):
"""En-tête HTML des pages "semestre" """
semlist = sco_formsemestre.do_formsemestre_list(
args={"formsemestre_id": formsemestre_id}
@ -912,12 +910,12 @@ def formsemestre_status_head(formsemestre_id=None, REQUEST=None, page_title=None
H = [
html_sco_header.html_sem_header(
REQUEST, page_title, sem, with_page_header=False, with_h2=False
page_title, sem, with_page_header=False, with_h2=False
),
"""<table>
f"""<table>
<tr><td class="fichetitre2">Formation: </td><td>
<a href="Notes/ue_list?formation_id=%(formation_id)s" class="discretelink" title="Formation %(acronyme)s, v%(version)s">%(titre)s</a>"""
% F,
<a href="{url_for('notes.ue_list', scodoc_dept=g.scodoc_dept, formation_id=F['formation_id'])}"
class="discretelink" title="Formation {F['acronyme']}, v{F['version']}">{F['titre']}</a>""",
]
if sem["semestre_id"] >= 0:
H.append(", %s %s" % (parcours.SESSION_NAME, sem["semestre_id"]))
@ -948,10 +946,13 @@ Il y a des notes en attente ! Le classement des étudiants n'a qu'une valeur ind
</td></tr>"""
)
H.append("</table>")
sem_warning = ""
if sem["bul_hide_xml"]:
H.append(
'<p class="fontorange"><em>Bulletins non publiés sur le portail</em></p>'
)
sem_warning += "Bulletins non publiés sur le portail. "
if sem["block_moyennes"]:
sem_warning += "Calcul des moyennes bloqué !"
if sem_warning:
H.append('<p class="fontorange"><em>' + sem_warning + "</em></p>")
if sem["semestre_id"] >= 0 and not sco_formsemestre.sem_une_annee(sem):
H.append(
'<p class="fontorange"><em>Attention: ce semestre couvre plusieurs années scolaires !</em></p>'
@ -962,7 +963,7 @@ Il y a des notes en attente ! Le classement des étudiants n'a qu'une valeur ind
return "".join(H)
def formsemestre_status(formsemestre_id=None, REQUEST=None):
def formsemestre_status(formsemestre_id=None):
"""Tableau de bord semestre HTML"""
# porté du DTML
cnx = ndb.GetDBConnexion()
@ -975,7 +976,7 @@ def formsemestre_status(formsemestre_id=None, REQUEST=None):
# )
prev_ue_id = None
can_edit = sco_formsemestre_edit.can_edit_sem(REQUEST, formsemestre_id, sem=sem)
can_edit = sco_formsemestre_edit.can_edit_sem(formsemestre_id, sem=sem)
H = [
html_sco_header.sco_header(page_title="Semestre %s" % sem["titreannee"]),
@ -1018,11 +1019,9 @@ def formsemestre_status(formsemestre_id=None, REQUEST=None):
ModInscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
moduleimpl_id=M["moduleimpl_id"]
)
mails_enseignants.add(
sco_users.user_info(M["responsable_id"], REQUEST)["email"]
)
mails_enseignants.add(sco_users.user_info(M["responsable_id"])["email"])
mails_enseignants |= set(
[sco_users.user_info(m["ens_id"], REQUEST)["email"] for m in M["ens"]]
[sco_users.user_info(m["ens_id"])["email"] for m in M["ens"]]
)
ue = M["ue"]
if prev_ue_id != ue["ue_id"]:
@ -1147,7 +1146,7 @@ def formsemestre_status(formsemestre_id=None, REQUEST=None):
# --- LISTE DES ETUDIANTS
H += [
'<div id="groupes">',
_make_listes_sem(sem, REQUEST),
_make_listes_sem(sem),
"</div>",
]
# --- Lien mail enseignants:

View File

@ -30,7 +30,7 @@
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error, time, datetime
import flask
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -64,7 +64,6 @@ def formsemestre_validation_etud_form(
desturl=None,
sortcol=None,
readonly=True,
REQUEST=None,
):
nt = sco_cache.NotesTableCache.get(
formsemestre_id
@ -149,9 +148,7 @@ def formsemestre_validation_etud_form(
'</td><td style="text-align: right;"><a href="%s">%s</a></td></tr></table>'
% (
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
sco_photos.etud_photo_html(
etud, title="fiche de %s" % etud["nom"], REQUEST=REQUEST
),
sco_photos.etud_photo_html(etud, title="fiche de %s" % etud["nom"]),
)
)
@ -163,10 +160,11 @@ def formsemestre_validation_etud_form(
if etud_etat != "I":
H.append(
tf_error_message(
"""Impossible de statuer sur cet étudiant:
il est démissionnaire ou défaillant (voir <a href="%s">sa fiche</a>)
f"""Impossible de statuer sur cet étudiant:
il est démissionnaire ou défaillant (voir <a href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
}">sa fiche</a>)
"""
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
)
)
return "\n".join(H + Footer)
@ -178,16 +176,19 @@ def formsemestre_validation_etud_form(
)
if check:
if not desturl:
desturl = (
"formsemestre_recapcomplet?modejury=1&hidemodules=1&hidebac=1&pref_override=0&formsemestre_id="
+ str(formsemestre_id)
desturl = url_for(
"notes.formsemestre_recapcomplet",
scodoc_dept=g.scodoc_dept,
modejury=1,
hidemodules=1,
hidebac=1,
pref_override=0,
formsemestre_id=formsemestre_id,
sortcol=sortcol
or None, # pour refaire tri sorttable du tableau de notes
_anchor="etudid%s" % etudid, # va a la bonne ligne
)
if sortcol:
desturl += (
"&sortcol=" + sortcol
) # pour refaire tri sorttable du tableau de notes
desturl += "#etudid%s" % etudid # va a la bonne ligne
H.append('<ul><li><a href="%s">Continuer</a></li></ul>' % desturl)
H.append(f'<ul><li><a href="{desturl}">Continuer</a></li></ul>')
return "\n".join(H + Footer)
@ -197,8 +198,12 @@ def formsemestre_validation_etud_form(
if nt.etud_has_notes_attente(etudid):
H.append(
tf_error_message(
"""Impossible de statuer sur cet étudiant: il a des notes en attente dans des évaluations de ce semestre (voir <a href="formsemestre_status?formsemestre_id=%s">tableau de bord</a>)"""
% formsemestre_id
f"""Impossible de statuer sur cet étudiant: il a des notes en
attente dans des évaluations de ce semestre (voir <a href="{
url_for( "notes.formsemestre_status",
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
}">tableau de bord</a>)
"""
)
)
return "\n".join(H + Footer)
@ -213,14 +218,24 @@ def formsemestre_validation_etud_form(
if not Se.prev_decision:
H.append(
tf_error_message(
"""Le jury n\'a pas statué sur le semestre précédent ! (<a href="formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s">le faire maintenant</a>)"""
% (Se.prev["formsemestre_id"], etudid)
f"""Le jury n'a pas statué sur le semestre précédent ! (<a href="{
url_for("notes.formsemestre_validation_etud_form",
scodoc_dept=g.scodoc_dept,
formsemestre_id=Se.prev["formsemestre_id"],
etudid=etudid)
}">le faire maintenant</a>)
"""
)
)
if decision_jury:
H.append(
'<a href="formsemestre_validation_suppress_etud?etudid=%s&formsemestre_id=%s" class="stdlink">Supprimer décision existante</a>'
% (etudid, formsemestre_id)
f"""<a href="{
url_for("notes.formsemestre_validation_suppress_etud",
scodoc_dept=g.scodoc_dept,
etudid=etudid, formsemestre_id=formsemestre_id
)
}" class="stdlink">Supprimer décision existante</a>
"""
)
H.append(html_sco_header.sco_footer())
return "\n".join(H)
@ -338,7 +353,6 @@ def formsemestre_validation_etud(
codechoice=None, # required
desturl="",
sortcol=None,
REQUEST=None,
):
"""Enregistre validation"""
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
@ -354,9 +368,9 @@ def formsemestre_validation_etud(
if not selected_choice:
raise ValueError("code choix invalide ! (%s)" % codechoice)
#
Se.valide_decision(selected_choice, REQUEST) # enregistre
Se.valide_decision(selected_choice) # enregistre
return _redirect_valid_choice(
formsemestre_id, etudid, Se, selected_choice, desturl, sortcol, REQUEST
formsemestre_id, etudid, Se, selected_choice, desturl, sortcol
)
@ -369,7 +383,6 @@ def formsemestre_validation_etud_manu(
assidu=False,
desturl="",
sortcol=None,
REQUEST=None,
redirect=True,
):
"""Enregistre validation"""
@ -399,22 +412,20 @@ def formsemestre_validation_etud_manu(
formsemestre_id_utilise_pour_compenser=formsemestre_id_utilise_pour_compenser,
)
#
Se.valide_decision(choice, REQUEST) # enregistre
Se.valide_decision(choice) # enregistre
if redirect:
return _redirect_valid_choice(
formsemestre_id, etudid, Se, choice, desturl, sortcol, REQUEST
formsemestre_id, etudid, Se, choice, desturl, sortcol
)
def _redirect_valid_choice(
formsemestre_id, etudid, Se, choice, desturl, sortcol, REQUEST
):
def _redirect_valid_choice(formsemestre_id, etudid, Se, choice, desturl, sortcol):
adr = "formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&check=1" % (
formsemestre_id,
etudid,
)
if sortcol:
adr += "&sortcol=" + sortcol
adr += "&sortcol=" + str(sortcol)
# if desturl:
# desturl += "&desturl=" + desturl
return flask.redirect(adr)
@ -821,12 +832,12 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None
# -----------
def formsemestre_validation_auto(formsemestre_id, REQUEST):
def formsemestre_validation_auto(formsemestre_id):
"Formulaire saisie automatisee des decisions d'un semestre"
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [
html_sco_header.html_sem_header(
REQUEST, "Saisie automatique des décisions du semestre", sem
"Saisie automatique des décisions du semestre", sem
),
"""
<ul>
@ -851,7 +862,7 @@ def formsemestre_validation_auto(formsemestre_id, REQUEST):
return "\n".join(H)
def do_formsemestre_validation_auto(formsemestre_id, REQUEST):
def do_formsemestre_validation_auto(formsemestre_id):
"Saisie automatisee des decisions d'un semestre"
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
next_semestre_id = sem["semestre_id"] + 1
@ -907,7 +918,6 @@ def do_formsemestre_validation_auto(formsemestre_id, REQUEST):
code_etat=ADM,
devenir="NEXT",
assidu=True,
REQUEST=REQUEST,
redirect=False,
)
nb_valid += 1
@ -972,7 +982,7 @@ def formsemestre_validation_suppress_etud(formsemestre_id, etudid):
) # > suppr. decision jury (peut affecter de plusieurs semestres utilisant UE capitalisée)
def formsemestre_validate_previous_ue(formsemestre_id, etudid, REQUEST=None):
def formsemestre_validate_previous_ue(formsemestre_id, etudid):
"""Form. saisie UE validée hors ScoDoc
(pour étudiants arrivant avec un UE antérieurement validée).
"""
@ -994,9 +1004,7 @@ def formsemestre_validate_previous_ue(formsemestre_id, etudid, REQUEST=None):
'</td><td style="text-align: right;"><a href="%s">%s</a></td></tr></table>'
% (
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
sco_photos.etud_photo_html(
etud, title="fiche de %s" % etud["nom"], REQUEST=REQUEST
),
sco_photos.etud_photo_html(etud, title="fiche de %s" % etud["nom"]),
)
),
"""<p class="help">Utiliser cette page pour enregistrer une UE validée antérieurement,
@ -1017,8 +1025,8 @@ def formsemestre_validate_previous_ue(formsemestre_id, etudid, REQUEST=None):
ue_names = ["Choisir..."] + ["%(acronyme)s %(titre)s" % ue for ue in ues]
ue_ids = [""] + [ue["ue_id"] for ue in ues]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(
("etudid", {"input_type": "hidden"}),
("formsemestre_id", {"input_type": "hidden"}),
@ -1091,7 +1099,6 @@ def formsemestre_validate_previous_ue(formsemestre_id, etudid, REQUEST=None):
tf[2]["moy_ue"],
tf[2]["date"],
semestre_id=semestre_id,
REQUEST=REQUEST,
)
return flask.redirect(
scu.ScoURL()
@ -1109,7 +1116,6 @@ def do_formsemestre_validate_previous_ue(
code=ADM,
semestre_id=None,
ue_coefficient=None,
REQUEST=None,
):
"""Enregistre (ou modifie) validation d'UE (obtenue hors ScoDoc).
Si le coefficient est spécifié, modifie le coefficient de
@ -1165,7 +1171,7 @@ def _invalidate_etud_formation_caches(etudid, formation_id):
) # > modif decision UE (inval tous semestres avec cet etudiant, ok mais conservatif)
def get_etud_ue_cap_html(etudid, formsemestre_id, ue_id, REQUEST=None):
def get_etud_ue_cap_html(etudid, formsemestre_id, ue_id):
"""Ramene bout de HTML pour pouvoir supprimer une validation de cette UE"""
valids = ndb.SimpleDictFetch(
"""SELECT SFV.*
@ -1201,7 +1207,7 @@ def get_etud_ue_cap_html(etudid, formsemestre_id, ue_id, REQUEST=None):
return "\n".join(H)
def etud_ue_suppress_validation(etudid, formsemestre_id, ue_id, REQUEST=None):
def etud_ue_suppress_validation(etudid, formsemestre_id, ue_id):
"""Suppress a validation (ue_id, etudid) and redirect to formsemestre"""
log("etud_ue_suppress_validation( %s, %s, %s)" % (etudid, formsemestre_id, ue_id))
cnx = ndb.GetDBConnexion()

View File

@ -42,8 +42,8 @@ from xml.etree import ElementTree
from xml.etree.ElementTree import Element
import flask
from flask import g
from flask import url_for
from flask import g, request
from flask import url_for, make_response
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -468,7 +468,7 @@ def get_etud_groups_in_partition(partition_id):
return R
def formsemestre_partition_list(formsemestre_id, format="xml", REQUEST=None):
def formsemestre_partition_list(formsemestre_id, format="xml"):
"""Get partitions and groups in this semestre
Supported formats: xml, json
"""
@ -476,11 +476,11 @@ def formsemestre_partition_list(formsemestre_id, format="xml", REQUEST=None):
# Ajoute les groupes
for p in partitions:
p["group"] = get_partition_groups(p)
return scu.sendResult(REQUEST, partitions, name="partition", format=format)
return scu.sendResult(partitions, name="partition", format=format)
# Encore utilisé par groupmgr.js
def XMLgetGroupsInPartition(partition_id, REQUEST=None): # was XMLgetGroupesTD
def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD
"""
Deprecated: use group_list
Liste des étudiants dans chaque groupe de cette partition.
@ -499,8 +499,7 @@ def XMLgetGroupsInPartition(partition_id, REQUEST=None): # was XMLgetGroupesTD
groups = get_partition_groups(partition)
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > inscrdict
etuds_set = set(nt.inscrdict)
# XML response:
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
# Build XML:
doc = Element("ajax-response")
x_response = Element("response", type="object", id="MyUpdater")
doc.append(x_response)
@ -552,7 +551,11 @@ def XMLgetGroupsInPartition(partition_id, REQUEST=None): # was XMLgetGroupesTD
)
)
log("XMLgetGroupsInPartition: %s seconds" % (time.time() - t0))
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
# XML response:
data = sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
response = make_response(data)
response.headers["Content-Type"] = scu.XML_MIMETYPE
return response
def comp_origin(etud, cur_sem):
@ -652,7 +655,6 @@ def setGroups(
groupsLists="", # members of each existing group
groupsToCreate="", # name and members of new groups
groupsToDelete="", # groups to delete
REQUEST=None,
):
"""Affect groups (Ajax request)
groupsLists: lignes de la forme "group_id;etudid;...\n"
@ -716,7 +718,7 @@ def setGroups(
# Supprime les groupes indiqués comme supprimés:
for group_id in groupsToDelete:
suppressGroup(group_id, partition_id=partition_id, REQUEST=REQUEST)
suppressGroup(group_id, partition_id=partition_id)
# Crée les nouveaux groupes
for line in groupsToCreate.split("\n"): # for each group_name (one per line)
@ -733,10 +735,12 @@ def setGroups(
for etudid in fs[1:-1]:
change_etud_group_in_partition(etudid, group_id, partition)
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
return (
data = (
'<?xml version="1.0" encoding="utf-8"?><response>Groupes enregistrés</response>'
)
response = make_response(data)
response.headers["Content-Type"] = scu.XML_MIMETYPE
return response
def createGroup(partition_id, group_name="", default=False):
@ -764,7 +768,7 @@ def createGroup(partition_id, group_name="", default=False):
return group_id
def suppressGroup(group_id, partition_id=None, REQUEST=None):
def suppressGroup(group_id, partition_id=None):
"""form suppression d'un groupe.
(ne desinscrit pas les etudiants, change juste leur
affectation aux groupes)
@ -840,7 +844,7 @@ def getArrowIconsTags():
return arrow_up, arrow_down, arrow_none
def editPartitionForm(formsemestre_id=None, REQUEST=None):
def editPartitionForm(formsemestre_id=None):
"""Form to create/suppress partitions"""
# ad-hoc form
if not sco_permissions_check.can_change_groups(formsemestre_id):
@ -968,7 +972,7 @@ def editPartitionForm(formsemestre_id=None, REQUEST=None):
return "\n".join(H) + html_sco_header.sco_footer()
def partition_set_attr(partition_id, attr, value, REQUEST=None):
def partition_set_attr(partition_id, attr, value):
"""Set partition attribute: bul_show_rank or show_in_lists"""
if attr not in {"bul_show_rank", "show_in_lists"}:
raise ValueError("invalid partition attribute: %s" % attr)
@ -991,9 +995,7 @@ def partition_set_attr(partition_id, attr, value, REQUEST=None):
return "enregistré"
def partition_delete(
partition_id, REQUEST=None, force=False, redirect=1, dialog_confirmed=False
):
def partition_delete(partition_id, force=False, redirect=1, dialog_confirmed=False):
"""Suppress a partition (and all groups within).
default partition cannot be suppressed (unless force)"""
partition = get_partition(partition_id)
@ -1036,7 +1038,7 @@ def partition_delete(
)
def partition_move(partition_id, after=0, REQUEST=None, redirect=1):
def partition_move(partition_id, after=0, redirect=1):
"""Move before/after previous one (decrement/increment numero)"""
partition = get_partition(partition_id)
formsemestre_id = partition["formsemestre_id"]
@ -1071,7 +1073,7 @@ def partition_move(partition_id, after=0, REQUEST=None, redirect=1):
)
def partition_rename(partition_id, REQUEST=None):
def partition_rename(partition_id):
"""Form to rename a partition"""
partition = get_partition(partition_id)
formsemestre_id = partition["formsemestre_id"]
@ -1079,8 +1081,8 @@ def partition_rename(partition_id, REQUEST=None):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
H = ["<h2>Renommer une partition</h2>"]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(
("partition_id", {"default": partition_id, "input_type": "hidden"}),
(
@ -1110,12 +1112,10 @@ def partition_rename(partition_id, REQUEST=None):
)
else:
# form submission
return partition_set_name(
partition_id, tf[2]["partition_name"], REQUEST=REQUEST, redirect=1
)
return partition_set_name(partition_id, tf[2]["partition_name"])
def partition_set_name(partition_id, partition_name, REQUEST=None, redirect=1):
def partition_set_name(partition_id, partition_name, redirect=1):
"""Set partition name"""
partition_name = partition_name.strip()
if not partition_name:
@ -1153,7 +1153,7 @@ def partition_set_name(partition_id, partition_name, REQUEST=None, redirect=1):
)
def group_set_name(group_id, group_name, REQUEST=None, redirect=1):
def group_set_name(group_id, group_name, redirect=1):
"""Set group name"""
if group_name:
group_name = group_name.strip()
@ -1180,7 +1180,7 @@ def group_set_name(group_id, group_name, REQUEST=None, redirect=1):
)
def group_rename(group_id, REQUEST=None):
def group_rename(group_id):
"""Form to rename a group"""
group = get_group(group_id)
formsemestre_id = group["formsemestre_id"]
@ -1188,8 +1188,8 @@ def group_rename(group_id, REQUEST=None):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
H = ["<h2>Renommer un groupe de %s</h2>" % group["partition_name"]]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(
("group_id", {"default": group_id, "input_type": "hidden"}),
(
@ -1223,12 +1223,10 @@ def group_rename(group_id, REQUEST=None):
)
else:
# form submission
return group_set_name(
group_id, tf[2]["group_name"], REQUEST=REQUEST, redirect=1
)
return group_set_name(group_id, tf[2]["group_name"], redirect=1)
def groups_auto_repartition(partition_id=None, REQUEST=None):
def groups_auto_repartition(partition_id=None):
"""Reparti les etudiants dans des groupes dans une partition, en respectant le niveau
et la mixité.
"""
@ -1268,8 +1266,8 @@ def groups_auto_repartition(partition_id=None, REQUEST=None):
]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
descr,
{},
cancelbutton="Annuler",
@ -1299,7 +1297,7 @@ def groups_auto_repartition(partition_id=None, REQUEST=None):
# checkGroupName(group_name)
# except:
# H.append('<p class="warning">Nom de groupe invalide: %s</p>'%group_name)
# return '\n'.join(H) + tf[1] + html_sco_header.sco_footer( REQUEST)
# return '\n'.join(H) + tf[1] + html_sco_header.sco_footer()
group_ids.append(createGroup(partition_id, group_name))
#
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > identdict
@ -1499,7 +1497,7 @@ def _sortgroups(groups):
# Tri: place 'all' en tête, puis groupe par partition / nom de groupe
R = [g for g in groups if g["partition_name"] is None]
o = [g for g in groups if g["partition_name"] != None]
o.sort(key=lambda x: (x["numero"], x["group_name"]))
o.sort(key=lambda x: (x["numero"] or 0, x["group_name"]))
return R + o

View File

@ -33,7 +33,7 @@ from app.scodoc import sco_groups
from app.scodoc.sco_exceptions import AccessDenied
def affectGroups(partition_id, REQUEST=None):
def affectGroups(partition_id):
"""Formulaire affectation des etudiants aux groupes de la partition.
Permet aussi la creation et la suppression de groupes.
"""

File diff suppressed because it is too large Load Diff

View File

@ -219,21 +219,20 @@ def sco_import_generate_excel_sample(
def students_import_excel(
csvfile,
REQUEST=None,
formsemestre_id=None,
check_homonyms=True,
require_ine=False,
return_html=True,
):
"import students from Excel file"
diag = scolars_import_excel_file(
csvfile,
REQUEST,
formsemestre_id=formsemestre_id,
check_homonyms=check_homonyms,
require_ine=require_ine,
exclude_cols=["photo_filename"],
)
if REQUEST:
if return_html:
if formsemestre_id:
dest = url_for(
"notes.formsemestre_status",
@ -254,7 +253,6 @@ def students_import_excel(
def scolars_import_excel_file(
datafile,
REQUEST,
formsemestre_id=None,
check_homonyms=True,
require_ine=False,
@ -419,7 +417,6 @@ def scolars_import_excel_file(
formsemestre_to_invalidate.add(
_import_one_student(
cnx,
REQUEST,
formsemestre_id,
values,
GroupIdInferers,
@ -492,16 +489,15 @@ def scolars_import_excel_file(
def students_import_admission(
csvfile, type_admission="", REQUEST=None, formsemestre_id=None
csvfile, type_admission="", formsemestre_id=None, return_html=True
):
"import donnees admission from Excel file (v2016)"
diag = scolars_import_admission(
csvfile,
REQUEST,
formsemestre_id=formsemestre_id,
type_admission=type_admission,
)
if REQUEST:
if return_html:
H = [html_sco_header.sco_header(page_title="Import données admissions")]
H.append("<p>Import terminé !</p>")
H.append(
@ -520,7 +516,6 @@ def students_import_admission(
def _import_one_student(
cnx,
REQUEST,
formsemestre_id,
values,
GroupIdInferers,
@ -538,7 +533,7 @@ def _import_one_student(
)
# Identite
args = values.copy()
etudid = sco_etud.identite_create(cnx, args, REQUEST=REQUEST)
etudid = sco_etud.identite_create(cnx, args)
created_etudids.append(etudid)
# Admissions
args["etudid"] = etudid
@ -587,9 +582,7 @@ def _is_new_ine(cnx, code_ine):
# ------ Fonction ré-écrite en nov 2016 pour lire des fichiers sans etudid (fichiers APB)
def scolars_import_admission(
datafile, REQUEST, formsemestre_id=None, type_admission=None
):
def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None):
"""Importe données admission depuis un fichier Excel quelconque
par exemple ceux utilisés avec APB

View File

@ -31,7 +31,7 @@
import datetime
from operator import itemgetter
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -81,6 +81,7 @@ def list_authorized_etuds_by_sem(sem, delai=274):
"title": src["titreannee"],
"title_target": "formsemestre_status?formsemestre_id=%s"
% src["formsemestre_id"],
"filename": "etud_autorises",
},
}
# ajoute attribut inscrit qui indique si l'étudiant est déjà inscrit dans le semestre dest.
@ -99,6 +100,7 @@ def list_authorized_etuds_by_sem(sem, delai=274):
% sem["formsemestre_id"],
"comment": " actuellement inscrits dans ce semestre",
"help": "Ces étudiants sont actuellement inscrits dans ce semestre. Si vous les décochez, il seront désinscrits.",
"filename": "etud_inscrits",
},
}
@ -146,16 +148,15 @@ def list_inscrits_date(sem):
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
cursor.execute(
"""SELECT I.etudid
FROM
notes_formsemestre_inscription ins,
notes_formsemestre S,
identite i
"""SELECT ins.etudid
FROM
notes_formsemestre_inscription ins,
notes_formsemestre S
WHERE ins.formsemestre_id = S.id
AND S.id != %(formsemestre_id)s
AND S.date_debut <= %(date_debut_iso)s
AND S.date_fin >= %(date_debut_iso)s
AND ins.dept_id = %(dept_id)
AND S.dept_id = %(dept_id)s
""",
sem,
)
@ -264,7 +265,6 @@ def formsemestre_inscr_passage(
inscrit_groupes=False,
submitted=False,
dialog_confirmed=False,
REQUEST=None,
):
"""Form. pour inscription des etudiants d'un semestre dans un autre
(donné par formsemestre_id).
@ -287,9 +287,11 @@ def formsemestre_inscr_passage(
header = html_sco_header.sco_header(page_title="Passage des étudiants")
footer = html_sco_header.sco_footer()
H = [header]
if type(etuds) == type(""):
if isinstance(etuds, str):
etuds = etuds.split(",") # vient du form de confirmation
elif isinstance(etuds, int):
etuds = [etuds]
etuds = [int(x) for x in etuds]
auth_etuds_by_sem, inscrits, candidats = list_authorized_etuds_by_sem(sem)
etuds_set = set(etuds)
candidats_set = set(candidats)
@ -312,7 +314,6 @@ def formsemestre_inscr_passage(
if not submitted:
H += build_page(
REQUEST,
sem,
auth_etuds_by_sem,
inscrits,
@ -342,18 +343,22 @@ def formsemestre_inscr_passage(
% inscrits[etudid]
)
H.append("</ol>")
if not a_inscrire and not a_desinscrire:
todo = a_inscrire or a_desinscrire
if not todo:
H.append("""<h3>Il n'y a rien à modifier !</h3>""")
H.append(
scu.confirm_dialog(
dest_url="formsemestre_inscr_passage",
dest_url="formsemestre_inscr_passage"
if todo
else "formsemestre_status",
message="<p>Confirmer ?</p>" if todo else "",
add_headers=False,
cancel_url="formsemestre_inscr_passage?formsemestre_id="
+ str(formsemestre_id),
OK="Effectuer l'opération",
OK="Effectuer l'opération" if todo else "",
parameters={
"formsemestre_id": formsemestre_id,
"etuds": ",".join(etuds),
"etuds": ",".join([str(x) for x in etuds]),
"inscrit_groupes": inscrit_groupes,
"submitted": 1,
},
@ -397,7 +402,6 @@ def formsemestre_inscr_passage(
def build_page(
REQUEST,
sem,
auth_etuds_by_sem,
inscrits,
@ -413,9 +417,9 @@ def build_page(
H = [
html_sco_header.html_sem_header(
REQUEST, "Passages dans le semestre", sem, with_page_header=False
"Passages dans le semestre", sem, with_page_header=False
),
"""<form method="post" action="%s">""" % REQUEST.URL0,
"""<form method="post" action="%s">""" % request.base_url,
"""<input type="hidden" name="formsemestre_id" value="%(formsemestre_id)s"/>
<input type="submit" name="submitted" value="Appliquer les modifications"/>
&nbsp;<a href="#help">aide</a>
@ -507,7 +511,12 @@ def etuds_select_boxes(
</script>
<div class="etuds_select_boxes">"""
] # "
# Élimine les boites vides:
auth_etuds_by_cat = {
k: auth_etuds_by_cat[k]
for k in auth_etuds_by_cat
if auth_etuds_by_cat[k]["etuds"]
}
for src_cat in auth_etuds_by_cat.keys():
infos = auth_etuds_by_cat[src_cat]["infos"]
infos["comment"] = infos.get("comment", "") # commentaire dans sous-titre boite
@ -550,10 +559,8 @@ def etuds_select_boxes(
if with_checkbox or sel_inscrits:
H.append(")")
if base_url and etuds:
H.append(
'<a href="%s&export_cat_xls=%s">%s</a>&nbsp;'
% (base_url, src_cat, scu.ICON_XLS)
)
url = scu.build_url_query(base_url, export_cat_xls=src_cat)
H.append(f'<a href="{url}">{scu.ICON_XLS}</a>&nbsp;')
H.append("</div>")
for etud in etuds:
if etud.get("inscrit", False):
@ -633,4 +640,4 @@ def etuds_select_box_xls(src_cat):
caption="%(title)s. %(help)s" % src_cat["infos"],
preferences=sco_preferences.SemPreferences(),
)
return tab.excel()
return tab.excel() # tab.make_page(filename=src_cat["infos"]["filename"])

View File

@ -27,11 +27,11 @@
"""Liste des notes d'une évaluation
"""
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
from operator import itemgetter
import urllib
import flask
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -56,7 +56,7 @@ from app.scodoc.gen_tables import GenTable
from app.scodoc.htmlutils import histogram_notes
def do_evaluation_listenotes(REQUEST):
def do_evaluation_listenotes():
"""
Affichage des notes d'une évaluation
@ -64,12 +64,13 @@ def do_evaluation_listenotes(REQUEST):
(si moduleimpl_id, affiche toutes les évaluatons du module)
"""
mode = None
if "evaluation_id" in REQUEST.form:
evaluation_id = int(REQUEST.form["evaluation_id"])
vals = scu.get_request_args()
if "evaluation_id" in vals:
evaluation_id = int(vals["evaluation_id"])
mode = "eval"
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
if "moduleimpl_id" in REQUEST.form:
moduleimpl_id = int(REQUEST.form["moduleimpl_id"])
if "moduleimpl_id" in vals:
moduleimpl_id = int(vals["moduleimpl_id"])
mode = "module"
evals = sco_evaluations.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
if not mode:
@ -77,7 +78,7 @@ def do_evaluation_listenotes(REQUEST):
if not evals:
return "<p>Aucune évaluation !</p>"
format = REQUEST.form.get("format", "html")
format = vals.get("format", "html")
E = evals[0] # il y a au moins une evaluation
# description de l'evaluation
if mode == "eval":
@ -177,8 +178,8 @@ def do_evaluation_listenotes(REQUEST):
),
]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
descr,
cancelbutton=None,
submitbutton=None,
@ -201,7 +202,6 @@ def do_evaluation_listenotes(REQUEST):
hide_groups = tf[2]["hide_groups"]
with_emails = tf[2]["with_emails"]
return _make_table_notes(
REQUEST,
tf[1],
evals,
format=format,
@ -214,7 +214,6 @@ def do_evaluation_listenotes(REQUEST):
def _make_table_notes(
REQUEST,
html_form,
evals,
format="",
@ -482,7 +481,7 @@ def _make_table_notes(
# html_generate_cells=False # la derniere ligne (moyennes) est incomplete
)
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
t = tab.make_page(format=format, with_html_headers=False)
if format != "html":
return t
@ -760,9 +759,7 @@ def evaluation_check_absences(evaluation_id):
return ValButAbs, AbsNonSignalee, ExcNonSignalee, ExcNonJust, AbsButExc
def evaluation_check_absences_html(
evaluation_id, with_header=True, show_ok=True, REQUEST=None
):
def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True):
"""Affiche etat verification absences d'une evaluation"""
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
@ -778,9 +775,7 @@ def evaluation_check_absences_html(
if with_header:
H = [
html_sco_header.html_sem_header(
REQUEST, "Vérification absences à l'évaluation"
),
html_sco_header.html_sem_header("Vérification absences à l'évaluation"),
sco_evaluations.evaluation_describe(evaluation_id=evaluation_id),
"""<p class="help">Vérification de la cohérence entre les notes saisies et les absences signalées.</p>""",
]
@ -817,8 +812,8 @@ def evaluation_check_absences_html(
'<a class="stdlink" href="Absences/doSignaleAbsence?etudid=%s&datedebut=%s&datefin=%s&demijournee=%s&moduleimpl_id=%s">signaler cette absence</a>'
% (
etud["etudid"],
six.moves.urllib.parse.quote(E["jour"]),
six.moves.urllib.parse.quote(E["jour"]),
urllib.parse.quote(E["jour"]),
urllib.parse.quote(E["jour"]),
demijournee,
E["moduleimpl_id"],
)
@ -861,12 +856,11 @@ def evaluation_check_absences_html(
return "\n".join(H)
def formsemestre_check_absences_html(formsemestre_id, REQUEST=None):
def formsemestre_check_absences_html(formsemestre_id):
"""Affiche etat verification absences pour toutes les evaluations du semestre !"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [
html_sco_header.html_sem_header(
REQUEST,
"Vérification absences aux évaluations de ce semestre",
sem,
),
@ -894,7 +888,6 @@ def formsemestre_check_absences_html(formsemestre_id, REQUEST=None):
E["evaluation_id"],
with_header=False,
show_ok=False,
REQUEST=REQUEST,
)
)
if evals:

View File

@ -31,7 +31,7 @@
"""
from operator import itemgetter
from flask import url_for, g
from flask import url_for, g, request
import app
import app.scodoc.sco_utils as scu
@ -63,7 +63,7 @@ def formsemestre_table_etuds_lycees(
)
def scodoc_table_etuds_lycees(format="html", REQUEST=None):
def scodoc_table_etuds_lycees(format="html"):
"""Table avec _tous_ les étudiants des semestres non verrouillés
de _tous_ les départements.
"""
@ -84,8 +84,8 @@ def scodoc_table_etuds_lycees(format="html", REQUEST=None):
sco_preferences.SemPreferences(),
no_links=True,
)
tab.base_url = REQUEST.URL0
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
tab.base_url = request.base_url
t = tab.make_page(format=format, with_html_headers=False)
if format != "html":
return t
H = [
@ -181,23 +181,22 @@ def formsemestre_etuds_lycees(
format="html",
only_primo=False,
no_grouping=False,
REQUEST=None,
):
"""Table des lycées d'origine"""
tab, etuds_by_lycee = formsemestre_table_etuds_lycees(
formsemestre_id, only_primo=only_primo, group_lycees=not no_grouping
)
tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
if only_primo:
tab.base_url += "&only_primo=1"
if no_grouping:
tab.base_url += "&no_grouping=1"
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
t = tab.make_page(format=format, with_html_headers=False)
if format != "html":
return t
F = [
sco_report.tsp_form_primo_group(
REQUEST, only_primo, no_grouping, formsemestre_id, format
only_primo, no_grouping, formsemestre_id, format
)
]
H = [

View File

@ -88,14 +88,14 @@ def do_modalite_list(*args, **kw):
return _modaliteEditor.list(cnx, *args, **kw)
def do_modalite_create(args, REQUEST=None):
def do_modalite_create(args):
"create a modalite"
cnx = ndb.GetDBConnexion()
r = _modaliteEditor.create(cnx, args)
return r
def do_modalite_delete(oid, REQUEST=None):
def do_modalite_delete(oid):
"delete a modalite"
cnx = ndb.GetDBConnexion()
log("do_modalite_delete: form_modalite_id=%s" % oid)

View File

@ -100,9 +100,7 @@ def do_moduleimpl_delete(oid, formsemestre_id=None):
) # > moduleimpl_delete
def do_moduleimpl_list(
moduleimpl_id=None, formsemestre_id=None, module_id=None, REQUEST=None
):
def do_moduleimpl_list(moduleimpl_id=None, formsemestre_id=None, module_id=None):
"list moduleimpls"
args = locals()
cnx = ndb.GetDBConnexion()
@ -110,7 +108,7 @@ def do_moduleimpl_list(
# Ajoute la liste des enseignants
for mo in modimpls:
mo["ens"] = do_ens_list(args={"moduleimpl_id": mo["moduleimpl_id"]})
return scu.return_text_if_published(modimpls, REQUEST)
return modimpls
def do_moduleimpl_edit(args, formsemestre_id=None, cnx=None):
@ -125,7 +123,7 @@ def do_moduleimpl_edit(args, formsemestre_id=None, cnx=None):
def do_moduleimpl_withmodule_list(
moduleimpl_id=None, formsemestre_id=None, module_id=None, REQUEST=None
moduleimpl_id=None, formsemestre_id=None, module_id=None
):
"""Liste les moduleimpls et ajoute dans chacun le module correspondant
Tri la liste par semestre/UE/numero_matiere/numero_module.
@ -137,7 +135,6 @@ def do_moduleimpl_withmodule_list(
from app.scodoc import sco_edit_module
args = locals()
del args["REQUEST"]
modimpls = do_moduleimpl_list(
**{
"moduleimpl_id": moduleimpl_id,
@ -166,16 +163,14 @@ def do_moduleimpl_withmodule_list(
)
)
return scu.return_text_if_published(modimpls, REQUEST)
return modimpls
def do_moduleimpl_inscription_list(moduleimpl_id=None, etudid=None, REQUEST=None):
def do_moduleimpl_inscription_list(moduleimpl_id=None, etudid=None):
"list moduleimpl_inscriptions"
args = locals()
cnx = ndb.GetDBConnexion()
return scu.return_text_if_published(
_moduleimpl_inscriptionEditor.list(cnx, args), REQUEST
)
return _moduleimpl_inscriptionEditor.list(cnx, args)
def do_moduleimpl_listeetuds(moduleimpl_id):
@ -244,9 +239,7 @@ def do_moduleimpl_inscription_delete(oid, formsemestre_id=None):
) # > moduleimpl_inscription
def do_moduleimpl_inscrit_etuds(
moduleimpl_id, formsemestre_id, etudids, reset=False, REQUEST=None
):
def do_moduleimpl_inscrit_etuds(moduleimpl_id, formsemestre_id, etudids, reset=False):
"""Inscrit les etudiants (liste d'etudids) a ce module.
Si reset, desinscrit tous les autres.
"""
@ -309,7 +302,7 @@ def do_ens_create(args):
return r
def can_change_module_resp(REQUEST, moduleimpl_id):
def can_change_module_resp(moduleimpl_id):
"""Check if current user can modify module resp. (raise exception if not).
= Admin, et dir des etud. (si option l'y autorise)
"""

View File

@ -30,7 +30,8 @@
from operator import itemgetter
import flask
from flask import url_for, g
from flask import url_for, g, request
from flask_login import current_user
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -50,9 +51,7 @@ from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission
def moduleimpl_inscriptions_edit(
moduleimpl_id, etuds=[], submitted=False, REQUEST=None
):
def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
"""Formulaire inscription des etudiants a ce module
* Gestion des inscriptions
Nom TD TA TP (triable)
@ -137,7 +136,7 @@ def moduleimpl_inscriptions_edit(
</script>"""
)
H.append("""<form method="post" id="mi_form" action="%s">""" % REQUEST.URL0)
H.append("""<form method="post" id="mi_form" action="%s">""" % request.base_url)
H.append(
"""
<input type="hidden" name="moduleimpl_id" value="%(moduleimpl_id)s"/>
@ -199,7 +198,7 @@ def moduleimpl_inscriptions_edit(
else: # SUBMISSION
# inscrit a ce module tous les etuds selectionnes
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
moduleimpl_id, formsemestre_id, etuds, reset=True, REQUEST=REQUEST
moduleimpl_id, formsemestre_id, etuds, reset=True
)
return flask.redirect("moduleimpl_status?moduleimpl_id=%s" % (moduleimpl_id))
#
@ -230,7 +229,7 @@ def _make_menu(partitions, title="", check="true"):
)
def moduleimpl_inscriptions_stats(formsemestre_id, REQUEST=None):
def moduleimpl_inscriptions_stats(formsemestre_id):
"""Affiche quelques informations sur les inscriptions
aux modules de ce semestre.
@ -250,7 +249,7 @@ def moduleimpl_inscriptions_stats(formsemestre_id, REQUEST=None):
tous sauf <liste d'au plus 7 noms>
"""
authuser = REQUEST.AUTHENTICATED_USER
authuser = current_user
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
@ -285,9 +284,7 @@ def moduleimpl_inscriptions_stats(formsemestre_id, REQUEST=None):
mod["nb_inscrits"] = nb_inscrits
options.append(mod)
# Page HTML:
H = [
html_sco_header.html_sem_header(REQUEST, "Inscriptions aux modules du semestre")
]
H = [html_sco_header.html_sem_header("Inscriptions aux modules du semestre")]
H.append("<h3>Inscrits au semestre: %d étudiants</h3>" % len(inscrits))
@ -524,7 +521,7 @@ def is_inscrit_ue(etudid, formsemestre_id, ue_id):
return r
def do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id, REQUEST=None):
def do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
"""Desincrit l'etudiant de tous les modules de cette UE dans ce semestre."""
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
@ -544,20 +541,19 @@ def do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id, REQUEST=None):
""",
{"etudid": etudid, "formsemestre_id": formsemestre_id, "ue_id": ue_id},
)
if REQUEST:
logdb(
cnx,
method="etud_desinscrit_ue",
etudid=etudid,
msg="desinscription UE %s" % ue_id,
commit=False,
)
logdb(
cnx,
method="etud_desinscrit_ue",
etudid=etudid,
msg="desinscription UE %s" % ue_id,
commit=False,
)
sco_cache.invalidate_formsemestre(
formsemestre_id=formsemestre_id
) # > desinscription etudiant des modules
def do_etud_inscrit_ue(etudid, formsemestre_id, ue_id, REQUEST=None):
def do_etud_inscrit_ue(etudid, formsemestre_id, ue_id):
"""Incrit l'etudiant de tous les modules de cette UE dans ce semestre."""
# Verifie qu'il est bien inscrit au semestre
insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(

View File

@ -28,7 +28,7 @@
"""Tableau de bord module
"""
import time
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
import urllib
from flask import g, url_for
from flask_login import current_user
@ -55,7 +55,7 @@ from app.scodoc import sco_users
# ported from old DTML code in oct 2009
# menu evaluation dans moduleimpl
def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0):
"Menu avec actions sur une evaluation"
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
modimpl = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
@ -64,7 +64,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
if (
sco_permissions_check.can_edit_notes(
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"], allow_ens=False
current_user, E["moduleimpl_id"], allow_ens=False
)
and nbnotes != 0
):
@ -80,7 +80,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
"evaluation_id": evaluation_id,
},
"enabled": sco_permissions_check.can_edit_notes(
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"]
current_user, E["moduleimpl_id"]
),
},
{
@ -90,7 +90,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
"evaluation_id": evaluation_id,
},
"enabled": sco_permissions_check.can_edit_notes(
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"], allow_ens=False
current_user, E["moduleimpl_id"], allow_ens=False
),
},
{
@ -101,7 +101,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
},
"enabled": nbnotes == 0
and sco_permissions_check.can_edit_notes(
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"], allow_ens=False
current_user, E["moduleimpl_id"], allow_ens=False
),
},
{
@ -111,7 +111,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
"evaluation_id": evaluation_id,
},
"enabled": sco_permissions_check.can_edit_notes(
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"], allow_ens=False
current_user, E["moduleimpl_id"], allow_ens=False
),
},
{
@ -128,16 +128,15 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
"args": {
"evaluation_id": evaluation_id,
},
"enabled": nbnotes == 0
and sco_permissions_check.can_edit_notes(
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"]
"enabled": sco_permissions_check.can_edit_notes(
current_user, E["moduleimpl_id"]
),
},
{
"title": "Absences ce jour",
"endpoint": "absences.EtatAbsencesDate",
"args": {
"date": six.moves.urllib.parse.quote(E["jour"], safe=""),
"date": urllib.parse.quote(E["jour"], safe=""),
"group_ids": group_id,
},
"enabled": E["jour"],
@ -155,7 +154,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
return htmlutils.make_menu("actions", menuEval, alone=True)
def moduleimpl_status(moduleimpl_id=None, partition_id=None, REQUEST=None):
def moduleimpl_status(moduleimpl_id=None, partition_id=None):
"""Tableau de bord module (liste des evaluations etc)"""
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
formsemestre_id = M["formsemestre_id"]
@ -191,7 +190,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None, REQUEST=None):
f"""<span class="blacktt">({module_resp.user_name})</span>""",
]
try:
sco_moduleimpl.can_change_module_resp(REQUEST, moduleimpl_id)
sco_moduleimpl.can_change_module_resp(moduleimpl_id)
H.append(
"""<a class="stdlink" href="edit_moduleimpl_resp?moduleimpl_id=%s">modifier</a>"""
% moduleimpl_id
@ -515,7 +514,6 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None, REQUEST=None):
moduleimpl_evaluation_menu(
eval["evaluation_id"],
nbnotes=etat["nb_notes"],
REQUEST=REQUEST,
)
)
H.append("</td>")

View File

@ -30,7 +30,8 @@
Fiche description d'un étudiant et de son parcours
"""
from flask import url_for, g
from flask import url_for, g, request
from flask_login import current_user
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -46,6 +47,7 @@ from app.scodoc import sco_groups
from app.scodoc import sco_parcours_dut
from app.scodoc import sco_permissions_check
from app.scodoc import sco_photos
from app.scodoc import sco_users
from app.scodoc import sco_report
from app.scodoc import sco_etud
from app.scodoc.sco_bulletins import etud_descr_situation_semestre
@ -142,18 +144,18 @@ def _menuScolarite(authuser, sem, etudid):
)
def ficheEtud(etudid=None, REQUEST=None):
def ficheEtud(etudid=None):
"fiche d'informations sur un etudiant"
authuser = REQUEST.AUTHENTICATED_USER
authuser = current_user
cnx = ndb.GetDBConnexion()
if etudid and REQUEST:
if etudid:
# la sidebar est differente s'il y a ou pas un etudid
# voir html_sidebar.sidebar()
REQUEST.form["etudid"] = etudid
g.etudid = etudid
args = sco_etud.make_etud_args(etudid=etudid)
etuds = sco_etud.etudident_list(cnx, args)
if not etuds:
log("ficheEtud: etudid=%s REQUEST.form=%s" % (etudid, REQUEST.form))
log("ficheEtud: etudid=%s request.args=%s" % (etudid, request.args))
raise ScoValueError("Etudiant inexistant !")
etud = etuds[0]
etudid = etud["etudid"]
@ -167,7 +169,7 @@ def ficheEtud(etudid=None, REQUEST=None):
info["info_naissance"] += " à " + info["lieu_naissance"]
if info["dept_naissance"]:
info["info_naissance"] += " (%s)" % info["dept_naissance"]
info["etudfoto"] = sco_photos.etud_photo_html(etud, REQUEST=REQUEST)
info["etudfoto"] = sco_photos.etud_photo_html(etud)
if (
(not info["domicile"])
and (not info["codepostaldomicile"])
@ -254,10 +256,19 @@ def ficheEtud(etudid=None, REQUEST=None):
with_all_columns=False,
a_url="Notes/",
)
info["link_bul_pdf"] = (
'<span class="link_bul_pdf"><a class="stdlink" href="Notes/etud_bulletins_pdf?etudid=%(etudid)s">tous les bulletins</a></span>'
% etud
)
info[
"link_bul_pdf"
] = f"""<span class="link_bul_pdf"><a class="stdlink" href="{
url_for("notes.etud_bulletins_pdf", scodoc_dept=g.scodoc_dept, etudid=etudid)
}">tous les bulletins</a></span>"""
if authuser.has_permission(Permission.ScoEtudInscrit):
info[
"link_inscrire_ailleurs"
] = f"""<span class="link_bul_pdf"><a class="stdlink" href="{
url_for("notes.formsemestre_inscription_with_modules_form", scodoc_dept=g.scodoc_dept, etudid=etudid)
}">inscrire à un autre semestre</a></span>"""
else:
info["link_inscrire_ailleurs"] = ""
else:
# non inscrit
l = ["<p><b>Etudiant%s non inscrit%s" % (info["ne"], info["ne"])]
@ -269,6 +280,7 @@ def ficheEtud(etudid=None, REQUEST=None):
l.append("</b></b>")
info["liste_inscriptions"] = "\n".join(l)
info["link_bul_pdf"] = ""
info["link_inscrire_ailleurs"] = ""
# Liste des annotations
alist = []
@ -289,9 +301,11 @@ def ficheEtud(etudid=None, REQUEST=None):
title="Supprimer cette annotation",
),
)
author = sco_users.user_info(a["author"])
alist.append(
'<tr><td><span class="annodate">Le %(date)s par %(author)s : </span><span class="annoc">%(comment)s</span></td>%(dellink)s</tr>'
% a
f"""<tr><td><span class="annodate">Le {a['date']} par {author['prenomnom']} :
</span><span class="annoc">{a['comment']}</span></td>{a['dellink']}</tr>
"""
)
info["liste_annotations"] = "\n".join(alist)
# fiche admission
@ -345,7 +359,7 @@ def ficheEtud(etudid=None, REQUEST=None):
# Fichiers archivés:
info["fichiers_archive_htm"] = (
'<div class="fichetitre">Fichiers associés</div>'
+ sco_archives_etud.etud_list_archives_html(REQUEST, etudid)
+ sco_archives_etud.etud_list_archives_html(etudid)
)
# Devenir de l'étudiant:
@ -392,10 +406,11 @@ def ficheEtud(etudid=None, REQUEST=None):
"inscriptions_mkup"
] = """<div class="ficheinscriptions" id="ficheinscriptions">
<div class="fichetitre">Parcours</div>%s
%s
%s %s
</div>""" % (
info["liste_inscriptions"],
info["link_bul_pdf"],
info["link_inscrire_ailleurs"],
)
#
@ -405,7 +420,7 @@ def ficheEtud(etudid=None, REQUEST=None):
)
else:
info["groupes_row"] = ""
info["menus_etud"] = menus_etud(REQUEST)
info["menus_etud"] = menus_etud(etudid)
tmpl = """<div class="menus_etud">%(menus_etud)s</div>
<div class="ficheEtud" id="ficheEtud"><table>
<tr><td>
@ -487,13 +502,11 @@ def ficheEtud(etudid=None, REQUEST=None):
return header + tmpl % info + html_sco_header.sco_footer()
def menus_etud(REQUEST=None):
def menus_etud(etudid):
"""Menu etudiant (operations sur l'etudiant)"""
if "etudid" not in REQUEST.form:
return ""
authuser = REQUEST.AUTHENTICATED_USER
authuser = current_user
etud = sco_etud.get_etud_info(filled=True)[0]
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
menuEtud = [
{
@ -532,16 +545,14 @@ def menus_etud(REQUEST=None):
return htmlutils.make_menu("Etudiant", menuEtud, alone=True)
def etud_info_html(etudid, with_photo="1", REQUEST=None, debug=False):
def etud_info_html(etudid, with_photo="1", debug=False):
"""An HTML div with basic information and links about this etud.
Used for popups information windows.
"""
formsemestre_id = sco_formsemestre_status.retreive_formsemestre_from_request()
with_photo = int(with_photo)
etud = sco_etud.get_etud_info(filled=True)[0]
photo_html = sco_photos.etud_photo_html(
etud, title="fiche de " + etud["nom"], REQUEST=REQUEST
)
photo_html = sco_photos.etud_photo_html(etud, title="fiche de " + etud["nom"])
# experimental: may be too slow to be here
etud["codeparcours"], etud["decisions_jury"] = sco_report.get_codeparcoursetud(
etud, prefix="S", separator=", "

View File

@ -535,7 +535,7 @@ class SituationEtudParcoursGeneric(object):
validated = True
return s
def valide_decision(self, decision, REQUEST):
def valide_decision(self, decision):
"""Enregistre la decision (instance de DecisionSem)
Enregistre codes semestre et UE, et autorisations inscription.
"""
@ -588,7 +588,6 @@ class SituationEtudParcoursGeneric(object):
self.etudid,
decision.code_etat,
decision.assiduite,
REQUEST=REQUEST,
)
# -- modification du code du semestre precedent
if self.prev and decision.new_code_prev:
@ -619,7 +618,6 @@ class SituationEtudParcoursGeneric(object):
self.etudid,
decision.new_code_prev,
decision.assiduite, # attention: en toute rigueur il faudrait utiliser une indication de l'assiduite au sem. precedent, que nous n'avons pas...
REQUEST=REQUEST,
)
sco_cache.invalidate_formsemestre(
@ -897,9 +895,7 @@ def formsemestre_update_validation_sem(
return to_invalidate
def formsemestre_validate_ues(
formsemestre_id, etudid, code_etat_sem, assiduite, REQUEST=None
):
def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite):
"""Enregistre codes UE, selon état semestre.
Les codes UE sont toujours calculés ici, et non passés en paramètres
car ils ne dépendent que de la note d'UE et de la validation ou non du semestre.
@ -933,14 +929,13 @@ def formsemestre_validate_ues(
cnx, nt, formsemestre_id, etudid, ue_id, code_ue
)
if REQUEST:
logdb(
cnx,
method="validate_ue",
etudid=etudid,
msg="ue_id=%s code=%s" % (ue_id, code_ue),
commit=False,
)
logdb(
cnx,
method="validate_ue",
etudid=etudid,
msg="ue_id=%s code=%s" % (ue_id, code_ue),
commit=False,
)
cnx.commit()

View File

@ -43,6 +43,8 @@ Les images sont servies par ScoDoc, via la méthode getphotofile?etudid=xxx
"""
from flask.helpers import make_response
from app.scodoc.sco_exceptions import ScoGenError
import datetime
import glob
import io
@ -52,6 +54,7 @@ import requests
import time
import traceback
import PIL
from PIL import Image as PILImage
from flask import request, g
@ -118,7 +121,7 @@ def etud_photo_url(etud, size="small", fast=False):
return photo_url
def get_photo_image(etudid=None, size="small", REQUEST=None):
def get_photo_image(etudid=None, size="small"):
"""Returns photo image (HTTP response)
If not etudid, use "unknown" image
"""
@ -129,24 +132,14 @@ def get_photo_image(etudid=None, size="small", REQUEST=None):
filename = photo_pathname(etud, size=size)
if not filename:
filename = UNKNOWN_IMAGE_PATH
return _http_jpeg_file(filename, REQUEST=REQUEST)
return _http_jpeg_file(filename)
def _http_jpeg_file(filename, REQUEST=None):
"""returns an image.
This function will be modified when we kill #zope
"""
def _http_jpeg_file(filename):
"""returns an image as a Flask response"""
st = os.stat(filename)
last_modified = st.st_mtime # float timestamp
last_modified_str = time.strftime(
"%a, %d %b %Y %H:%M:%S GMT", time.gmtime(last_modified)
)
file_size = st.st_size
RESPONSE = REQUEST.RESPONSE
RESPONSE.setHeader("Content-Type", "image/jpeg")
RESPONSE.setHeader("Last-Modified", last_modified_str)
RESPONSE.setHeader("Cache-Control", "max-age=3600")
RESPONSE.setHeader("Content-Length", str(file_size))
header = request.headers.get("If-Modified-Since")
if header is not None:
header = header.split(";")[0]
@ -159,20 +152,27 @@ def _http_jpeg_file(filename, REQUEST=None):
try:
dt = datetime.datetime.strptime(header, "%a, %d %b %Y %H:%M:%S GMT")
mod_since = dt.timestamp()
except:
except ValueError:
mod_since = None
if (mod_since is not None) and last_modified <= mod_since:
RESPONSE.setStatus(304) # not modified
return ""
return open(filename, mode="rb").read()
return "", 304 # not modified
#
last_modified_str = time.strftime(
"%a, %d %b %Y %H:%M:%S GMT", time.gmtime(last_modified)
)
response = make_response(open(filename, mode="rb").read())
response.headers["Content-Type"] = "image/jpeg"
response.headers["Last-Modified"] = last_modified_str
response.headers["Cache-Control"] = "max-age=3600"
response.headers["Content-Length"] = str(file_size)
return response
def etud_photo_is_local(etud, size="small"):
return photo_pathname(etud, size=size)
def etud_photo_html(etud=None, etudid=None, title=None, size="small", REQUEST=None):
def etud_photo_html(etud=None, etudid=None, title=None, size="small"):
"""HTML img tag for the photo, either in small size (h90)
or original size (size=="orig")
"""
@ -204,14 +204,12 @@ def etud_photo_html(etud=None, etudid=None, title=None, size="small", REQUEST=No
)
def etud_photo_orig_html(etud=None, etudid=None, title=None, REQUEST=None):
def etud_photo_orig_html(etud=None, etudid=None, title=None):
"""HTML img tag for the photo, in full size.
Full-size images are always stored locally in the filesystem.
They are the original uploaded images, converted in jpeg.
"""
return etud_photo_html(
etud=etud, etudid=etudid, title=title, size="orig", REQUEST=REQUEST
)
return etud_photo_html(etud=etud, etudid=etudid, title=title, size="orig")
def photo_pathname(etud, size="orig"):
@ -246,7 +244,10 @@ def store_photo(etud, data):
filesize = len(data)
if filesize < 10 or filesize > MAX_FILE_SIZE:
return 0, "Fichier image de taille invalide ! (%d)" % filesize
filename = save_image(etud["etudid"], data)
try:
filename = save_image(etud["etudid"], data)
except PIL.UnidentifiedImageError:
raise ScoGenError(msg="Fichier d'image invalide ou non format non supporté")
# update database:
etud["photo_filename"] = filename
etud["foto"] = None
@ -260,7 +261,7 @@ def store_photo(etud, data):
return 1, "ok"
def suppress_photo(etud, REQUEST=None):
def suppress_photo(etud):
"""Suppress a photo"""
log("suppress_photo etudid=%s" % etud["etudid"])
rel_path = photo_pathname(etud)
@ -278,8 +279,7 @@ def suppress_photo(etud, REQUEST=None):
log("removing file %s" % filename)
os.remove(filename)
# 3- log
if REQUEST:
logdb(cnx, method="changePhoto", msg="suppression", etudid=etud["etudid"])
logdb(cnx, method="changePhoto", msg="suppression", etudid=etud["etudid"])
# ---------------------------------------------------------------------------
@ -298,6 +298,7 @@ def save_image(etudid, data):
filename = get_new_filename(etudid)
path = os.path.join(PHOTO_DIR, filename)
log("saving %dx%d jpeg to %s" % (img.size[0], img.size[1], path))
img = img.convert("RGB")
img.save(path + IMAGE_EXT, format="JPEG", quality=92)
# resize:
img = scale_height(img)
@ -341,7 +342,7 @@ def find_new_dir():
def copy_portal_photo_to_fs(etud):
"""Copy the photo from portal (distant website) to local fs.
Returns rel. path or None if copy failed, with a diagnotic message
Returns rel. path or None if copy failed, with a diagnostic message
"""
sco_etud.format_etud_ident(etud)
url = photo_portal_url(etud)
@ -353,11 +354,12 @@ def copy_portal_photo_to_fs(etud):
log("copy_portal_photo_to_fs: getting %s" % url)
r = requests.get(url, timeout=portal_timeout)
except:
log("download failed: exception:\n%s" % traceback.format_exc())
log("called from:\n" + "".join(traceback.format_stack()))
# log("download failed: exception:\n%s" % traceback.format_exc())
# log("called from:\n" + "".join(traceback.format_stack()))
log("copy_portal_photo_to_fs: error.")
return None, "%s: erreur chargement de %s" % (etud["nomprenom"], url)
if r.status_code != 200:
log("download failed")
log(f"copy_portal_photo_to_fs: download failed {r.status_code }")
return None, "%s: erreur chargement de %s" % (etud["nomprenom"], url)
data = r.content # image bytes
try:

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@ Recapitule tous les semestres validés dans une feuille excel.
"""
import collections
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.sco_utils as scu
from app.scodoc import sco_abs
@ -164,7 +164,7 @@ def _getEtudInfoGroupes(group_ids, etat=None):
return etuds
def formsemestre_poursuite_report(formsemestre_id, format="html", REQUEST=None):
def formsemestre_poursuite_report(formsemestre_id, format="html"):
"""Table avec informations "poursuite" """
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
etuds = _getEtudInfoGroupes([sco_groups.get_default_group(formsemestre_id)])
@ -211,12 +211,11 @@ def formsemestre_poursuite_report(formsemestre_id, format="html", REQUEST=None):
)
tab.caption = "Récapitulatif %s." % sem["titreannee"]
tab.html_caption = "Récapitulatif %s." % sem["titreannee"]
tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
return tab.make_page(
title="""<h2 class="formsemestre">Poursuite d'études</h2>""",
init_qtip=True,
javascripts=["js/etud_info.js"],
format=format,
REQUEST=REQUEST,
with_html_headers=True,
)

View File

@ -77,7 +77,7 @@ sinon, elle ne concerne que le semestre indiqué.
- avoir un mapping (read only) de toutes les valeurs:
sco_preferences.SemPreferences(formsemestre_id)
- editer les preferences globales:
sco_preferences.get_base_preferences(self).edit(REQUEST=REQUEST)
sco_preferences.get_base_preferences(self).edit()
- editer les preferences d'un semestre:
SemPreferences(formsemestre_id).edit()
@ -111,7 +111,8 @@ get_base_preferences(formsemestre_id)
"""
import flask
from flask import g, url_for
from flask import g, url_for, request
from flask_login import current_user
from app.models import Departement
from app.scodoc import sco_cache
@ -180,7 +181,7 @@ def _convert_pref_type(p, pref_spec):
def _get_pref_default_value_from_config(name, pref_spec):
"""get default value store in application level config.
If not found, use defalut value hardcoded in pref_spec.
If not found, use default value hardcoded in pref_spec.
"""
# XXX va changer avec la nouvelle base
# search in scu.CONFIG
@ -1408,7 +1409,7 @@ class BasePreferences(object):
{
"initvalue": 1,
"title": "Indique si les bulletins sont publiés",
"explanation": "décocher si vous n'avez pas de portal étudiant publiant les bulletins",
"explanation": "décocher si vous n'avez pas de portail étudiant publiant les bulletins",
"input_type": "boolcheckbox",
"labels": ["non", "oui"],
"category": "bul",
@ -1891,7 +1892,7 @@ class BasePreferences(object):
def get(self, formsemestre_id, name):
"""Returns preference value.
If global_lookup, when no value defined for this semestre, returns global value.
when no value defined for this semestre, returns global value.
"""
params = {
"dept_id": self.dept_id,
@ -1901,7 +1902,7 @@ class BasePreferences(object):
cnx = ndb.GetDBConnexion()
plist = self._editor.list(cnx, params)
if not plist:
del params["formsemestre_id"]
params["formsemestre_id"] = None
plist = self._editor.list(cnx, params)
if not plist:
return self.default[name]
@ -2013,7 +2014,7 @@ class BasePreferences(object):
self._editor.delete(cnx, pdb[0]["pref_id"])
sco_cache.invalidate_formsemestre() # > modif preferences
def edit(self, REQUEST):
def edit(self):
"""HTML dialog: edit global preferences"""
from app.scodoc import html_sco_header
@ -2022,15 +2023,17 @@ class BasePreferences(object):
html_sco_header.sco_header(page_title="Préférences"),
"<h2>Préférences globales pour %s</h2>" % scu.ScoURL(),
f"""<p><a href="{url_for("scolar.config_logos", scodoc_dept=g.scodoc_dept)
}">modification des logos du département (pour documents pdf)</a></p>""",
}">modification des logos du département (pour documents pdf)</a></p>"""
if current_user.is_administrator()
else "",
"""<p class="help">Ces paramètres s'appliquent par défaut à tous les semestres, sauf si ceux-ci définissent des valeurs spécifiques.</p>
<p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
""",
]
form = self.build_tf_form()
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
form,
initvalues=self.prefs[None],
submitlabel="Enregistrer les modifications",
@ -2140,7 +2143,7 @@ class SemPreferences(object):
return self.base_prefs.is_global(self.formsemestre_id, name)
# The dialog
def edit(self, categories=[], REQUEST=None):
def edit(self, categories=[]):
"""Dialog to edit semestre preferences in given categories"""
from app.scodoc import html_sco_header
from app.scodoc import sco_formsemestre
@ -2151,7 +2154,7 @@ class SemPreferences(object):
) # a bug !
sem = sco_formsemestre.get_formsemestre(self.formsemestre_id)
H = [
html_sco_header.html_sem_header(REQUEST, "Préférences du semestre", sem),
html_sco_header.html_sem_header("Préférences du semestre", sem),
"""
<p class="help">Les paramètres définis ici ne s'appliqueront qu'à ce semestre.</p>
<p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
@ -2194,8 +2197,8 @@ function set_global_pref(el, pref_name) {
form.append(("destination", {"input_type": "hidden"}))
form.append(("formsemestre_id", {"input_type": "hidden"}))
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
form,
initvalues=self,
cssclass="sco_pref",
@ -2245,7 +2248,7 @@ function set_global_pref(el, pref_name) {
return flask.redirect(dest_url + "&head_message=Préférences modifiées")
elif destination == "again":
return flask.redirect(
REQUEST.URL0 + "?formsemestre_id=" + str(self.formsemestre_id)
request.base_url + "?formsemestre_id=" + str(self.formsemestre_id)
)
elif destination == "global":
return flask.redirect(scu.ScoURL() + "/edit_preferences")
@ -2253,7 +2256,7 @@ function set_global_pref(el, pref_name) {
#
def doc_preferences():
""" Liste les preferences en MarkDown, pour la documentation"""
"""Liste les preferences en MarkDown, pour la documentation"""
L = []
for cat, cat_descr in PREF_CATEGORIES:
L.append([""])

View File

@ -31,6 +31,9 @@ import time
from openpyxl.styles.numbers import FORMAT_NUMBER_00
from flask import request
from flask_login import current_user
import app.scodoc.sco_utils as scu
from app.scodoc import sco_abs
from app.scodoc import sco_groups
@ -45,7 +48,7 @@ from app.scodoc import sco_preferences
from app.scodoc.sco_excel import ScoExcelSheet
def feuille_preparation_jury(formsemestre_id, REQUEST):
def feuille_preparation_jury(formsemestre_id):
"Feuille excel pour preparation des jurys"
nt = sco_cache.NotesTableCache.get(
formsemestre_id
@ -318,9 +321,14 @@ def feuille_preparation_jury(formsemestre_id, REQUEST):
% (
sco_version.SCONAME,
time.strftime("%d/%m/%Y"),
REQUEST.BASE0,
REQUEST.AUTHENTICATED_USER,
request.url_root,
current_user,
)
)
xls = ws.generate_standalone()
return sco_excel.send_excel_file(REQUEST, xls, f"PrepaJury{sn}{scu.XLSX_SUFFIX}")
xls = ws.generate()
return scu.send_file(
xls,
f"PrepaJury{sn}",
scu.XLSX_SUFFIX,
mime=scu.XLSX_MIMETYPE,
)

View File

@ -52,7 +52,7 @@ from reportlab.platypus import Paragraph
from reportlab.lib import styles
import flask
from flask import url_for, g
from flask import url_for, g, request
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -495,7 +495,7 @@ def pvjury_table(
return lines, titles, columns_ids
def formsemestre_pvjury(formsemestre_id, format="html", publish=True, REQUEST=None):
def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
"""Page récapitulant les décisions de jury
dpv: result of dict_pvjury
"""
@ -535,13 +535,11 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True, REQUEST=No
return tab.make_page(
format=format,
with_html_headers=False,
REQUEST=REQUEST,
publish=publish,
)
tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
H = [
html_sco_header.html_sem_header(
REQUEST,
"Décisions du jury pour le semestre",
sem,
init_qtip=True,
@ -599,7 +597,7 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True, REQUEST=No
# ---------------------------------------------------------------------------
def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None, REQUEST=None):
def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None):
"""Generation PV jury en PDF: saisie des paramètres
Si etudid, PV pour un seul etudiant. Sinon, tout les inscrits au groupe indiqué.
"""
@ -628,7 +626,6 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None, REQUEST=
H = [
html_sco_header.html_sem_header(
REQUEST,
"Edition du PV de jury %s" % etuddescr,
sem=sem,
javascripts=sco_groups_view.JAVASCRIPTS,
@ -658,8 +655,8 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None, REQUEST=
else:
menu_choix_groupe = "" # un seul etudiant à editer
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
descr,
cancelbutton="Annuler",
method="get",
@ -707,7 +704,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None, REQUEST=
else:
groups_filename = ""
filename = "PV-%s%s-%s.pdf" % (sem["titre_num"], groups_filename, dt)
return scu.sendPDFFile(REQUEST, pdfdoc, filename)
return scu.sendPDFFile(pdfdoc, filename)
def descrform_pvjury(sem):
@ -793,7 +790,7 @@ def descrform_pvjury(sem):
]
def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[], REQUEST=None):
def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
"Lettres avis jury en PDF"
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if not group_ids:
@ -806,8 +803,7 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[], REQUEST=No
H = [
html_sco_header.html_sem_header(
REQUEST,
"Edition des lettres individuelles",
"Édition des lettres individuelles",
sem=sem,
javascripts=sco_groups_view.JAVASCRIPTS,
cssstyles=sco_groups_view.CSSSTYLES,
@ -827,8 +823,8 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[], REQUEST=No
)
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
descr,
cancelbutton="Annuler",
method="POST",
@ -868,7 +864,7 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[], REQUEST=No
dt = time.strftime("%Y-%m-%d")
groups_filename = "-" + groups_infos.groups_filename
filename = "lettres-%s%s-%s.pdf" % (sem["titre_num"], groups_filename, dt)
return scu.sendPDFFile(REQUEST, pdfdoc, filename)
return scu.sendPDFFile(pdfdoc, filename)
def descrform_lettres_individuelles():

View File

@ -27,10 +27,14 @@
"""Tableau recapitulatif des notes d'un semestre
"""
import time
import datetime
import json
import time
from xml.etree import ElementTree
from flask import request
from flask import make_response
import app.scodoc.sco_utils as scu
from app import log
from app.scodoc import html_sco_header
@ -65,7 +69,6 @@ def formsemestre_recapcomplet(
rank_partition_id=None, # si None, calcul rang global
pref_override=True, # si vrai, les prefs ont la priorite sur le param hidebac
force_publishing=True, # publie les XML/JSON meme si bulletins non publiés
REQUEST=None,
):
"""Page récapitulant les notes d'un semestre.
Grand tableau récapitulatif avec toutes les notes de modules
@ -98,9 +101,9 @@ def formsemestre_recapcomplet(
javascripts=["libjs/sorttable.js", "js/etud_info.js"],
),
sco_formsemestre_status.formsemestre_status_head(
formsemestre_id=formsemestre_id, REQUEST=REQUEST
formsemestre_id=formsemestre_id
),
'<form name="f" method="get" action="%s">' % REQUEST.URL0,
'<form name="f" method="get" action="%s">' % request.base_url,
'<input type="hidden" name="formsemestre_id" value="%s"></input>'
% formsemestre_id,
'<input type="hidden" name="pref_override" value="0"></input>',
@ -144,22 +147,22 @@ def formsemestre_recapcomplet(
if hidebac:
H.append("checked")
H.append(""" >cacher bac</input>""")
if tabformat == "xml":
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
H.append(
do_formsemestre_recapcomplet(
REQUEST,
formsemestre_id,
format=tabformat,
hidemodules=hidemodules,
hidebac=hidebac,
modejury=modejury,
sortcol=sortcol,
xml_with_decisions=xml_with_decisions,
rank_partition_id=rank_partition_id,
force_publishing=force_publishing,
)
data = do_formsemestre_recapcomplet(
formsemestre_id,
format=tabformat,
hidemodules=hidemodules,
hidebac=hidebac,
modejury=modejury,
sortcol=sortcol,
xml_with_decisions=xml_with_decisions,
rank_partition_id=rank_partition_id,
force_publishing=force_publishing,
)
if tabformat == "xml":
response = make_response(data)
response.headers["Content-Type"] = scu.XML_MIMETYPE
return response
H.append(data)
if not isFile:
H.append("</form>")
@ -197,7 +200,6 @@ def formsemestre_recapcomplet(
def do_formsemestre_recapcomplet(
REQUEST=None,
formsemestre_id=None,
format="html", # html, xml, xls, xlsall, json
hidemodules=False, # ne pas montrer les modules (ignoré en XML)
@ -227,11 +229,14 @@ def do_formsemestre_recapcomplet(
if format == "xml" or format == "html":
return data
elif format == "csv":
return scu.sendCSVFile(REQUEST, data, filename)
elif format[:3] == "xls":
return sco_excel.send_excel_file(REQUEST, data, filename)
return scu.send_file(data, filename=filename, mime=scu.CSV_MIMETYPE)
elif format.startswith("xls") or format.startswith("xlsx"):
return scu.send_file(data, filename=filename, mime=scu.XLSX_MIMETYPE)
elif format == "json":
return scu.sendJSON(REQUEST, data)
js = json.dumps(data, indent=1, cls=scu.ScoDocJSONEncoder)
return scu.send_file(
js, filename=filename, suffix=scu.JSON_SUFFIX, mime=scu.JSON_MIMETYPE
)
else:
raise ValueError("unknown format %s" % format)
@ -953,7 +958,7 @@ def _formsemestre_recapcomplet_json(
return J, "", "json"
def formsemestres_bulletins(annee_scolaire, REQUEST=None):
def formsemestres_bulletins(annee_scolaire):
"""Tous les bulletins des semestres publiés des semestres de l'année indiquée.
:param annee_scolaire(int): année de début de l'année scoalaire
:returns: JSON
@ -967,4 +972,4 @@ def formsemestres_bulletins(annee_scolaire, REQUEST=None):
)
jslist.append(J)
return scu.sendJSON(REQUEST, jslist)
return scu.sendJSON(jslist)

View File

@ -37,7 +37,7 @@ import time
import datetime
from operator import itemgetter
from flask import url_for, g
from flask import url_for, g, request
import pydot
import app.scodoc.sco_utils as scu
@ -210,7 +210,6 @@ def _results_by_category(
def formsemestre_report(
formsemestre_id,
etuds,
REQUEST=None,
category="bac",
result="codedecision",
category_name="",
@ -247,32 +246,31 @@ def formsemestre_report(
sem["titreannee"],
)
tab.html_caption = "Répartition des résultats par %s." % category_name
tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
if only_primo:
tab.base_url += "&only_primo=on"
return tab
# def formsemestre_report_bacs(formsemestre_id, format='html', REQUEST=None):
# def formsemestre_report_bacs(formsemestre_id, format='html'):
# """
# Tableau sur résultats par type de bac
# """
# sem = sco_formsemestre.get_formsemestre( formsemestre_id)
# title = 'Statistiques bacs ' + sem['titreannee']
# etuds = formsemestre_etuds_stats(sem)
# tab = formsemestre_report(formsemestre_id, etuds, REQUEST=REQUEST,
# tab = formsemestre_report(formsemestre_id, etuds,
# category='bac', result='codedecision',
# category_name='Bac',
# title=title)
# return tab.make_page(
# title = """<h2>Résultats de <a href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titreannee)s</a></h2>""" % sem,
# format=format, page_title = title, REQUEST=REQUEST )
# format=format, page_title = title)
def formsemestre_report_counts(
formsemestre_id,
format="html",
REQUEST=None,
category="bac",
result="codedecision",
allkeys=False,
@ -288,7 +286,6 @@ def formsemestre_report_counts(
tab = formsemestre_report(
formsemestre_id,
etuds,
REQUEST=REQUEST,
category=category,
result=result,
category_name=category_name,
@ -322,7 +319,7 @@ def formsemestre_report_counts(
F = [
"""<form name="f" method="get" action="%s"><p>
Colonnes: <select name="result" onchange="document.f.submit()">"""
% REQUEST.URL0
% request.base_url
]
for k in keys:
if k == result:
@ -355,7 +352,6 @@ def formsemestre_report_counts(
t = tab.make_page(
title="""<h2 class="formsemestre">Comptes croisés</h2>""",
format=format,
REQUEST=REQUEST,
with_html_headers=False,
)
if format != "html":
@ -693,7 +689,6 @@ def formsemestre_suivi_cohorte(
civilite=None,
statut="",
only_primo=False,
REQUEST=None,
):
"""Affiche suivi cohortes par numero de semestre"""
annee_bac = str(annee_bac)
@ -718,15 +713,15 @@ def formsemestre_suivi_cohorte(
)
tab.base_url = (
"%s?formsemestre_id=%s&percent=%s&bac=%s&bacspecialite=%s&civilite=%s"
% (REQUEST.URL0, formsemestre_id, percent, bac, bacspecialite, civilite)
% (request.base_url, formsemestre_id, percent, bac, bacspecialite, civilite)
)
if only_primo:
tab.base_url += "&only_primo=on"
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
t = tab.make_page(format=format, with_html_headers=False)
if format != "html":
return t
base_url = REQUEST.URL0
base_url = request.base_url
burl = "%s?formsemestre_id=%s&bac=%s&bacspecialite=%s&civilite=%s&statut=%s" % (
base_url,
formsemestre_id,
@ -756,7 +751,6 @@ def formsemestre_suivi_cohorte(
"""<h2 class="formsemestre">Suivi cohorte: devenir des étudiants de ce semestre</h2>""",
_gen_form_selectetuds(
formsemestre_id,
REQUEST=REQUEST,
only_primo=only_primo,
bac=bac,
bacspecialite=bacspecialite,
@ -780,7 +774,6 @@ def formsemestre_suivi_cohorte(
def _gen_form_selectetuds(
formsemestre_id,
REQUEST=None,
percent=None,
only_primo=None,
bac=None,
@ -816,7 +809,7 @@ def _gen_form_selectetuds(
<p>Bac: <select name="bac" onchange="javascript: submit(this);">
<option value="" %s>tous</option>
"""
% (REQUEST.URL0, selected)
% (request.base_url, selected)
]
for b in bacs:
if bac == b:
@ -1165,9 +1158,9 @@ def table_suivi_parcours(formsemestre_id, only_primo=False, grouped_parcours=Tru
return tab
def tsp_form_primo_group(REQUEST, only_primo, no_grouping, formsemestre_id, format):
def tsp_form_primo_group(only_primo, no_grouping, formsemestre_id, format):
"""Element de formulaire pour choisir si restriction aux primos entrants et groupement par lycees"""
F = ["""<form name="f" method="get" action="%s">""" % REQUEST.URL0]
F = ["""<form name="f" method="get" action="%s">""" % request.base_url]
if only_primo:
checked = 'checked="1"'
else:
@ -1197,7 +1190,6 @@ def formsemestre_suivi_parcours(
format="html",
only_primo=False,
no_grouping=False,
REQUEST=None,
):
"""Effectifs dans les differents parcours possibles."""
tab = table_suivi_parcours(
@ -1205,17 +1197,15 @@ def formsemestre_suivi_parcours(
only_primo=only_primo,
grouped_parcours=not no_grouping,
)
tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
if only_primo:
tab.base_url += "&only_primo=1"
if no_grouping:
tab.base_url += "&no_grouping=1"
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
t = tab.make_page(format=format, with_html_headers=False)
if format != "html":
return t
F = [
tsp_form_primo_group(REQUEST, only_primo, no_grouping, formsemestre_id, format)
]
F = [tsp_form_primo_group(only_primo, no_grouping, formsemestre_id, format)]
H = [
html_sco_header.sco_header(
@ -1405,7 +1395,7 @@ def graph_parcours(
if len(edges[(src_id, dst_id)]) <= MAX_ETUD_IN_DESCR:
etud_descr = _descr_etud_set(edges[(src_id, dst_id)])
bubbles[src_id + ":" + dst_id] = etud_descr
e.set_URL("__xxxetudlist__?" + src_id + ":" + dst_id)
e.set_URL(f"__xxxetudlist__?{src_id}:{dst_id}")
# Genere graphe
_, path = tempfile.mkstemp(".gr")
g.write(path=path, format=format)
@ -1466,7 +1456,6 @@ def formsemestre_graph_parcours(
civilite="",
statut="",
allkeys=False, # unused
REQUEST=None,
):
"""Graphe suivi cohortes"""
annee_bac = str(annee_bac)
@ -1492,7 +1481,7 @@ def formsemestre_graph_parcours(
statut=statut,
)
filename = scu.make_filename("flux " + sem["titreannee"])
return scu.sendPDFFile(REQUEST, doc, filename + ".pdf")
return scu.sendPDFFile(doc, filename + ".pdf")
elif format == "png":
#
(
@ -1513,12 +1502,13 @@ def formsemestre_graph_parcours(
civilite=civilite,
statut=statut,
)
filename = scu.make_filename("flux " + sem["titreannee"])
REQUEST.RESPONSE.setHeader(
"content-disposition", 'attachment; filename="%s"' % filename
return scu.send_file(
doc,
filename="flux " + sem["titreannee"],
suffix=".png",
attached=True,
mime="image/png",
)
REQUEST.RESPONSE.setHeader("content-type", "image/png")
return doc
elif format == "html":
url_kw = {
"scodoc_dept": g.scodoc_dept,
@ -1558,7 +1548,6 @@ def formsemestre_graph_parcours(
"<p>%d étudiants sélectionnés</p>" % len(etuds),
_gen_form_selectetuds(
formsemestre_id,
REQUEST=REQUEST,
only_primo=only_primo,
bac=bac,
bacspecialite=bacspecialite,

View File

@ -9,48 +9,50 @@ from app.scodoc.sco_permissions import Permission as p
SCO_ROLES_DEFAULTS = {
"Observateur": (p.ScoObservateur,),
"Ens": (
p.ScoObservateur,
p.ScoView,
p.ScoEnsView,
p.ScoUsersView,
p.ScoEtudAddAnnotations,
p.ScoAbsChange,
p.ScoAbsAddBillet,
p.ScoAbsChange,
p.ScoEnsView,
p.ScoEntrepriseView,
p.ScoEtudAddAnnotations,
p.ScoObservateur,
p.ScoUsersView,
p.ScoView,
),
"Secr": (
p.ScoObservateur,
p.ScoView,
p.ScoUsersView,
p.ScoEtudAddAnnotations,
p.ScoAbsChange,
p.ScoAbsAddBillet,
p.ScoEntrepriseView,
p.ScoAbsChange,
p.ScoEditApo,
p.ScoEntrepriseChange,
p.ScoEntrepriseView,
p.ScoEtudAddAnnotations,
p.ScoEtudChangeAdr,
p.ScoObservateur,
p.ScoUsersView,
p.ScoView,
),
# Admin est le chef du département, pas le "super admin"
# on doit donc lister toutes ses permissions:
"Admin": (
p.ScoObservateur,
p.ScoView,
p.ScoEnsView,
p.ScoUsersView,
p.ScoEtudAddAnnotations,
p.ScoAbsChange,
p.ScoAbsAddBillet,
p.ScoEntrepriseView,
p.ScoEntrepriseChange,
p.ScoEtudChangeAdr,
p.ScoAbsChange,
p.ScoChangeFormation,
p.ScoEditFormationTags,
p.ScoEditAllNotes,
p.ScoChangePreferences,
p.ScoEditAllEvals,
p.ScoImplement,
p.ScoEditAllNotes,
p.ScoEditApo,
p.ScoEditFormationTags,
p.ScoEnsView,
p.ScoEntrepriseChange,
p.ScoEntrepriseView,
p.ScoEtudAddAnnotations,
p.ScoEtudChangeAdr,
p.ScoEtudChangeGroups,
p.ScoEtudInscrit,
p.ScoImplement,
p.ScoObservateur,
p.ScoUsersAdmin,
p.ScoChangePreferences,
p.ScoUsersView,
p.ScoView,
),
# RespPE est le responsable poursuites d'études
# il peut ajouter des tags sur les formations:

View File

@ -35,7 +35,7 @@ import datetime
import psycopg2
import flask
from flask import g, url_for
from flask import g, url_for, request
from flask_login import current_user
import app.scodoc.sco_utils as scu
@ -164,13 +164,14 @@ def _check_notes(notes, evaluation, mod):
return L, invalids, withoutnotes, absents, tosuppress
def do_evaluation_upload_xls(REQUEST):
def do_evaluation_upload_xls():
"""
Soumission d'un fichier XLS (evaluation_id, notefile)
"""
authuser = REQUEST.AUTHENTICATED_USER
evaluation_id = int(REQUEST.form["evaluation_id"])
comment = REQUEST.form["comment"]
authuser = current_user
vals = scu.get_request_args()
evaluation_id = int(vals["evaluation_id"])
comment = vals["comment"]
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
M = sco_moduleimpl.do_moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[
0
@ -181,7 +182,7 @@ def do_evaluation_upload_xls(REQUEST):
# XXX imaginer un redirect + msg erreur
raise AccessDenied("Modification des notes impossible pour %s" % authuser)
#
diag, lines = sco_excel.excel_file_to_list(REQUEST.form["notefile"])
diag, lines = sco_excel.excel_file_to_list(vals["notefile"])
try:
if not lines:
raise InvalidNoteValue()
@ -287,7 +288,6 @@ def do_evaluation_upload_xls(REQUEST):
def do_evaluation_set_missing(evaluation_id, value, dialog_confirmed=False):
"""Initialisation des notes manquantes"""
# ? evaluation_id = REQUEST.form["evaluation_id"]
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
M = sco_moduleimpl.do_moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[
0
@ -494,9 +494,10 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
}
ndb.quote_dict(aa)
cursor.execute(
"""INSERT INTO notes_notes
(etudid,evaluation_id,value,comment,date,uid)
VALUES (%(etudid)s,%(evaluation_id)s,%(value)s,%(comment)s,%(date)s,%(uid)s)""",
"""INSERT INTO notes_notes
(etudid, evaluation_id, value, comment, date, uid)
VALUES (%(etudid)s,%(evaluation_id)s,%(value)s,%(comment)s,%(date)s,%(uid)s)
""",
aa,
)
changed = True
@ -515,10 +516,10 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
# recopie l'ancienne note dans notes_notes_log, puis update
if do_it:
cursor.execute(
"""INSERT INTO notes_notes_log
"""INSERT INTO notes_notes_log
(etudid,evaluation_id,value,comment,date,uid)
SELECT etudid, evaluation_id, value, comment, date, uid
FROM notes_notes
FROM notes_notes
WHERE etudid=%(etudid)s
and evaluation_id=%(evaluation_id)s
""",
@ -536,8 +537,8 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
if value != scu.NOTES_SUPPRESS:
if do_it:
cursor.execute(
"""UPDATE notes_notes
SET value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
"""UPDATE notes_notes
SET value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
WHERE etudid = %(etudid)s
and evaluation_id = %(evaluation_id)s
""",
@ -550,7 +551,7 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
% (evaluation_id, etudid, oldval)
)
cursor.execute(
"""DELETE FROM notes_notes
"""DELETE FROM notes_notes
WHERE etudid = %(etudid)s
AND evaluation_id = %(evaluation_id)s
""",
@ -587,20 +588,19 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
return nb_changed, nb_suppress, existing_decisions
def saisie_notes_tableur(evaluation_id, group_ids=[], REQUEST=None):
def saisie_notes_tableur(evaluation_id, group_ids=[]):
"""Saisie des notes via un fichier Excel"""
authuser = REQUEST.AUTHENTICATED_USER
authusername = str(authuser)
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
if not evals:
raise ScoValueError("invalid evaluation_id")
E = evals[0]
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
formsemestre_id = M["formsemestre_id"]
if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_id"]):
if not sco_permissions_check.can_edit_notes(current_user, E["moduleimpl_id"]):
return (
html_sco_header.sco_header()
+ "<h2>Modification des notes impossible pour %s</h2>" % authusername
+ "<h2>Modification des notes impossible pour %s</h2>"
% current_user.user_name
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
avez l'autorisation d'effectuer cette opération)</p>
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
@ -657,8 +657,8 @@ def saisie_notes_tableur(evaluation_id, group_ids=[], REQUEST=None):
)
nf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(
("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
(
@ -687,7 +687,7 @@ def saisie_notes_tableur(evaluation_id, group_ids=[], REQUEST=None):
elif nf[0] == -1:
H.append("<p>Annulation</p>")
elif nf[0] == 1:
updiag = do_evaluation_upload_xls(REQUEST)
updiag = do_evaluation_upload_xls()
if updiag[0]:
H.append(updiag[1])
H.append(
@ -711,7 +711,7 @@ def saisie_notes_tableur(evaluation_id, group_ids=[], REQUEST=None):
#
H.append("""</div><h3>Autres opérations</h3><ul>""")
if sco_permissions_check.can_edit_notes(
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"], allow_ens=False
current_user, E["moduleimpl_id"], allow_ens=False
):
H.append(
"""
@ -759,7 +759,7 @@ def saisie_notes_tableur(evaluation_id, group_ids=[], REQUEST=None):
return "\n".join(H)
def feuille_saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
def feuille_saisie_notes(evaluation_id, group_ids=[]):
"""Document Excel pour saisie notes dans l'évaluation et les groupes indiqués"""
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
if not evals:
@ -829,7 +829,8 @@ def feuille_saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
filename = "notes_%s_%s.xlsx" % (evalname, gr_title_filename)
xls = sco_excel.excel_feuille_saisie(E, sem["titreannee"], description, lines=L)
return sco_excel.send_excel_file(REQUEST, xls, filename)
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
# return sco_excel.send_excel_file(xls, filename)
def has_existing_decision(M, E, etudid):
@ -856,11 +857,9 @@ def has_existing_decision(M, E, etudid):
# Nouveau formulaire saisie notes (2016)
def saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
def saisie_notes(evaluation_id, group_ids=[]):
"""Formulaire saisie notes d'une évaluation pour un groupe"""
authuser = REQUEST.AUTHENTICATED_USER
authusername = str(authuser)
group_ids = [int(group_id) for group_id in group_ids]
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
if not evals:
raise ScoValueError("invalid evaluation_id")
@ -871,10 +870,11 @@ def saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
formsemestre_id = M["formsemestre_id"]
# Check access
# (admin, respformation, and responsable_id)
if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_id"]):
if not sco_permissions_check.can_edit_notes(current_user, E["moduleimpl_id"]):
return (
html_sco_header.sco_header()
+ "<h2>Modification des notes impossible pour %s</h2>" % authusername
+ "<h2>Modification des notes impossible pour %s</h2>"
% current_user.user_name
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
avez l'autorisation d'effectuer cette opération)</p>
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
@ -946,9 +946,7 @@ def saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
moduleimpl_id=E["moduleimpl_id"],
)
form = _form_saisie_notes(
E, M, groups_infos.group_ids, destination=destination, REQUEST=REQUEST
)
form = _form_saisie_notes(E, M, groups_infos.group_ids, destination=destination)
if form is None:
log(f"redirecting to {destination}")
return flask.redirect(destination)
@ -1039,7 +1037,7 @@ def _get_sorted_etuds(E, etudids, formsemestre_id):
return etuds
def _form_saisie_notes(E, M, group_ids, destination="", REQUEST=None):
def _form_saisie_notes(E, M, group_ids, destination=""):
"""Formulaire HTML saisie des notes dans l'évaluation E du moduleimpl M
pour les groupes indiqués.
@ -1154,9 +1152,9 @@ def _form_saisie_notes(E, M, group_ids, destination="", REQUEST=None):
"attributes": [
'class="note%s"' % classdem,
disabled_attr,
"data-last-saved-value=%s" % e["val"],
"data-orig-value=%s" % e["val"],
"data-etudid=%s" % etudid,
'data-last-saved-value="%s"' % e["val"],
'data-orig-value="%s"' % e["val"],
'data-etudid="%s"' % etudid,
],
"template": """<tr%(item_dom_attr)s class="etud_elem """
+ " ".join(etud_classes)
@ -1181,7 +1179,7 @@ def _form_saisie_notes(E, M, group_ids, destination="", REQUEST=None):
tf = TF(
destination,
REQUEST.form,
scu.get_request_args(),
descr,
initvalues=initvalues,
submitbutton=False,
@ -1220,9 +1218,9 @@ def _form_saisie_notes(E, M, group_ids, destination="", REQUEST=None):
return None
def save_note(etudid=None, evaluation_id=None, value=None, comment="", REQUEST=None):
def save_note(etudid=None, evaluation_id=None, value=None, comment=""):
"""Enregistre une note (ajax)"""
authuser = REQUEST.AUTHENTICATED_USER
authuser = current_user
log(
"save_note: evaluation_id=%s etudid=%s uid=%s value=%s"
% (evaluation_id, etudid, authuser, value)
@ -1259,7 +1257,7 @@ def save_note(etudid=None, evaluation_id=None, value=None, comment="", REQUEST=N
else:
result["history_menu"] = "" # no update needed
result["status"] = "ok"
return scu.sendJSON(REQUEST, result)
return scu.sendJSON(result)
def get_note_history_menu(evaluation_id, etudid):
@ -1290,7 +1288,7 @@ def get_note_history_menu(evaluation_id, etudid):
nv = "" # ne repete pas la valeur de la note courante
else:
# ancienne valeur
nv = '<span class="histvalue">: %s</span>' % dispnote
nv = ": %s" % dispnote
first = False
if i["comment"]:
comment = ' <span class="histcomment">%s</span>' % i["comment"]

View File

@ -171,7 +171,7 @@ class SemSet(dict):
def remove(self, formsemestre_id):
ndb.SimpleQuery(
"""DELETE FROM notes_semset_formsemestre
WHERE id=%(semset_id)s
WHERE semset_id=%(semset_id)s
AND formsemestre_id=%(formsemestre_id)s
""",
{"formsemestre_id": formsemestre_id, "semset_id": self.semset_id},
@ -418,7 +418,7 @@ def do_semset_remove_sem(semset_id, formsemestre_id):
# ----------------------------------------
def semset_page(format="html", REQUEST=None):
def semset_page(format="html"):
"""Page avec liste semsets:
Table avec : date_debut date_fin titre liste des semestres
"""
@ -468,7 +468,7 @@ def semset_page(format="html", REQUEST=None):
preferences=sco_preferences.SemPreferences(),
)
if format != "html":
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
page_title = "Ensembles de semestres"
H = [

View File

@ -32,7 +32,7 @@ import time
import pprint
from operator import itemgetter
from flask import g, url_for, send_file
from flask import g, url_for
from flask_login import current_user
import app.scodoc.sco_utils as scu
@ -112,6 +112,7 @@ def formsemestre_synchro_etuds(
base_url = url_for(
"notes.formsemestre_synchro_etuds",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
anneeapogee=anneeapogee or None, # si None, le param n'est pas dans l'URL
)
@ -125,9 +126,13 @@ def formsemestre_synchro_etuds(
etuds = etuds.split(",") # vient du form de confirmation
elif isinstance(etuds, int):
etuds = [etuds]
if isinstance(inscrits_without_key, str):
if isinstance(inscrits_without_key, int):
inscrits_without_key = [inscrits_without_key]
elif isinstance(inscrits_without_key, str):
inscrits_without_key = inscrits_without_key.split(",")
elif not isinstance(inscrits_without_key, list):
raise ValueError("invalid type for inscrits_without_key")
inscrits_without_key = [int(x) for x in inscrits_without_key if x]
(
etuds_by_cat,
a_importer,
@ -146,11 +151,11 @@ def formsemestre_synchro_etuds(
base_url=base_url,
read_only=read_only,
)
return send_file(
return scu.send_file(
xls,
mimetype=scu.XLS_MIMETYPE,
download_name=scu.sanitize_filename(filename + scu.XLSX_SUFFIX),
as_attachment=True,
mime=scu.XLS_MIMETYPE,
filename=filename,
suffix=scu.XLSX_SUFFIX,
)
H = [header]
@ -199,31 +204,38 @@ def formsemestre_synchro_etuds(
)
H.append("</ol>")
if a_desinscrire or a_desinscrire_without_key:
if a_desinscrire:
H.append("<h3>Etudiants à désinscrire :</h3><ol>")
for key in a_desinscrire:
etud = sco_etud.get_etud_info(filled=True, code_nip=key)[0]
H.append('<li class="desinscription">%(nomprenom)s</li>' % etud)
H.append("</ol>")
if a_desinscrire_without_key:
H.append("<h3>Etudiants à désinscrire (sans code):</h3><ol>")
for etudid in a_desinscrire_without_key:
etud = inscrits_without_key_all[etudid]
sco_etud.format_etud_ident(etud)
H.append('<li class="desinscription">%(nomprenom)s</li>' % etud)
H.append("</ol>")
if not a_importer and not a_inscrire and not a_desinscrire:
todo = (
a_importer or a_inscrire or a_desinscrire or a_desinscrire_without_key
)
if not todo:
H.append("""<h3>Il n'y a rien à modifier !</h3>""")
H.append(
scu.confirm_dialog(
dest_url="formsemestre_synchro_etuds",
add_headers=False,
cancel_url="formsemestre_synchro_etuds?formsemestre_id="
+ str(formsemestre_id),
OK="Effectuer l'opération",
OK="Effectuer l'opération" if todo else "OK",
parameters={
"formsemestre_id": formsemestre_id,
"etuds": ",".join(etuds),
"inscrits_without_key": ",".join(inscrits_without_key),
"inscrits_without_key": ",".join(
[str(x) for x in inscrits_without_key]
),
"submitted": 1,
"anneeapogee": anneeapogee,
},
@ -317,7 +329,8 @@ def build_page(
"""
% sem,
"""
Année Apogée: <select id="anneeapogee" name="anneeapogee" onchange="document.location='formsemestre_synchro_etuds?formsemestre_id=%s&anneeapogee='+document.getElementById('anneeapogee').value">"""
Année Apogée: <select id="anneeapogee" name="anneeapogee"
onchange="document.location='formsemestre_synchro_etuds?formsemestre_id=%s&anneeapogee='+document.getElementById('anneeapogee').value">"""
% (sem["formsemestre_id"]),
"\n".join(options),
"""
@ -430,17 +443,20 @@ def list_synch(sem, anneeapogee=None):
return etuds
#
r = {
"etuds_ok": {
"etuds": set_to_sorted_list(etuds_ok, is_inscrit=True),
boites = {
"etuds_a_importer": {
"etuds": set_to_sorted_list(a_importer, is_inscrit=True, etud_apo=True),
"infos": {
"id": "etuds_ok",
"title": "Etudiants dans Apogée et déjà inscrits",
"help": "Ces etudiants sont inscrits dans le semestre ScoDoc et sont présents dans Apogée: tout est donc correct. Décocher les étudiants que vous souhaitez désinscrire.",
"id": "etuds_a_importer",
"title": "Etudiants dans Apogée à importer",
"help": """Ces étudiants sont inscrits dans cette étape Apogée mais ne sont pas connus par ScoDoc:
cocher les noms à importer et inscrire puis appuyer sur le bouton "Appliquer".""",
"title_target": "",
"with_checkbox": True,
"etud_key": EKEY_SCO,
"etud_key": EKEY_APO, # clé à stocker dans le formulaire html
"filename": "etuds_a_importer",
},
"nomprenoms": etudsapo_ident,
},
"etuds_noninscrits": {
"etuds": set_to_sorted_list(etuds_noninscrits, is_inscrit=True),
@ -453,20 +469,9 @@ def list_synch(sem, anneeapogee=None):
"title_target": "",
"with_checkbox": True,
"etud_key": EKEY_SCO,
"filename": "etuds_non_inscrits",
},
},
"etuds_a_importer": {
"etuds": set_to_sorted_list(a_importer, is_inscrit=True, etud_apo=True),
"infos": {
"id": "etuds_a_importer",
"title": "Etudiants dans Apogée à importer",
"help": """Ces étudiants sont inscrits dans cette étape Apogée mais ne sont pas connus par ScoDoc: cocher les noms à importer et inscrire puis appuyer sur le bouton "Appliquer".""",
"title_target": "",
"with_checkbox": True,
"etud_key": EKEY_APO, # clé à stocker dans le formulaire html
},
"nomprenoms": etudsapo_ident,
},
"etuds_nonapogee": {
"etuds": set_to_sorted_list(etuds_nonapogee, is_inscrit=True),
"infos": {
@ -478,6 +483,7 @@ def list_synch(sem, anneeapogee=None):
"title_target": "",
"with_checkbox": True,
"etud_key": EKEY_SCO,
"filename": "etuds_non_apogee",
},
},
"inscrits_without_key": {
@ -489,11 +495,25 @@ def list_synch(sem, anneeapogee=None):
"title_target": "",
"with_checkbox": True,
"checkbox_name": "inscrits_without_key",
"filename": "inscrits_without_key",
},
},
"etuds_ok": {
"etuds": set_to_sorted_list(etuds_ok, is_inscrit=True),
"infos": {
"id": "etuds_ok",
"title": "Etudiants dans Apogée et déjà inscrits",
"help": """Ces etudiants sont inscrits dans le semestre ScoDoc et sont présents dans Apogée:
tout est donc correct. Décocher les étudiants que vous souhaitez désinscrire.""",
"title_target": "",
"with_checkbox": True,
"etud_key": EKEY_SCO,
"filename": "etuds_inscrits_ok_apo",
},
},
}
return (
r,
boites,
a_importer,
etuds_noninscrits,
inscrits_set,

View File

@ -199,7 +199,7 @@ class ModuleTag(ScoTag):
# API
def module_tag_search(term, REQUEST=None):
def module_tag_search(term):
"""List all used tag names (for auto-completion)"""
# restrict charset to avoid injections
if not scu.ALPHANUM_EXP.match(term):
@ -214,7 +214,7 @@ def module_tag_search(term, REQUEST=None):
)
data = [x["title"] for x in r]
return scu.sendJSON(REQUEST, data)
return scu.sendJSON(data)
def module_tag_list(module_id=""):

View File

@ -44,7 +44,7 @@ from reportlab.lib import colors
from PIL import Image as PILImage
import flask
from flask import url_for, g, send_file
from flask import url_for, g, send_file, request
from app import log
import app.scodoc.sco_utils as scu
@ -66,7 +66,6 @@ from app.scodoc import sco_etud
def trombino(
REQUEST=None,
group_ids=[], # liste des groupes à afficher
formsemestre_id=None, # utilisé si pas de groupes selectionné
etat=None,
@ -83,28 +82,26 @@ def trombino(
#
if format != "html" and not dialog_confirmed:
ok, dialog = check_local_photos_availability(
groups_infos, REQUEST, format=format
)
ok, dialog = check_local_photos_availability(groups_infos, format=format)
if not ok:
return dialog
if format == "zip":
return _trombino_zip(groups_infos)
elif format == "pdf":
return _trombino_pdf(groups_infos, REQUEST)
return _trombino_pdf(groups_infos)
elif format == "pdflist":
return _listeappel_photos_pdf(groups_infos, REQUEST)
return _listeappel_photos_pdf(groups_infos)
else:
raise Exception("invalid format")
# return _trombino_html_header() + trombino_html( group, members, REQUEST=REQUEST) + html_sco_header.sco_footer( REQUEST)
# return _trombino_html_header() + trombino_html( group, members) + html_sco_header.sco_footer()
def _trombino_html_header():
return html_sco_header.sco_header(javascripts=["js/trombino.js"])
def trombino_html(groups_infos, REQUEST=None):
def trombino_html(groups_infos):
"HTML snippet for trombino (with title and menu)"
menuTrombi = [
{
@ -150,7 +147,7 @@ def trombino_html(groups_infos, REQUEST=None):
% t["etudid"]
)
if sco_photos.etud_photo_is_local(t, size="small"):
foto = sco_photos.etud_photo_html(t, title="", REQUEST=REQUEST)
foto = sco_photos.etud_photo_html(t, title="")
else: # la photo n'est pas immédiatement dispo
foto = (
'<span class="unloaded_img" id="%s"><img border="0" height="90" alt="en cours" src="/ScoDoc/static/icons/loading.jpg"/></span>'
@ -183,7 +180,7 @@ def trombino_html(groups_infos, REQUEST=None):
return "\n".join(H)
def check_local_photos_availability(groups_infos, REQUEST, format=""):
def check_local_photos_availability(groups_infos, format=""):
"""Verifie que toutes les photos (des gropupes indiqués) sont copiées localement
dans ScoDoc (seules les photos dont nous disposons localement peuvent être exportées
en pdf ou en zip).
@ -245,7 +242,7 @@ def _trombino_zip(groups_infos):
# Copy photos from portal to ScoDoc
def trombino_copy_photos(group_ids=[], REQUEST=None, dialog_confirmed=False):
def trombino_copy_photos(group_ids=[], dialog_confirmed=False):
"Copy photos from portal to ScoDoc (overwriting local copy)"
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
back_url = "groups_view?%s&curtab=tab-photos" % groups_infos.groups_query_args
@ -316,7 +313,7 @@ def _get_etud_platypus_image(t, image_width=2 * cm):
raise
def _trombino_pdf(groups_infos, REQUEST):
def _trombino_pdf(groups_infos):
"Send photos as pdf page"
# Generate PDF page
filename = "trombino_%s" % groups_infos.groups_filename + ".pdf"
@ -394,7 +391,7 @@ def _trombino_pdf(groups_infos, REQUEST):
# --------------------- Sur une idée de l'IUT d'Orléans:
def _listeappel_photos_pdf(groups_infos, REQUEST):
def _listeappel_photos_pdf(groups_infos):
"Doc pdf pour liste d'appel avec photos"
filename = "trombino_%s" % groups_infos.groups_filename + ".pdf"
sem = groups_infos.formsemestre # suppose 1 seul semestre
@ -466,11 +463,11 @@ def _listeappel_photos_pdf(groups_infos, REQUEST):
document.build(objects)
data = report.getvalue()
return scu.sendPDFFile(REQUEST, data, filename)
return scu.sendPDFFile(data, filename)
# --------------------- Upload des photos de tout un groupe
def photos_generate_excel_sample(group_ids=[], REQUEST=None):
def photos_generate_excel_sample(group_ids=[]):
"""Feuille excel pour import fichiers photos"""
fmt = sco_import_etuds.sco_import_format()
data = sco_import_etuds.sco_import_generate_excel_sample(
@ -486,10 +483,13 @@ def photos_generate_excel_sample(group_ids=[], REQUEST=None):
],
extra_cols=["fichier_photo"],
)
return sco_excel.send_excel_file(REQUEST, data, "ImportPhotos" + scu.XLSX_SUFFIX)
return scu.send_file(
data, "ImportPhotos", scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE, attached=True
)
# return sco_excel.send_excel_file(data, "ImportPhotos" + scu.XLSX_SUFFIX)
def photos_import_files_form(group_ids=[], REQUEST=None):
def photos_import_files_form(group_ids=[]):
"""Formulaire pour importation photos"""
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
back_url = "groups_view?%s&curtab=tab-photos" % groups_infos.groups_query_args
@ -514,10 +514,11 @@ def photos_import_files_form(group_ids=[], REQUEST=None):
% groups_infos.groups_query_args,
]
F = html_sco_header.sco_footer()
REQUEST.form["group_ids"] = groups_infos.group_ids
vals = scu.get_request_args()
vals["group_ids"] = groups_infos.group_ids
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
vals,
(
("xlsfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}),
("zipfile", {"title": "Fichier zip:", "input_type": "file", "size": 40}),
@ -534,11 +535,10 @@ def photos_import_files_form(group_ids=[], REQUEST=None):
group_ids=tf[2]["group_ids"],
xlsfile=tf[2]["xlsfile"],
zipfile=tf[2]["zipfile"],
REQUEST=REQUEST,
)
def photos_import_files(group_ids=[], xlsfile=None, zipfile=None, REQUEST=None):
def photos_import_files(group_ids=[], xlsfile=None, zipfile=None):
"""Importation des photos"""
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
back_url = "groups_view?%s&curtab=tab-photos" % groups_infos.groups_query_args

View File

@ -56,7 +56,6 @@ N_PER_ROW = 5
def pdf_trombino_tours(
group_ids=[], # liste des groupes à afficher
formsemestre_id=None, # utilisé si pas de groupes selectionné
REQUEST=None,
):
"""Generation du trombinoscope en fichier PDF"""
# Informations sur les groupes à afficher:
@ -272,7 +271,7 @@ def pdf_trombino_tours(
document.build(objects)
data = report.getvalue()
return scu.sendPDFFile(REQUEST, data, filename)
return scu.sendPDFFile(data, filename)
# Feuille d'absences en pdf avec photos:
@ -281,7 +280,6 @@ def pdf_trombino_tours(
def pdf_feuille_releve_absences(
group_ids=[], # liste des groupes à afficher
formsemestre_id=None, # utilisé si pas de groupes selectionné
REQUEST=None,
):
"""Generation de la feuille d'absence en fichier PDF, avec photos"""
@ -466,4 +464,4 @@ def pdf_feuille_releve_absences(
document.build(objects)
data = report.getvalue()
return scu.sendPDFFile(REQUEST, data, filename)
return scu.sendPDFFile(data, filename)

View File

@ -54,6 +54,7 @@ Solution proposée (nov 2014):
"""
import flask
from flask import request
from flask_login import current_user
import app.scodoc.notesdb as ndb
@ -134,9 +135,7 @@ def external_ue_create(
return moduleimpl_id
def external_ue_inscrit_et_note(
moduleimpl_id, formsemestre_id, notes_etuds, REQUEST=None
):
def external_ue_inscrit_et_note(moduleimpl_id, formsemestre_id, notes_etuds):
log(
"external_ue_inscrit_et_note(moduleimpl_id=%s, notes_etuds=%s)"
% (moduleimpl_id, notes_etuds)
@ -146,7 +145,6 @@ def external_ue_inscrit_et_note(
moduleimpl_id,
formsemestre_id,
list(notes_etuds.keys()),
REQUEST=REQUEST,
)
# Création d'une évaluation si il n'y en a pas déjà:
@ -157,7 +155,6 @@ def external_ue_inscrit_et_note(
else:
# crée une évaluation:
evaluation_id = sco_evaluations.do_evaluation_create(
REQUEST=REQUEST,
moduleimpl_id=moduleimpl_id,
note_max=20.0,
coefficient=1.0,
@ -168,7 +165,7 @@ def external_ue_inscrit_et_note(
)
# Saisie des notes
_, _, _ = sco_saisie_notes._notes_add(
REQUEST.AUTHENTICATED_USER,
current_user,
evaluation_id,
list(notes_etuds.items()),
do_it=True,
@ -200,7 +197,7 @@ def get_external_moduleimpl_id(formsemestre_id, ue_id):
# Web function
def external_ue_create_form(formsemestre_id, etudid, REQUEST=None):
def external_ue_create_form(formsemestre_id, etudid):
"""Formulaire création UE externe + inscription étudiant et saisie note
- Demande UE: peut-être existante (liste les UE externes de cette formation),
ou sinon spécifier titre, acronyme, type, ECTS
@ -221,7 +218,6 @@ def external_ue_create_form(formsemestre_id, etudid, REQUEST=None):
H = [
html_sco_header.html_sem_header(
REQUEST,
"Ajout d'une UE externe pour %(nomprenom)s" % etud,
sem,
javascripts=["js/sco_ue_external.js"],
@ -248,8 +244,8 @@ def external_ue_create_form(formsemestre_id, etudid, REQUEST=None):
default_label = "Aucune UE externe existante"
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(
("formsemestre_id", {"input_type": "hidden"}),
("etudid", {"input_type": "hidden"}),
@ -365,6 +361,5 @@ def external_ue_create_form(formsemestre_id, etudid, REQUEST=None):
moduleimpl_id,
formsemestre_id,
{etudid: note_value},
REQUEST=REQUEST,
)
return flask.redirect(bull_url + "&head_message=Ajout%20effectué")

View File

@ -46,6 +46,8 @@ Opérations:
"""
import datetime
from flask import request
from app.scodoc.intervals import intervalmap
import app.scodoc.sco_utils as scu
@ -144,7 +146,7 @@ def list_operations(evaluation_id):
return Ops
def evaluation_list_operations(evaluation_id, REQUEST=None):
def evaluation_list_operations(evaluation_id):
"""Page listing operations on evaluation"""
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
@ -167,10 +169,10 @@ def evaluation_list_operations(evaluation_id, REQUEST=None):
% (E["description"], E["jour"]),
preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
)
return tab.make_page(REQUEST=REQUEST)
return tab.make_page()
def formsemestre_list_saisies_notes(formsemestre_id, format="html", REQUEST=None):
def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
"""Table listant toutes les opérations de saisies de notes, dans toutes
les évaluations du semestre.
"""
@ -217,15 +219,15 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html", REQUEST=None
html_sortable=True,
caption="Saisies de notes dans %s" % sem["titreannee"],
preferences=sco_preferences.SemPreferences(formsemestre_id),
base_url="%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id),
base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id),
origin="Généré par %s le " % sco_version.SCONAME
+ scu.timedate_human_repr()
+ "",
)
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
def get_note_history(evaluation_id, etudid, REQUEST=None, fmt=""):
def get_note_history(evaluation_id, etudid, fmt=""):
"""Historique d'une note
= liste chronologique d'opérations, la plus récente d'abord
[ { 'value', 'date', 'comment', 'uid' } ]
@ -261,7 +263,7 @@ def get_note_history(evaluation_id, etudid, REQUEST=None, fmt=""):
x["user_name"] = sco_users.user_info(x["uid"])["nomcomplet"]
if fmt == "json":
return scu.sendJSON(REQUEST, history)
return scu.sendJSON(history)
else:
return history

View File

@ -30,7 +30,7 @@
# Anciennement ZScoUsers.py, fonctions de gestion des données réécrite avec flask/SQLAlchemy
from flask import url_for, g
from flask import url_for, g, request
from flask_login import current_user
import cracklib # pylint: disable=import-error
@ -51,11 +51,7 @@ import app.scodoc.sco_utils as scu
from app.scodoc.sco_exceptions import (
AccessDenied,
ScoException,
ScoValueError,
ScoInvalidDateError,
ScoLockedFormError,
ScoGenError,
)
@ -81,12 +77,12 @@ def is_valid_password(cleartxt):
# ---------------
def index_html(REQUEST, all_depts=False, with_inactives=False, format="html"):
def index_html(all_depts=False, with_inactives=False, format="html"):
"gestion utilisateurs..."
all_depts = int(all_depts)
with_inactives = int(with_inactives)
H = [html_sco_header.html_sem_header(REQUEST, "Gestion des utilisateurs")]
H = [html_sco_header.html_sem_header("Gestion des utilisateurs")]
if current_user.has_permission(Permission.ScoUsersAdmin, g.scodoc_dept):
H.append(
@ -117,7 +113,7 @@ def index_html(REQUEST, all_depts=False, with_inactives=False, format="html"):
<input type="checkbox" name="all_depts" value="1" onchange="document.f.submit();" %s>Tous les départements</input>
<input type="checkbox" name="with_inactives" value="1" onchange="document.f.submit();" %s>Avec anciens utilisateurs</input>
</form></p>"""
% (REQUEST.URL0, checked, olds_checked)
% (request.base_url, checked, olds_checked)
)
L = list_users(
@ -125,7 +121,6 @@ def index_html(REQUEST, all_depts=False, with_inactives=False, format="html"):
all_depts=all_depts,
with_inactives=with_inactives,
format=format,
REQUEST=REQUEST,
with_links=current_user.has_permission(Permission.ScoUsersAdmin, g.scodoc_dept),
)
if format != "html":
@ -142,7 +137,6 @@ def list_users(
with_inactives=False, # inclut les anciens utilisateurs (status "old")
format="html",
with_links=True,
REQUEST=None,
):
"List users, returns a table in the specified format"
from app.scodoc.sco_permissions_check import can_handle_passwd
@ -212,12 +206,12 @@ def list_users(
html_class="table_leftalign list_users",
html_with_td_classes=True,
html_sortable=True,
base_url="%s?all=%s" % (REQUEST.URL0, all),
base_url="%s?all=%s" % (request.base_url, all),
pdf_link=False, # table is too wide to fit in a paper page => disable pdf
preferences=sco_preferences.SemPreferences(),
)
return tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
return tab.make_page(format=format, with_html_headers=False)
def get_user_list(dept=None, with_inactives=False):

View File

@ -39,21 +39,16 @@ import os
import pydot
import re
import requests
import six
import six.moves._thread
import sys
import _thread
import time
import traceback
import types
import unicodedata
import urllib
from xml.etree import ElementTree
from flask import g, current_app
from urllib.parse import urlparse, parse_qsl, urlunparse, urlencode
from PIL import Image as PILImage
from flask import g, url_for, request
from flask import g, request
from flask import url_for, make_response
from config import Config
from app import log
@ -217,7 +212,7 @@ def group_by_key(d, key):
# ----- Global lock for critical sections (except notes_tables caches)
GSL = six.moves._thread.allocate_lock() # Global ScoDoc Lock
GSL = _thread.allocate_lock() # Global ScoDoc Lock
SCODOC_DIR = Config.SCODOC_DIR
@ -228,7 +223,7 @@ SCODOC_CFG_DIR = os.path.join(Config.SCODOC_VAR_DIR, "config")
SCODOC_VERSION_DIR = os.path.join(SCODOC_CFG_DIR, "version")
# ----- Repertoire tmp : /opt/scodoc-data/tmp
SCO_TMP_DIR = os.path.join(Config.SCODOC_VAR_DIR, "tmp")
if not os.path.exists(SCO_TMP_DIR):
if not os.path.exists(SCO_TMP_DIR) and os.path.exists(Config.SCODOC_VAR_DIR):
os.mkdir(SCO_TMP_DIR, 0o755)
# ----- Les logos: /opt/scodoc-data/config/logos
SCODOC_LOGOS_DIR = os.path.join(SCODOC_CFG_DIR, "logos")
@ -296,16 +291,39 @@ SCO_DEV_MAIL = "emmanuel.viennet@gmail.com" # SVP ne pas changer
# Adresse pour l'envoi des dumps (pour assistance technnique):
# ne pas changer (ou vous perdez le support)
SCO_DUMP_UP_URL = "https://scodoc.iutv.univ-paris13.fr/scodoc-installmgr/upload-dump"
# SCO_DUMP_UP_URL = "http://192.168.56.1:5000/upload_dump"
CSV_FIELDSEP = ";"
CSV_LINESEP = "\n"
CSV_MIMETYPE = "text/comma-separated-values"
CSV_SUFFIX = ".csv"
JSON_MIMETYPE = "application/json"
JSON_SUFFIX = ".json"
PDF_MIMETYPE = "application/pdf"
PDF_SUFFIX = ".pdf"
XLS_MIMETYPE = "application/vnd.ms-excel"
XLS_SUFFIX = ".xls"
XLSX_MIMETYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
XLSX_SUFFIX = ".xlsx"
PDF_MIMETYPE = "application/pdf"
XML_MIMETYPE = "text/xml"
JSON_MIMETYPE = "application/json"
XML_SUFFIX = ".xml"
def get_mime_suffix(format_code: str) -> tuple[str, str]:
"""Returns (MIME, SUFFIX) from format_code == "xls", "xml", ...
SUFFIX includes the dot: ".xlsx", ".xml", ...
"xls" and "xlsx" format codes give XLSX
"""
d = {
"csv": (CSV_MIMETYPE, CSV_SUFFIX),
"xls": (XLSX_MIMETYPE, XLSX_SUFFIX),
"xlsx": (XLSX_MIMETYPE, XLSX_SUFFIX),
"pdf": (PDF_MIMETYPE, PDF_SUFFIX),
"xml": (XML_MIMETYPE, XML_SUFFIX),
"json": (JSON_MIMETYPE, JSON_SUFFIX),
}
return d[format_code]
# Admissions des étudiants
# Différents types de voies d'admission:
@ -388,6 +406,18 @@ def unescape_html(s):
return s
def build_url_query(url: str, **params) -> str:
"""Add parameters to existing url, as a query string"""
url_parse = urlparse(url)
query = url_parse.query
url_dict = dict(parse_qsl(query))
url_dict.update(params)
url_new_query = urlencode(url_dict)
url_parse = url_parse._replace(query=url_new_query)
new_url = urlunparse(url_parse)
return new_url
# test if obj is iterable (but not a string)
isiterable = lambda obj: getattr(obj, "__iter__", False)
@ -445,6 +475,22 @@ def suppress_accents(s):
return s # may be int
class PurgeChars:
"""delete all chars except those belonging to the specified string"""
def __init__(self, allowed_chars=""):
self.allowed_chars_set = {ord(c) for c in allowed_chars}
def __getitem__(self, x):
if x not in self.allowed_chars_set:
return None
raise LookupError()
def purge_chars(s, allowed_chars=""):
return s.translate(PurgeChars(allowed_chars=allowed_chars))
def sanitize_string(s):
"""s is an ordinary string, encoding given by SCO_ENCODING"
suppress accents and chars interpreted in XML
@ -457,14 +503,17 @@ def sanitize_string(s):
return suppress_accents(s.translate(trans)).replace(" ", "_").replace("\t", "_")
_BAD_FILENAME_CHARS = str.maketrans("", "", ":/\\")
_BAD_FILENAME_CHARS = str.maketrans("", "", ":/\\&[]*?'")
def make_filename(name):
"""Try to convert name to a reasonable filename
without spaces, (back)slashes, : and without accents
"""
return suppress_accents(name.translate(_BAD_FILENAME_CHARS)).replace(" ", "_")
return (
suppress_accents(name.translate(_BAD_FILENAME_CHARS)).replace(" ", "_")
or "scodoc"
)
VALID_CARS = (
@ -490,30 +539,21 @@ def is_valid_filename(filename):
return VALID_EXP.match(filename)
def sendCSVFile(REQUEST, data, filename):
"""publication fichier.
(on ne doit rien avoir émis avant, car ici sont générés les entetes)
"""
filename = (
unescape_html(suppress_accents(filename)).replace("&", "").replace(" ", "_")
)
REQUEST.RESPONSE.setHeader("content-type", CSV_MIMETYPE)
REQUEST.RESPONSE.setHeader(
"content-disposition", 'attachment; filename="%s"' % filename
)
return data
def bul_filename(sem, etud, format):
"""Build a filename for this bulletin"""
dt = time.strftime("%Y-%m-%d")
filename = f"bul-{sem['titre_num']}-{dt}-{etud['nom']}.{format}"
filename = make_filename(filename)
return filename
def sendPDFFile(REQUEST, data, filename):
filename = (
unescape_html(suppress_accents(filename)).replace("&", "").replace(" ", "_")
)
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", PDF_MIMETYPE)
REQUEST.RESPONSE.setHeader(
"content-disposition", 'attachment; filename="%s"' % filename
)
return data
def sendCSVFile(data, filename): # DEPRECATED utiliser send_file
"""publication fichier CSV."""
return send_file(data, filename=filename, mime=CSV_MIMETYPE, attached=True)
def sendPDFFile(data, filename): # DEPRECATED utiliser send_file
return send_file(data, filename=filename, mime=PDF_MIMETYPE, attached=True)
class ScoDocJSONEncoder(json.JSONEncoder):
@ -526,38 +566,88 @@ class ScoDocJSONEncoder(json.JSONEncoder):
return json.JSONEncoder.default(self, o)
def sendJSON(REQUEST, data):
def sendJSON(data, attached=False):
js = json.dumps(data, indent=1, cls=ScoDocJSONEncoder)
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", JSON_MIMETYPE)
return js
return send_file(
js, filename="sco_data.json", mime=JSON_MIMETYPE, attached=attached
)
def sendXML(REQUEST, data, tagname=None, force_outer_xml_tag=True):
def sendXML(data, tagname=None, force_outer_xml_tag=True, attached=False):
if type(data) != list:
data = [data] # always list-of-dicts
if force_outer_xml_tag:
data = [{tagname: data}]
tagname += "_list"
doc = sco_xml.simple_dictlist2xml(data, tagname=tagname)
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
return doc
return send_file(doc, filename="sco_data.xml", mime=XML_MIMETYPE, attached=attached)
def sendResult(REQUEST, data, name=None, format=None, force_outer_xml_tag=True):
def sendResult(data, name=None, format=None, force_outer_xml_tag=True, attached=False):
if (format is None) or (format == "html"):
return data
elif format == "xml": # name is outer tagname
return sendXML(
REQUEST, data, tagname=name, force_outer_xml_tag=force_outer_xml_tag
data,
tagname=name,
force_outer_xml_tag=force_outer_xml_tag,
attached=attached,
)
elif format == "json":
return sendJSON(REQUEST, data)
return sendJSON(data, attached=attached)
else:
raise ValueError("invalid format: %s" % format)
def send_file(data, filename="", suffix="", mime=None, attached=None):
"""Build Flask Response for file download of given type
By default (attached is None), json and xml are inlined and otrher types are attached.
"""
if attached is None:
if mime == XML_MIMETYPE or mime == JSON_MIMETYPE:
attached = False
else:
attached = True
# if attached and not filename:
# raise ValueError("send_file: missing attachement filename")
if filename:
if suffix:
filename += suffix
filename = make_filename(filename)
response = make_response(data)
response.headers["Content-Type"] = mime
if attached and filename:
response.headers["Content-Disposition"] = 'attachment; filename="%s"' % filename
return response
def get_request_args():
"""returns a dict with request (POST or GET) arguments
converted to suit legacy Zope style (scodoc7) functions.
"""
# copy to get a mutable object (necessary for TrivialFormulator and several methods)
if request.method == "POST":
# request.form is a werkzeug.datastructures.ImmutableMultiDict
# must copy to get a mutable version (needed by TrivialFormulator)
vals = request.form.copy()
if request.files:
# Add files in form:
vals.update(request.files)
for k in request.form:
if k.endswith(":list"):
vals[k[:-5]] = request.form.getlist(k)
elif request.method == "GET":
vals = {}
for k in request.args:
# current_app.logger.debug("%s\t%s" % (k, request.args.getlist(k)))
if k.endswith(":list"):
vals[k[:-5]] = request.args.getlist(k)
else:
values = request.args.getlist(k)
vals[k] = values[0] if len(values) == 1 else values
return vals
def get_scodoc_version():
"return a string identifying ScoDoc version"
return sco_version.SCOVERSION
@ -759,45 +849,6 @@ def AnneeScolaire(sco_year=None):
return year
def log_unknown_etud(REQUEST=None, format="html"):
"""Log request: cas ou getEtudInfo n'a pas ramene de resultat"""
etudid = REQUEST.form.get("etudid", "?")
code_nip = REQUEST.form.get("code_nip", "?")
code_ine = REQUEST.form.get("code_ine", "?")
log(
"unknown student: etudid=%s code_nip=%s code_ine=%s"
% (etudid, code_nip, code_ine)
)
return _sco_error_response("unknown student", format=format, REQUEST=REQUEST)
# XXX #sco8 à tester ou ré-écrire
def _sco_error_response(msg, format="html", REQUEST=None):
"""Send an error message to the client, in html or xml format."""
REQUEST.RESPONSE.setStatus(404, reason=msg)
if format == "html" or format == "pdf":
raise sco_exceptions.ScoValueError(msg)
elif format == "xml":
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
doc = ElementTree.Element("error", msg=msg)
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(SCO_ENCODING)
elif format == "json":
REQUEST.RESPONSE.setHeader("content-type", JSON_MIMETYPE)
return "undefined" # XXX voir quoi faire en cas d'erreur json
else:
raise ValueError("ScoErrorResponse: invalid format")
def return_text_if_published(val, REQUEST):
"""Pour les méthodes publiées qui ramènent soit du texte (HTML) soit du JSON
sauf quand elles sont appellées depuis python.
La présence de l'argument REQUEST indique la publication.
"""
if REQUEST and not isinstance(val, str):
return sendJSON(REQUEST, val)
return val
def confirm_dialog(
message="<p>Confirmer ?</p>",
OK="OK",
@ -823,10 +874,12 @@ def confirm_dialog(
action = f'action="{dest_url}"'
H = [
f"""<form {action} method="post">""",
message,
"""<input type="submit" value="%s"/>""" % OK,
f"""<form {action} method="POST">
{message}
""",
]
if OK or not cancel_url:
H.append(f'<input type="submit" value="{OK}"/>')
if cancel_url:
H.append(
"""<input type ="button" value="%s"

View File

@ -27,6 +27,10 @@ h3 {
font-weight: bold;
}
div#gtrcontent {
margin-bottom: 4ex;
}
.scotext {
font-family : TimesNewRoman, "Times New Roman", Times, Baskerville, Georgia, serif;
}
@ -154,7 +158,7 @@ div.scovalueerror {
p.footer {
font-size: 80%;
color: rgb(60,60,60);
margin-top: 10px;
margin-top: 15px;
border-top: 1px solid rgb(60,60,60);
}
@ -688,6 +692,7 @@ td.fichetitre2 .fl {
span.link_bul_pdf {
font-size: 80%;
padding-right: 2em;
}
/* Page accueil Sco */
@ -1092,13 +1097,13 @@ h2.formsemestre, .gtrcontent h2 {
#formnotes td.tf-fieldlabel {
border-bottom: 1px dotted #fdcaca;
}
/* Formulaires ScoDoc 9 */
form.sco-form {
margin-top: 1em;
.wtf-field li {
display: inline;
}.wtf-field ul {
padding-left: 0;
}
div.sco-submit {
margin-top: 2em;
.wtf-field .errors {
color: red ; font-weight: bold;
}
/*
.formsemestre_menubar {

View File

@ -11,7 +11,7 @@
ou écrire la liste "notes" <a href="mailto:notes@listes.univ-paris13.fr">notes@listes.univ-paris13.fr</a> en
indiquant la version du logiciel
<br />
(plus d'informations sur les listes de diffusion<a href="https://scodoc.org/ListesDeDiffusion/">voir
(plus d'informations sur les listes de diffusion <a href="https://scodoc.org/ListesDeDiffusion/">voir
cette page</a>).
</p>

View File

@ -5,13 +5,14 @@
<h2>Erreur !</h2>
<p>{{ exc }}</p>
{{ exc | safe }}
<p>
<p class="footer">
{% if g.scodoc_dept %}
<a href="{{ exc.dest_url or url_for('scolar.index_html', scodoc_dept=g.scodoc_dept) }}">continuer</a>
<a href="{{ exc.dest_url or url_for('scolar.index_html', scodoc_dept=g.scodoc_dept) }}">retour page d'accueil
departement {{ g.scodoc_dept }}</a>
{% else %}
<a href="{{ exc.dest_url or url_for('scodoc.index') }}">continuer</a>
<a href="{{ exc.dest_url or url_for('scodoc.index') }}">retour page d'accueil</a>
{% endif %}
</p>

View File

@ -0,0 +1,80 @@
{% import 'bootstrap/wtf.html' as wtf %}
{% macro render_field(field) %}
<tr>
<td class="wtf-field">{{ field.label }}</td>
<td class="wtf-field">{{ field(**kwargs)|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</td>
</tr>
{% endmacro %}
<div class="saisienote_etape1 form_placement">
<form method=post>
{{ form.evaluation_id }}
{{ form.csrf_token }}
<table class="tf">
<tbody>
{{ render_field(form.surveillants) }}
{{ render_field(form.batiment) }}
{{ render_field(form.salle) }}
{{ render_field(form.nb_rangs) }}
{{ render_field(form.etiquetage) }}
{% if form.has_groups %}
{{ render_field(form.groups, size=form.nb_groups) }}
<!-- Tentative de recréer le choix des groupes sous forme de cases à cocher // demande à créer des champs wtf dynamiquement
{% for partition in form.groups_tree %}
<tr>
{% if partition == 'Tous' %}
<td rowspan="{{ form.nb_groups }}">Groupes</td>
{% endif %}
<td>{{ partition }}</td>
<td>
{% for groupe in form.groups_tree[partition] %}
{{ groupe }}{{ form[form.groups_tree[partition][groupe]] }}
{% endfor %}
</td>
</tr>
{% endfor %}
-->
{% endif %}
{{ render_field(form.file_format) }}
</tbody>
</table>
<p>
<input id="gr_submit" type=submit value="Ok">
<input id="gr_cancel" type=submit value="Annuler">
</script>
</form>
<h3>Explications</h3>
<ul>
<li>préciser les surveillants et la localisation (bâtiment et salle) et indiquer la largeur de la salle (nombre
de colonnes);</li>
<li>deux types de placements sont possibles :
<ul>
<li>continue suppose que les tables ont toutes un numéro unique;</li>
<li>coordonnées localise chaque table via un numéro de colonne et un numéro de ligne (ou rangée).</li>
</ul>
</li>
<li>Il est possible de choisir un ou plusieurs groupes (shift/ctrl click) ou de choisir 'tous'.</li>
<li>Choisir le format du fichier résultat :
<ul>
<li>le format pdf consiste en un tableau précisant pour chaque étudiant la localisation de sa table;
</li>
<li>le format xls produit un classeur avec deux onglets:
<ul>
<li>le premier onglet donne une vue de la salle avec la localisation des étudiants et
peut servir de feuille d'émargement;</li>
<li>le second onglet est un tableau similaire à celui du fichier pdf;</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>

View File

@ -0,0 +1,12 @@
<h2 class="insidebar">Dépt. {{ prefs["DeptName"] }}</h2>
<a href="{{ url_for('scodoc.index') }}" class="sidebar">Accueil</a> <br/>
{% if prefs["DeptIntranetURL"] %}
<a href="{{ prefs["DeptIntranetURL"] }}" class="sidebar">
{{ prefs["DeptIntranetTitle"] }}</a>
{% endif %}
<br/>
{#
# Entreprises pas encore supporté en ScoDoc8
# <br/><a href="%(ScoURL)s/Entreprises" class="sidebar">Entreprises</a> <br/>
#}

View File

@ -47,20 +47,18 @@ L'API de plus bas niveau est en gros:
"""
import calendar
import cgi
import datetime
import dateutil
import dateutil.parser
import re
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
import string
import time
import urllib
from xml.etree import ElementTree
import flask
from flask import g
from flask import g, request
from flask import url_for
from flask import current_app
from flask_login import current_user
from app.decorators import (
scodoc,
@ -74,6 +72,7 @@ from app.decorators import (
from app.views import absences_bp as bp
# ---------------
from app.models.absences import BilletAbsence
from app.scodoc import sco_utils as scu
from app.scodoc import notesdb as ndb
from app import log
@ -123,11 +122,11 @@ def sco_publish(route, function, permission, methods=["GET"]):
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
def index_html(REQUEST=None):
def index_html():
"""Gestionnaire absences, page principale"""
# crude portage from 1999 DTML
sems = sco_formsemestre.do_formsemestre_list()
authuser = REQUEST.AUTHENTICATED_USER
authuser = current_user
H = [
html_sco_header.sco_header(
@ -164,7 +163,7 @@ def index_html(REQUEST=None):
Saisie par semaine </span> - Choix du groupe:
<input name="datelundi" type="hidden" value="x"/>
"""
% REQUEST.URL0,
% request.base_url,
sco_abs_views.formChoixSemestreGroupe(),
"</p>",
cal_select_week(),
@ -270,7 +269,6 @@ def doSignaleAbsenceGrSemestre(
dates="",
etudids="",
destination=None,
REQUEST=None,
):
"""Enregistre absences aux dates indiquees (abslist et dates).
dates est une liste de dates ISO (séparées par des ',').
@ -295,10 +293,10 @@ def doSignaleAbsenceGrSemestre(
# 2- Ajoute les absences
if abslist:
sco_abs._add_abslist(abslist, REQUEST, moduleimpl_id)
sco_abs.add_abslist(abslist, moduleimpl_id)
return "Absences ajoutées"
return ""
return ("", 204)
# ------------ HTML Interfaces
@ -307,14 +305,14 @@ def doSignaleAbsenceGrSemestre(
@permission_required(Permission.ScoAbsChange)
@scodoc7func
def SignaleAbsenceGrHebdo(
datelundi, group_ids=[], destination="", moduleimpl_id=None, REQUEST=None
datelundi, group_ids=[], destination="", moduleimpl_id=None, formsemestre_id=None
):
"Saisie hebdomadaire des absences"
if not moduleimpl_id:
moduleimpl_id = None
groups_infos = sco_groups_view.DisplayedGroupsInfos(
group_ids, moduleimpl_id=moduleimpl_id
group_ids, moduleimpl_id=moduleimpl_id, formsemestre_id=formsemestre_id
)
if not groups_infos.members:
return (
@ -326,7 +324,7 @@ def SignaleAbsenceGrHebdo(
base_url = "SignaleAbsenceGrHebdo?datelundi=%s&%s&destination=%s" % (
datelundi,
groups_infos.groups_query_args,
six.moves.urllib.parse.quote(destination),
urllib.parse.quote(destination),
)
formsemestre_id = groups_infos.formsemestre_id
@ -473,7 +471,6 @@ def SignaleAbsenceGrSemestre(
group_ids=[], # list of groups to display
nbweeks=4, # ne montre que les nbweeks dernieres semaines
moduleimpl_id=None,
REQUEST=None,
):
"""Saisie des absences sur une journée sur un semestre (ou intervalle de dates) entier"""
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
@ -510,7 +507,7 @@ def SignaleAbsenceGrSemestre(
datedebut,
datefin,
groups_infos.groups_query_args,
six.moves.urllib.parse.quote(destination),
urllib.parse.quote(destination),
)
)
base_url = base_url_noweeks + "&nbweeks=%s" % nbweeks # sans le moduleimpl_id
@ -810,7 +807,7 @@ def _gen_form_saisie_groupe(
H.append('<input type="hidden" name="dates" value="%s"/>' % ",".join(dates))
H.append(
'<input type="hidden" name="destination" value="%s"/>'
% six.moves.urllib.parse.quote(destination)
% urllib.parse.quote(destination)
)
#
# version pour formulaire avec AJAX (Yann LB)
@ -843,7 +840,6 @@ def EtatAbsencesGr(
fin="",
with_boursier=True, # colonne boursier
format="html",
REQUEST=None,
):
"""Liste les absences de groupes"""
datedebut = ndb.DateDMYtoISO(debut)
@ -938,7 +934,7 @@ def EtatAbsencesGr(
javascripts=["js/etud_info.js"],
),
html_title=html_sco_header.html_sem_header(
REQUEST, "%s" % title, sem, with_page_header=False
"%s" % title, sem, with_page_header=False
)
+ "<p>Période du %s au %s (nombre de <b>demi-journées</b>)<br/>" % (debut, fin),
base_url="%s&formsemestre_id=%s&debut=%s&fin=%s"
@ -964,9 +960,9 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
<input type="submit" name="" value="visualiser les absences">
</form></div>
"""
% (REQUEST.URL0, formsemestre_id, groups_infos.get_form_elem()),
% (request.base_url, formsemestre_id, groups_infos.get_form_elem()),
)
return tab.make_page(format=format, REQUEST=REQUEST)
return tab.make_page(format=format)
@bp.route("/EtatAbsencesDate")
@ -1062,7 +1058,6 @@ def AddBilletAbsence(
code_nip=None,
code_ine=None,
justified=True,
REQUEST=None,
xml_reply=True,
):
"""Memorise un "billet"
@ -1072,7 +1067,9 @@ def AddBilletAbsence(
# check etudid
etuds = sco_etud.get_etud_info(etudid=etudid, code_nip=code_nip, filled=True)
if not etuds:
return scu.log_unknown_etud(REQUEST=REQUEST)
sco_etud.log_unknown_etud()
raise ScoValueError("étudiant inconnu")
etud = etuds[0]
# check dates
begin_date = dateutil.parser.isoparse(begin) # may raises ValueError
@ -1096,13 +1093,10 @@ def AddBilletAbsence(
)
if xml_reply:
# Renvoie le nouveau billet en XML
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
tab = _tableBillets(billets, etud=etud)
log("AddBilletAbsence: new billet_id=%s (%gs)" % (billet_id, time.time() - t0))
return tab.make_page(REQUEST=REQUEST, format="xml")
return tab.make_page(format="xml")
else:
return billet_id
@ -1111,7 +1105,7 @@ def AddBilletAbsence(
@scodoc
@permission_required(Permission.ScoAbsAddBillet)
@scodoc7func
def AddBilletAbsenceForm(etudid, REQUEST=None):
def AddBilletAbsenceForm(etudid):
"""Formulaire ajout billet (pour tests seulement, le vrai formulaire accessible aux etudiants
étant sur le portail étudiant).
"""
@ -1122,8 +1116,8 @@ def AddBilletAbsenceForm(etudid, REQUEST=None):
)
]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(
("etudid", {"input_type": "hidden"}),
("begin", {"input_type": "date"}),
@ -1223,17 +1217,18 @@ def _tableBillets(billets, etud=None, title=""):
@scodoc
@permission_required(Permission.ScoView)
@scodoc7func
def listeBilletsEtud(etudid=False, REQUEST=None, format="html"):
def listeBilletsEtud(etudid=False, format="html"):
"""Liste billets pour un etudiant"""
etuds = sco_etud.get_etud_info(filled=True, etudid=etudid)
if not etuds:
return scu.log_unknown_etud(format=format, REQUEST=REQUEST)
sco_etud.log_unknown_etud()
raise ScoValueError("étudiant inconnu")
etud = etuds[0]
cnx = ndb.GetDBConnexion()
billets = sco_abs.billet_absence_list(cnx, {"etudid": etud["etudid"]})
tab = _tableBillets(billets, etud=etud)
return tab.make_page(REQUEST=REQUEST, format=format)
return tab.make_page(format=format)
@bp.route(
@ -1242,12 +1237,12 @@ def listeBilletsEtud(etudid=False, REQUEST=None, format="html"):
@scodoc
@permission_required_compat_scodoc7(Permission.ScoView)
@scodoc7func
def XMLgetBilletsEtud(etudid=False, REQUEST=None):
def XMLgetBilletsEtud(etudid=False):
"""Liste billets pour un etudiant"""
if not sco_preferences.get_preference("handle_billets_abs"):
return ""
t0 = time.time()
r = listeBilletsEtud(etudid, REQUEST=REQUEST, format="xml")
r = listeBilletsEtud(etudid, format="xml")
log("XMLgetBilletsEtud (%gs)" % (time.time() - t0))
return r
@ -1256,10 +1251,17 @@ def XMLgetBilletsEtud(etudid=False, REQUEST=None):
@scodoc
@permission_required_compat_scodoc7(Permission.ScoView)
@scodoc7func
def listeBillets(REQUEST=None):
def listeBillets():
"""Page liste des billets non traités et formulaire recherche d'un billet"""
cnx = ndb.GetDBConnexion()
billets = sco_abs.billet_absence_list(cnx, {"etat": 0})
# utilise Flask, jointure avec departement de l'étudiant
billets = (
BilletAbsence.query.filter_by(etat=False)
.join(BilletAbsence.etudiant, aliased=True)
.filter_by(dept_id=g.scodoc_dept_id)
)
# reconverti en dict pour les fonctions scodoc7
billets = [b.to_dict() for b in billets]
#
tab = _tableBillets(billets)
T = tab.html()
H = [
@ -1268,8 +1270,8 @@ def listeBillets(REQUEST=None):
]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(("billet_id", {"input_type": "text", "title": "Numéro du billet"}),),
submitbutton=False,
)
@ -1281,11 +1283,11 @@ def listeBillets(REQUEST=None):
)
@bp.route("/deleteBilletAbsence")
@bp.route("/deleteBilletAbsence", methods=["POST", "GET"])
@scodoc
@permission_required(Permission.ScoAbsChange)
@scodoc7func
def deleteBilletAbsence(billet_id, REQUEST=None, dialog_confirmed=False):
def deleteBilletAbsence(billet_id, dialog_confirmed=False):
"""Supprime un billet."""
cnx = ndb.GetDBConnexion()
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
@ -1307,7 +1309,7 @@ def deleteBilletAbsence(billet_id, REQUEST=None, dialog_confirmed=False):
return flask.redirect("listeBillets?head_message=Billet%20supprimé")
def _ProcessBilletAbsence(billet, estjust, description, REQUEST):
def _ProcessBilletAbsence(billet, estjust, description):
"""Traite un billet: ajoute absence(s) et éventuellement justificatifs,
et change l'état du billet à 1.
NB: actuellement, les heures ne sont utilisées que pour déterminer si matin et/ou après-midi.
@ -1329,7 +1331,6 @@ def _ProcessBilletAbsence(billet, estjust, description, REQUEST):
dates[0],
0,
estjust,
REQUEST,
description=description,
)
n += 1
@ -1341,7 +1342,6 @@ def _ProcessBilletAbsence(billet, estjust, description, REQUEST):
dates[-1],
1,
estjust,
REQUEST,
description=description,
)
n += 1
@ -1353,7 +1353,6 @@ def _ProcessBilletAbsence(billet, estjust, description, REQUEST):
jour,
0,
estjust,
REQUEST,
description=description,
)
sco_abs.add_absence(
@ -1361,7 +1360,6 @@ def _ProcessBilletAbsence(billet, estjust, description, REQUEST):
jour,
1,
estjust,
REQUEST,
description=description,
)
n += 2
@ -1372,11 +1370,11 @@ def _ProcessBilletAbsence(billet, estjust, description, REQUEST):
return n
@bp.route("/ProcessBilletAbsenceForm")
@bp.route("/ProcessBilletAbsenceForm", methods=["POST", "GET"])
@scodoc
@permission_required(Permission.ScoAbsChange)
@scodoc7func
def ProcessBilletAbsenceForm(billet_id, REQUEST=None):
def ProcessBilletAbsenceForm(billet_id):
"""Formulaire traitement d'un billet"""
cnx = ndb.GetDBConnexion()
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
@ -1401,8 +1399,8 @@ def ProcessBilletAbsenceForm(billet_id, REQUEST=None):
]
tf = TrivialFormulator(
REQUEST.URL0,
REQUEST.form,
request.base_url,
scu.get_request_args(),
(
("billet_id", {"input_type": "hidden"}),
(
@ -1439,9 +1437,7 @@ def ProcessBilletAbsenceForm(billet_id, REQUEST=None):
elif tf[0] == -1:
return flask.redirect(scu.ScoURL())
else:
n = _ProcessBilletAbsence(
billet, tf[2]["estjust"], tf[2]["description"], REQUEST
)
n = _ProcessBilletAbsence(billet, tf[2]["estjust"], tf[2]["description"])
if tf[2]["estjust"]:
j = "justifiées"
else:
@ -1477,7 +1473,7 @@ def ProcessBilletAbsenceForm(billet_id, REQUEST=None):
@scodoc
@permission_required_compat_scodoc7(Permission.ScoView)
@scodoc7func
def XMLgetAbsEtud(beg_date="", end_date="", REQUEST=None):
def XMLgetAbsEtud(beg_date="", end_date=""):
"""returns list of absences in date interval"""
t0 = time.time()
etuds = sco_etud.get_etud_info(filled=False)
@ -1493,7 +1489,6 @@ def XMLgetAbsEtud(beg_date="", end_date="", REQUEST=None):
abs_list = sco_abs.list_abs_date(etud["etudid"], beg_date, end_date)
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
doc = ElementTree.Element(
"absences", etudid=str(etud["etudid"]), beg_date=beg_date, end_date=end_date
)
@ -1509,4 +1504,5 @@ def XMLgetAbsEtud(beg_date="", end_date="", REQUEST=None):
)
)
log("XMLgetAbsEtud (%gs)" % (time.time() - t0))
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
data = sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
return scu.send_file(data, mime=scu.XML_MIMETYPE, attached=False)

Some files were not shown because too many files have changed in this diff Show More