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 *.code-workspace
copy

View File

@ -3,16 +3,12 @@
(c) Emmanuel Viennet 1999 - 2021 (voir LICENCE.txt) (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> Installation: voir instructions à jour sur <https://scodoc.org/GuideInstallDebian11>
Documentation utilisateur: <https://scodoc.org> Documentation utilisateur: <https://scodoc.org>
## Version ScoDoc 9 ## 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 La version ScoDoc 9 est basée sur Flask (au lieu de Zope) et sur
**python 3.9+**. **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:** **Fonctionnalités non intégrées:**
- feuille "placement" (en cours) - génération LaTeX des avis de poursuite d'études
- ancien module "Entreprises" (obsolete) - ancien module "Entreprises" (obsolete)

View File

@ -17,14 +17,14 @@ from flask import render_template
from flask.logging import default_handler from flask.logging import default_handler
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_login import LoginManager from flask_login import LoginManager, current_user
from flask_mail import Mail from flask_mail import Mail
from flask_bootstrap import Bootstrap from flask_bootstrap import Bootstrap
from flask_moment import Moment from flask_moment import Moment
from flask_caching import Cache from flask_caching import Cache
import sqlalchemy import sqlalchemy
from app.scodoc.sco_exceptions import ScoValueError, APIInvalidParams from app.scodoc.sco_exceptions import ScoGenError, ScoValueError, APIInvalidParams
from config import DevConfig from config import DevConfig
import sco_version import sco_version
@ -82,7 +82,7 @@ def postgresql_server_error(e):
return render_raw_html("error_503.html", SCOVERSION=sco_version.SCOVERSION), 503 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""" """Ajoute URL et remote_addr for logging"""
def format(self, record): def format(self, record):
@ -92,6 +92,34 @@ class RequestFormatter(logging.Formatter):
else: else:
record.url = None record.url = None
record.remote_addr = 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) return super().format(record)
@ -105,8 +133,24 @@ class ScoSMTPHandler(SMTPHandler):
return subject 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): def create_app(config_class=DevConfig):
app = Flask(__name__, static_url_path="/ScoDoc/static", static_folder="static") app = Flask(__name__, static_url_path="/ScoDoc/static", static_folder="static")
app.wsgi_app = ReverseProxied(app.wsgi_app)
app.logger.setLevel(logging.DEBUG) app.logger.setLevel(logging.DEBUG)
app.config.from_object(config_class) app.config.from_object(config_class)
@ -119,6 +163,7 @@ def create_app(config_class=DevConfig):
cache.init_app(app) cache.init_app(app)
sco_cache.CACHE = cache 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(ScoValueError, handle_sco_value_error)
app.register_error_handler(500, internal_server_error) app.register_error_handler(500, internal_server_error)
app.register_error_handler(503, postgresql_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" absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences"
) )
app.register_blueprint(api_bp, url_prefix="/ScoDoc/api") app.register_blueprint(api_bp, url_prefix="/ScoDoc/api")
scodoc_exc_formatter = RequestFormatter( scodoc_log_formatter = LogRequestFormatter(
"[%(asctime)s] %(remote_addr)s requested %(url)s\n" "[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
"%(levelname)s in %(module)s: %(message)s" "%(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.testing:
if not app.debug: if not app.debug:
@ -179,7 +231,7 @@ def create_app(config_class=DevConfig):
app.logger.addHandler(mail_handler) app.logger.addHandler(mail_handler)
else: else:
# Pour logs en DEV uniquement: # Pour logs en DEV uniquement:
default_handler.setFormatter(scodoc_exc_formatter) default_handler.setFormatter(scodoc_log_formatter)
# Config logs pour DEV et PRODUCTION # Config logs pour DEV et PRODUCTION
# Configuration des logs (actifs aussi en mode development) # Configuration des logs (actifs aussi en mode development)
@ -188,9 +240,17 @@ def create_app(config_class=DevConfig):
file_handler = WatchedFileHandler( file_handler = WatchedFileHandler(
app.config["SCODOC_LOG_FILE"], encoding="utf-8" 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) file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler) 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.setLevel(logging.INFO)
app.logger.info(f"{sco_version.SCONAME} {sco_version.SCOVERSION} startup") 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 # 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 # 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 # 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 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 import app.scodoc.sco_utils as scu
from app.scodoc import sco_etud # a deplacer dans 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): class User(UserMixin, db.Model):

View File

@ -20,6 +20,7 @@ import flask_login
import app import app
from app.auth.models import User from app.auth.models import User
import app.scodoc.sco_utils as scu
class ZUser(object): class ZUser(object):
@ -39,69 +40,6 @@ class ZUser(object):
raise NotImplementedError() 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): def scodoc(func):
"""Décorateur pour toutes les fonctions ScoDoc """Décorateur pour toutes les fonctions ScoDoc
Affecte le département à g Affecte le département à g
@ -132,7 +70,6 @@ def permission_required(permission):
def decorator(f): def decorator(f):
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
# current_app.logger.info("PERMISSION; kwargs=%s" % str(kwargs))
scodoc_dept = getattr(g, "scodoc_dept", None) scodoc_dept = getattr(g, "scodoc_dept", None)
if not current_user.has_permission(permission, scodoc_dept): if not current_user.has_permission(permission, scodoc_dept):
abort(403) abort(403)
@ -193,7 +130,6 @@ def admin_required(f):
def scodoc7func(func): def scodoc7func(func):
"""Décorateur pour intégrer les fonctions Zope 2 de ScoDoc 7. """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. 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") 1. via a Flask route ("top level call")
2. or be called directly from Python. 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") # Détermine si on est appelé via une route ("toplevel")
# ou par un appel de fonction python normal. # 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: if not top_level:
# ne "redécore" pas # ne "redécore" pas
return func(*args, **kwargs) return func(*args, **kwargs)
g.scodoc7_decorated = True
# --- Emulate Zope's REQUEST # --- Emulate Zope's REQUEST
REQUEST = ZRequest() # REQUEST = ZRequest()
g.zrequest = REQUEST # g.zrequest = REQUEST
req_args = REQUEST.form # args from query string (get) or form (post) # args from query string (get) or form (post)
# --- Add positional arguments req_args = scu.get_request_args()
## --- Add positional arguments
pos_arg_values = [] pos_arg_values = []
argspec = inspect.getfullargspec(func) argspec = inspect.getfullargspec(func)
# current_app.logger.info("argspec=%s" % str(argspec)) # current_app.logger.info("argspec=%s" % str(argspec))
@ -227,10 +164,12 @@ def scodoc7func(func):
arg_names = argspec.args[:-nb_default_args] arg_names = argspec.args[:-nb_default_args]
else: else:
arg_names = argspec.args arg_names = argspec.args
for arg_name in arg_names: for arg_name in arg_names: # pour chaque arg de la fonction vue
if arg_name == "REQUEST": # special case if arg_name == "REQUEST": # ne devrait plus arriver !
pos_arg_values.append(REQUEST) # debug check, TODO remove after tests
raise ValueError("invalid REQUEST parameter !")
else: else:
# peut produire une KeyError s'il manque un argument attendu:
v = req_args[arg_name] v = req_args[arg_name]
# try to convert all arguments to INTEGERS # try to convert all arguments to INTEGERS
# necessary for db ids and boolean values # necessary for db ids and boolean values
@ -244,9 +183,9 @@ def scodoc7func(func):
# Add keyword arguments # Add keyword arguments
if nb_default_args: if nb_default_args:
for arg_name in argspec.args[-nb_default_args:]: for arg_name in argspec.args[-nb_default_args:]:
if arg_name == "REQUEST": # special case # if arg_name == "REQUEST": # special case
kwargs[arg_name] = REQUEST # kwargs[arg_name] = REQUEST
elif arg_name in req_args: if arg_name in req_args:
# set argument kw optionnel # set argument kw optionnel
v = req_args[arg_name] v = req_args[arg_name]
# try to convert all arguments to INTEGERS # try to convert all arguments to INTEGERS
@ -270,13 +209,13 @@ def scodoc7func(func):
# Build response, adding collected http headers: # Build response, adding collected http headers:
headers = [] headers = []
kw = {"response": value, "status": 200} kw = {"response": value, "status": 200}
if g.zrequest: # if g.zrequest:
headers = g.zrequest.RESPONSE.headers # headers = g.zrequest.RESPONSE.headers
if not headers: # if not headers:
# no customized header, speedup: # # no customized header, speedup:
return value # return value
if "content-type" in headers: # if "content-type" in headers:
kw["mimetype"] = headers["content-type"] # kw["mimetype"] = headers["content-type"]
r = flask.Response(**kw) r = flask.Response(**kw)
for h in headers: for h in headers:
r.headers[h] = headers[h] 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()) entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
# true si l'absence _pourrait_ etre justifiée # true si l'absence _pourrait_ etre justifiée
justified = db.Column(db.Boolean(), default=False, server_default="false") 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") semsets = db.relationship("NotesSemSet", lazy="dynamic", backref="departement")
def __repr__(self): def __repr__(self):
return f"<Departement {self.acronym}>" return f"<{self.__class__.__name__}(id={self.id}, acronym='{self.acronym}')>"
def to_dict(self): def to_dict(self):
data = { data = {

View File

@ -41,7 +41,10 @@ class Identite(db.Model):
code_nip = db.Column(db.Text()) code_nip = db.Column(db.Text())
code_ine = db.Column(db.Text()) code_ine = db.Column(db.Text())
# Ancien id ScoDoc7 pour les migrations de bases anciennes # 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) scodoc7_id = db.Column(db.Text(), nullable=True)
#
billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
class Adresse(db.Model): class Adresse(db.Model):

View File

@ -11,7 +11,7 @@ class NotesFormation(db.Model):
"""Programme pédagogique d'une formation""" """Programme pédagogique d'une formation"""
__tablename__ = "notes_formations" __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) id = db.Column(db.Integer, primary_key=True)
formation_id = db.synonym("id") formation_id = db.synonym("id")
@ -30,8 +30,12 @@ class NotesFormation(db.Model):
type_parcours = db.Column(db.Integer, default=0, server_default="0") type_parcours = db.Column(db.Integer, default=0, server_default="0")
code_specialite = db.Column(db.String(SHORT_STR_LEN)) 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") 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): class NotesUE(db.Model):
"""Unité d'Enseignement""" """Unité d'Enseignement"""
@ -61,6 +65,9 @@ class NotesUE(db.Model):
# coef UE, utilise seulement si l'option use_ue_coefs est activée: # coef UE, utilise seulement si l'option use_ue_coefs est activée:
coefficient = db.Column(db.Float) 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): class NotesMatiere(db.Model):
"""Matières: regroupe les modules d'une UE """Matières: regroupe les modules d'une UE

View File

@ -41,6 +41,10 @@ class FormSemestre(db.Model):
bul_hide_xml = db.Column( bul_hide_xml = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false" 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): # semestres decales (pour gestion jurys):
gestion_semestrielle = db.Column( gestion_semestrielle = db.Column(
db.Boolean(), nullable=False, default=False, server_default="false" db.Boolean(), nullable=False, default=False, server_default="false"
@ -70,6 +74,7 @@ class FormSemestre(db.Model):
"NotesFormsemestreEtape", cascade="all,delete", backref="notes_formsemestre" "NotesFormsemestreEtape", cascade="all,delete", backref="notes_formsemestre"
) )
# Ancien id ScoDoc7 pour les migrations de bases anciennes # 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) scodoc7_id = db.Column(db.Text(), nullable=True)
def __init__(self, **kwargs): 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 os
import codecs import codecs
import re import re
from app.scodoc import pe_jurype from app.pe import pe_tagtable
from app.scodoc import pe_tagtable from app.pe import pe_jurype
from app.scodoc import pe_tools from app.pe import pe_tools
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb 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 DEBUG = False # Pour debug et repérage des prints à changer en Log
DONNEE_MANQUANTE = ( 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:) result: chaine unicode (EV:)
""" """
codelatexDebut = ( codelatexDebut = (
u""" """"
\\begin{parcourstimeline}{**debut**}{**fin**}{**nbreSemestres**}{%d} \\begin{parcourstimeline}{**debut**}{**fin**}{**nbreSemestres**}{%d}
""" """
% taille % taille
) )
modeleEvent = u""" modeleEvent = """
\\parcoursevent{**nosem**}{**nomsem**}{**descr**} \\parcoursevent{**nosem**}{**nomsem**}{**descr**}
""" """
codelatexFin = u""" codelatexFin = """
\\end{parcourstimeline} \\end{parcourstimeline}
""" """
reslatex = codelatexDebut reslatex = codelatexDebut
@ -125,13 +125,13 @@ def comp_latex_parcourstimeline(etudiant, promo, taille=17):
for no_sem in range(etudiant["nbSemestres"]): for no_sem in range(etudiant["nbSemestres"]):
descr = modeleEvent descr = modeleEvent
nom_semestre_dans_parcours = parcours[no_sem]["nom_semestre_dans_parcours"] 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: if no_sem % 2 == 0:
descr = descr.replace(u"**nomsem**", nom_semestre_dans_parcours) descr = descr.replace("**nomsem**", nom_semestre_dans_parcours)
descr = descr.replace(u"**descr**", u"") descr = descr.replace("**descr**", "")
else: else:
descr = descr.replace(u"**nomsem**", u"") descr = descr.replace("**nomsem**", "")
descr = descr.replace(u"**descr**", nom_semestre_dans_parcours) descr = descr.replace("**descr**", nom_semestre_dans_parcours)
reslatex += descr reslatex += descr
reslatex += codelatexFin reslatex += codelatexFin
return reslatex return reslatex
@ -166,7 +166,7 @@ def get_code_latex_avis_etudiant(
result: chaine unicode result: chaine unicode
""" """
if not donnees_etudiant or not un_avis_latex: # Cas d'un template vide 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) # Le template latex (corps + footer)
code = un_avis_latex + "\n\n" + footer_latex code = un_avis_latex + "\n\n" + footer_latex
@ -189,17 +189,17 @@ def get_code_latex_avis_etudiant(
) )
# La macro parcourstimeline # La macro parcourstimeline
elif tag_latex == u"parcourstimeline": elif tag_latex == "parcourstimeline":
valeur = comp_latex_parcourstimeline( valeur = comp_latex_parcourstimeline(
donnees_etudiant, donnees_etudiant["promo"] donnees_etudiant, donnees_etudiant["promo"]
) )
# Le tag annotationPE # Le tag annotationPE
elif tag_latex == u"annotation": elif tag_latex == "annotation":
valeur = annotationPE valeur = annotationPE
# Le tag bilanParTag # Le tag bilanParTag
elif tag_latex == u"bilanParTag": elif tag_latex == "bilanParTag":
valeur = get_bilanParTag(donnees_etudiant) valeur = get_bilanParTag(donnees_etudiant)
# Les tags "simples": par ex. nom, prenom, civilite, ... # Les tags "simples": par ex. nom, prenom, civilite, ...
@ -249,14 +249,14 @@ def get_annotation_PE(etudid, tag_annotation_pe):
]["comment_u"] ]["comment_u"]
annotationPE = exp.sub( annotationPE = exp.sub(
u"", annotationPE "", annotationPE
) # Suppression du tag d'annotation PE ) # 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( annotationPE = annotationPE.replace(
u"<br/>", u"\n\n" "<br/>", "\n\n"
) # Interprète les retours chariots html ) # Interprète les retours chariots html
return annotationPE 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] donnees_numeriques = donnees_etudiant[aggregat][groupe][tag_scodoc]
if champ == "rang": if champ == "rang":
valeur = u"%s/%d" % ( valeur = "%s/%d" % (
donnees_numeriques[ donnees_numeriques[
pe_tagtable.TableTag.FORMAT_DONNEES_ETUDIANTS.index("rang") 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( if isinstance(
donnees_numeriques[indice_champ], float donnees_numeriques[indice_champ], float
): # valeur numérique avec formattage unicode ): # valeur numérique avec formattage unicode
valeur = u"%2.2f" % donnees_numeriques[indice_champ] valeur = "%2.2f" % donnees_numeriques[indice_champ]
else: else:
valeur = u"%s" % donnees_numeriques[indice_champ] valeur = "%s" % donnees_numeriques[indice_champ]
return valeur return valeur
@ -356,29 +356,27 @@ def get_bilanParTag(donnees_etudiant, groupe="groupe"):
("\\textit{" + rang + "}") if note else "" ("\\textit{" + rang + "}") if note else ""
) # rang masqué si pas de notes ) # rang masqué si pas de notes
code_latex = u"\\begin{tabular}{|c|" + "|c" * (len(entete)) + "|}\n" code_latex = "\\begin{tabular}{|c|" + "|c" * (len(entete)) + "|}\n"
code_latex += u"\\hline \n" code_latex += "\\hline \n"
code_latex += ( code_latex += (
u" & " " & "
+ " & ".join(["\\textbf{" + intitule + "}" for (agg, intitule, _) in entete]) + " & ".join(["\\textbf{" + intitule + "}" for (agg, intitule, _) in entete])
+ " \\\\ \n" + " \\\\ \n"
) )
code_latex += u"\\hline" code_latex += "\\hline"
code_latex += u"\\hline \n" code_latex += "\\hline \n"
for (i, ligne_val) in enumerate(valeurs["note"]): for (i, ligne_val) in enumerate(valeurs["note"]):
titre = lignes[i] # règle le pb d'encodage titre = lignes[i] # règle le pb d'encodage
code_latex += "\\textbf{" + titre + "} & " + " & ".join(ligne_val) + "\\\\ \n"
code_latex += ( code_latex += (
u"\\textbf{" + titre + u"} & " + " & ".join(ligne_val) + u"\\\\ \n" " & "
+ " & ".join(
["{\\scriptsize " + clsmt + "}" for clsmt in valeurs["rang"][i]]
) )
code_latex += ( + "\\\\ \n"
u" & "
+ u" & ".join(
[u"{\\scriptsize " + clsmt + u"}" for clsmt in valeurs["rang"][i]]
) )
+ u"\\\\ \n" code_latex += "\\hline \n"
) code_latex += "\\end{tabular}"
code_latex += u"\\hline \n"
code_latex += u"\\end{tabular}"
return code_latex return code_latex
@ -397,21 +395,15 @@ def get_avis_poursuite_par_etudiant(
nom = jury.syntheseJury[etudid]["nom"].replace(" ", "-") nom = jury.syntheseJury[etudid]["nom"].replace(" ", "-")
prenom = jury.syntheseJury[etudid]["prenom"].replace(" ", "-") prenom = jury.syntheseJury[etudid]["prenom"].replace(" ", "-")
nom_fichier = ( nom_fichier = scu.sanitize_filename(
u"avis_poursuite_" "avis_poursuite_%s_%s_%s" % (nom, prenom, etudid)
+ pe_tools.remove_accents(nom)
+ "_"
+ pe_tools.remove_accents(prenom)
+ "_"
+ str(etudid)
) )
if pe_tools.PE_DEBUG: if pe_tools.PE_DEBUG:
pe_tools.pe_print("fichier latex =" + nom_fichier, type(nom_fichier)) pe_tools.pe_print("fichier latex =" + nom_fichier, type(nom_fichier))
# Entete (commentaire) # Entete (commentaire)
contenu_latex = ( contenu_latex = (
u"%% ---- Etudiant: " + civilite_str + " " + nom + " " + prenom + u"\n" "%% ---- Etudiant: " + civilite_str + " " + nom + " " + prenom + "\n"
) )
# les annnotations # 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_codes_parcours # sco_codes_parcours.NEXT -> sem suivant
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import pe_tagtable from app.pe import pe_tagtable
from app.scodoc import pe_tools from app.pe import pe_tools
from app.scodoc import pe_semestretag from app.pe import pe_semestretag
from app.scodoc import pe_settag from app.pe import pe_settag
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def comp_nom_semestre_dans_parcours(sem): def comp_nom_semestre_dans_parcours(sem):
@ -946,7 +946,7 @@ class JuryPE(object):
return list(taglist) return list(taglist)
def get_allTagInSyntheseJury(self): 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() allTags = set()
for nom in JuryPE.PARCOURS.keys(): for nom in JuryPE.PARCOURS.keys():
allTags = allTags.union(set(self.get_allTagForAggregat(nom))) 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_codes_parcours
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import sco_tag_module from app.scodoc import sco_tag_module
from app.scodoc import pe_tagtable from app.pe import pe_tagtable
class SemestreTag(pe_tagtable.TableTag): class SemestreTag(pe_tagtable.TableTag):

View File

@ -36,8 +36,8 @@ Created on Fri Sep 9 09:15:05 2016
@author: barasc @author: barasc
""" """
from app.scodoc.pe_tools import pe_print, PE_DEBUG from app.pe.pe_tools import pe_print, PE_DEBUG
from app.scodoc import pe_tagtable from app.pe import pe_tagtable
class SetTag(pe_tagtable.TableTag): 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): 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""" """Read pathname server file and add content to zip under path_in_zip"""
rooted_path_in_zip = os.path.join(ziproot, path_in_zip) rooted_path_in_zip = os.path.join(ziproot, path_in_zip)
data = open(pathname).read() zipfile.write(filename=pathname, arcname=rooted_path_in_zip)
zipfile.writestr(rooted_path_in_zip, data) # 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): def add_pe_stuff_to_zip(zipfile, ziproot):
@ -179,30 +190,16 @@ def add_pe_stuff_to_zip(zipfile, ziproot):
Also copy logos Also copy logos
""" """
register = {}
# first add standard (distrib references)
distrib_dir = os.path.join(REP_DEFAULT_AVIS, "distrib") distrib_dir = os.path.join(REP_DEFAULT_AVIS, "distrib")
distrib_pathnames = list_directory_filenames( add_refs_to_register(register=register, directory=distrib_dir)
distrib_dir # then add local references (some oh them may overwrite distrib refs)
) # 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
local_dir = os.path.join(REP_LOCAL_AVIS, "local") local_dir = os.path.join(REP_LOCAL_AVIS, "local")
local_pathnames = list_directory_filenames(local_dir) add_refs_to_register(register=register, directory=local_dir)
l = len(local_dir) # at this point register contains all refs (filename, pathname) to be saved
local_filenames = {x[l + 1 :] for x in local_pathnames} for filename, pathname in register.items():
add_local_file_to_zip(zipfile, ziproot, pathname, "avis/" + filename)
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,
)
# Logos: (add to logos/ directory in zip) # Logos: (add to logos/ directory in zip)
logos_names = ["logo_header.jpg", "logo_footer.jpg"] 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 html_sco_header
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import pe_tools from app.pe import pe_tools
from app.scodoc.pe_tools import PE_LATEX_ENCODING from app.pe import pe_jurype
from app.scodoc import pe_jurype from app.pe import pe_avislatex
from app.scodoc import pe_avislatex
def _pe_view_sem_recap_form(formsemestre_id): def _pe_view_sem_recap_form(formsemestre_id):
@ -90,7 +89,6 @@ def pe_view_sem_recap(
semBase = sco_formsemestre.get_formsemestre(formsemestre_id) semBase = sco_formsemestre.get_formsemestre(formsemestre_id)
jury = pe_jurype.JuryPE(semBase) jury = pe_jurype.JuryPE(semBase)
# Ajout avis LaTeX au même zip: # Ajout avis LaTeX au même zip:
etudids = list(jury.syntheseJury.keys()) etudids = list(jury.syntheseJury.keys())
@ -150,18 +148,14 @@ def pe_view_sem_recap(
footer_latex, footer_latex,
prefs, prefs,
) )
jury.add_file_to_zip("avis/" + nom_fichier + ".tex", contenu_latex)
jury.add_file_to_zip(
("avis/" + nom_fichier + ".tex").encode(PE_LATEX_ENCODING),
contenu_latex.encode(PE_LATEX_ENCODING),
)
latex_pages[nom_fichier] = contenu_latex # Sauvegarde dans un dico latex_pages[nom_fichier] = contenu_latex # Sauvegarde dans un dico
# Nouvelle version : 1 fichier par étudiant avec 1 fichier appelant créée ci-dessous # Nouvelle version : 1 fichier par étudiant avec 1 fichier appelant créée ci-dessous
doc_latex = "\n% -----\n".join( doc_latex = "\n% -----\n".join(
["\\include{" + nom + "}" for nom in sorted(latex_pages.keys())] ["\\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 # Ajoute image, LaTeX class file(s) and modeles
pe_tools.add_pe_stuff_to_zip(jury.zipfile, jury.NOM_EXPORT_ZIP) pe_tools.add_pe_stuff_to_zip(jury.zipfile, jury.NOM_EXPORT_ZIP)

View File

@ -8,6 +8,7 @@
v 1.3 (python3) v 1.3 (python3)
""" """
import html
def TrivialFormulator( def TrivialFormulator(
@ -134,7 +135,7 @@ class TF(object):
is_submitted=False, is_submitted=False,
): ):
self.form_url = form_url self.form_url = form_url
self.values = values self.values = values.copy()
self.formdescription = list(formdescription) self.formdescription = list(formdescription)
self.initvalues = initvalues self.initvalues = initvalues
self.method = method 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]): if str(descr["allowed_values"][i]) == str(self.values[field]):
R.append('<span class="tf-ro-value">%s</span>' % labels[i]) R.append('<span class="tf-ro-value">%s</span>' % labels[i])
elif input_type == "textarea": 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": elif input_type == "separator" or input_type == "hidden":
pass pass
elif input_type == "file": elif input_type == "file":

View File

@ -379,6 +379,25 @@ def bonus_iutbethune(notes_sport, coefs, infos=None):
return bonus 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): def bonus_demo(notes_sport, coefs, infos=None):
"""Fausse fonction "bonus" pour afficher les informations disponibles """Fausse fonction "bonus" pour afficher les informations disponibles
et aider les développeurs. et aider les développeurs.
@ -386,7 +405,7 @@ def bonus_demo(notes_sport, coefs, infos=None):
qui est ECRASE à chaque appel. qui est ECRASE à chaque appel.
*** Ne pas utiliser en production !!! *** *** Ne pas utiliser en production !!! ***
""" """
f = open("/tmp/scodoc_bonus.log", "w") # mettre 'a' pour ajouter en fin with open("/tmp/scodoc_bonus.log", "w") as f: # mettre 'a' pour ajouter en fin
f.write("\n---------------\n" + pprint.pformat(infos) + "\n") f.write("\n---------------\n" + pprint.pformat(infos) + "\n")
# Statut de chaque UE # Statut de chaque UE
# for ue_id in infos['moy_ues']: # for ue_id in infos['moy_ues']:

View File

@ -185,6 +185,9 @@ class GenTable(object):
else: else:
self.preferences = DEFAULT_TABLE_PREFERENCES() 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): def get_nb_cols(self):
return len(self.columns_ids) return len(self.columns_ids)
@ -468,7 +471,10 @@ class GenTable(object):
def excel(self, wb=None): def excel(self, wb=None):
"""Simple Excel representation of the table""" """Simple Excel representation of the table"""
if wb is None:
ses = sco_excel.ScoExcelSheet(sheet_name=self.xls_sheet_name, wb=wb) 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 ses.rows += self.xls_before_table
style_bold = sco_excel.excel_make_style(bold=True) style_bold = sco_excel.excel_make_style(bold=True)
style_base = sco_excel.excel_make_style() style_base = sco_excel.excel_make_style()
@ -482,9 +488,7 @@ class GenTable(object):
ses.append_blank_row() # empty line ses.append_blank_row() # empty line
ses.append_single_cell_row(self.origin, style_base) ses.append_single_cell_row(self.origin, style_base)
if wb is None: if wb is None:
return ses.generate_standalone() return ses.generate()
else:
ses.generate_embeded()
def text(self): def text(self):
"raw text representation of the table" "raw text representation of the table"
@ -573,7 +577,7 @@ class GenTable(object):
""" """
doc = ElementTree.Element( doc = ElementTree.Element(
self.xml_outer_tag, self.xml_outer_tag,
id=self.table_id, id=str(self.table_id),
origin=self.origin or "", origin=self.origin or "",
caption=self.caption or "", caption=self.caption or "",
) )
@ -587,7 +591,7 @@ class GenTable(object):
v = row.get(cid, "") v = row.get(cid, "")
if v is None: if v is None:
v = "" v = ""
x_cell = ElementTree.Element(cid, value=str(v)) x_cell = ElementTree.Element(str(cid), value=str(v))
x_row.append(x_cell) x_row.append(x_cell)
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING) return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
@ -610,7 +614,6 @@ class GenTable(object):
format="html", format="html",
page_title="", page_title="",
filename=None, filename=None,
REQUEST=None,
javascripts=[], javascripts=[],
with_html_headers=True, with_html_headers=True,
publish=True, publish=True,
@ -643,35 +646,53 @@ class GenTable(object):
H.append(html_sco_header.sco_footer()) H.append(html_sco_header.sco_footer())
return "\n".join(H) return "\n".join(H)
elif format == "pdf": elif format == "pdf":
objects = self.pdf() pdf_objs = self.pdf()
doc = sco_pdf.pdf_basic_page( pdf_doc = sco_pdf.pdf_basic_page(
objects, title=title, preferences=self.preferences pdf_objs, title=title, preferences=self.preferences
) )
if publish: if publish:
return scu.sendPDFFile(REQUEST, doc, filename + ".pdf") return scu.send_file(
pdf_doc,
filename,
suffix=".pdf",
mime=scu.PDF_MIMETYPE,
)
else: else:
return doc return pdf_doc
elif format == "xls" or format == "xlsx": elif format == "xls" or format == "xlsx": # dans les 2 cas retourne du xlsx
xls = self.excel() xls = self.excel()
if publish: if publish:
return sco_excel.send_excel_file( return scu.send_file(
REQUEST, xls, filename + scu.XLSX_SUFFIX xls,
filename,
suffix=scu.XLSX_SUFFIX,
mime=scu.XLSX_MIMETYPE,
) )
else: else:
return xls return xls
elif format == "text": elif format == "text":
return self.text() return self.text()
elif format == "csv": 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": elif format == "xml":
xml = self.xml() xml = self.xml()
if REQUEST and publish: if publish:
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE) return scu.send_file(
xml, filename, suffix=".xml", mime=scu.XML_MIMETYPE
)
return xml return xml
elif format == "json": elif format == "json":
js = self.json() js = self.json()
if REQUEST and publish: if publish:
REQUEST.RESPONSE.setHeader("content-type", scu.JSON_MIMETYPE) return scu.send_file(
js, filename, suffix=".json", mime=scu.JSON_MIMETYPE
)
return js return js
else: else:
log("make_page: format=%s" % format) log("make_page: format=%s" % format)
@ -732,5 +753,5 @@ if __name__ == "__main__":
document.build(objects) document.build(objects)
data = doc.getvalue() data = doc.getvalue()
open("/tmp/gen_table.pdf", "wb").write(data) 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) 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"?> _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"> <!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"> <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/scodoc.css" rel="stylesheet" type="text/css" />
<link href="/ScoDoc/static/css/menu.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 src="/ScoDoc/static/libjs/menu.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/sorttable.js"></script> <script src="/ScoDoc/static/libjs/sorttable.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/bubble.js"></script> <script src="/ScoDoc/static/libjs/bubble.js"></script>
<script type="text/javascript"> <script>
window.onload=function(){enableTooltips("gtrcontent")}; window.onload=function(){enableTooltips("gtrcontent")};
</script> </script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery.js"></script> <script 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 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/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" /> <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 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/etud_info.js"></script>
""" """
def scodoc_top_html_header(page_title="ScoDoc: bienvenue"): def scodoc_top_html_header(page_title="ScoDoc: bienvenue"):
H = [ H = [
_HTML_BEGIN % {"page_title": page_title, "encoding": scu.SCO_ENCODING}, _HTML_BEGIN % {"page_title": page_title, "encoding": scu.SCO_ENCODING},
_TOP_LEVEL_CSS,
"""</head><body class="gtrcontent" id="gtrcontent">""", """</head><body class="gtrcontent" id="gtrcontent">""",
scu.CUSTOM_HTML_HEADER_CNX, scu.CUSTOM_HTML_HEADER_CNX,
] ]
@ -185,13 +180,10 @@ def sco_header(
init_jquery = True init_jquery = True
H = [ H = [
"""<?xml version="1.0" encoding="%(encoding)s"?> """<!DOCTYPE html><html lang="fr">
<!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">
<head> <head>
<meta charset="utf-8"/>
<title>%(page_title)s</title> <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="LANG" content="fr" />
<meta name="DESCRIPTION" content="ScoDoc" /> <meta name="DESCRIPTION" content="ScoDoc" />
@ -206,9 +198,7 @@ def sco_header(
) )
if init_google_maps: if init_google_maps:
# It may be necessary to add an API key: # It may be necessary to add an API key:
H.append( H.append('<script src="https://maps.google.com/maps/api/js"></script>')
'<script type="text/javascript" src="https://maps.google.com/maps/api/js"></script>'
)
# Feuilles de style additionnelles: # Feuilles de style additionnelles:
for cssstyle in cssstyles: 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/menu.css" rel="stylesheet" type="text/css" />
<link href="/ScoDoc/static/css/gt_table.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 src="/ScoDoc/static/libjs/menu.js"></script>
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/bubble.js"></script> <script src="/ScoDoc/static/libjs/bubble.js"></script>
<script type="text/javascript"> <script>
window.onload=function(){enableTooltips("gtrcontent")}; window.onload=function(){enableTooltips("gtrcontent")};
var SCO_URL="%(ScoURL)s"; var SCO_URL="%(ScoURL)s";
@ -236,16 +226,14 @@ def sco_header(
# jQuery # jQuery
if init_jquery: if init_jquery:
H.append( 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( H.append('<script src="/ScoDoc/static/libjs/jquery.field.min.js"></script>')
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery.field.min.js"></script>'
)
# qTip # qTip
if init_qtip: if init_qtip:
H.append( 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( H.append(
'<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.css" />' '<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: if init_jquery_ui:
H.append( 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>' '<script 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>'
) )
# 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: if init_google_maps:
H.append( 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: if init_datatables:
H.append( H.append(
'<link rel="stylesheet" type="text/css" href="/ScoDoc/static/DataTables/datatables.min.css"/>' '<link rel="stylesheet" type="text/css" href="/ScoDoc/static/DataTables/datatables.min.css"/>'
) )
H.append( H.append('<script src="/ScoDoc/static/DataTables/datatables.min.js"></script>')
'<script type="text/javascript" src="/ScoDoc/static/DataTables/datatables.min.js"></script>'
)
# JS additionels # JS additionels
for js in javascripts: for js in javascripts:
H.append( H.append("""<script src="/ScoDoc/static/%s"></script>\n""" % js)
"""<script language="javascript" type="text/javascript" src="/ScoDoc/static/%s"></script>\n"""
% js
)
H.append( H.append(
"""<style type="text/css"> """<style>
.gtrcontent { .gtrcontent {
margin-left: %(margin_left)s; margin-left: %(margin_left)s;
height: 100%%; height: 100%%;
@ -290,7 +271,7 @@ def sco_header(
) )
# Scripts de la page: # Scripts de la page:
if scripts: if scripts:
H.append("""<script language="javascript" type="text/javascript">""") H.append("""<script>""")
for script in scripts: for script in scripts:
H.append(script) H.append(script)
H.append("""</script>""") H.append("""</script>""")
@ -337,13 +318,7 @@ def sco_footer():
def html_sem_header( 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" "Titre d'une page semestre avec lien vers tableau de bord"
# sem now unused and thus optional... # sem now unused and thus optional...

View File

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

View File

@ -186,6 +186,8 @@ class NotesTable(object):
self.use_ue_coefs = sco_preferences.get_preference( self.use_ue_coefs = sco_preferences.get_preference(
"use_ue_coefs", formsemestre_id "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 # Infos sur les etudiants
self.inscrlist = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( self.inscrlist = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
args={"formsemestre_id": formsemestre_id} args={"formsemestre_id": formsemestre_id}
@ -738,6 +740,7 @@ class NotesTable(object):
block_computation = ( block_computation = (
self.inscrdict[etudid]["etat"] == "D" self.inscrdict[etudid]["etat"] == "D"
or self.inscrdict[etudid]["etat"] == DEF or self.inscrdict[etudid]["etat"] == DEF
or self.block_moyennes
) )
moy_ues = {} moy_ues = {}

View File

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

View File

@ -626,7 +626,6 @@ def add_absence(
jour, jour,
matin, matin,
estjust, estjust,
REQUEST,
description=None, description=None,
moduleimpl_id=None, moduleimpl_id=None,
): ):
@ -656,7 +655,7 @@ def add_absence(
sco_abs_notification.abs_notify(etudid, jour) 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" "Ajoute un justificatif dans la base"
# unpublished # unpublished
if _isFarFutur(jour): if _isFarFutur(jour):
@ -665,7 +664,9 @@ def add_justif(etudid, jour, matin, REQUEST, description=None):
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute( 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(), vars(),
) )
logdb( logdb(
@ -678,7 +679,7 @@ def add_justif(etudid, jour, matin, REQUEST, description=None):
invalidate_abs_etud_date(etudid, jour) 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: for a in abslist:
etudid, jour, ampm = a.split(":") etudid, jour, ampm = a.split(":")
if ampm == "am": if ampm == "am":
@ -689,7 +690,7 @@ def _add_abslist(abslist, REQUEST, moduleimpl_id=None):
raise ValueError("invalid ampm !") raise ValueError("invalid ampm !")
# ajoute abs si pas deja absent # ajoute abs si pas deja absent
if count_abs(etudid, jour, jour, matin, moduleimpl_id) == 0: 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): 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) invalidate_abs_etud_date(etudid, jour)
def annule_justif(etudid, jour, matin, REQUEST=None): def annule_justif(etudid, jour, matin):
"Annule un justificatif" "Annule un justificatif"
# unpublished # unpublished
matin = _toboolean(matin) matin = _toboolean(matin)

View File

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

View File

@ -29,37 +29,40 @@
Archives are plain files, stored in Archives are plain files, stored in
<SCODOC_VAR_DIR>/archives/<deptid> <SCODOC_VAR_DIR>/archives/<dept_id>
(where <SCODOC_VAR_DIR> is usually /opt/scodoc-data, and <deptid> a departement 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 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> <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 Les documents liés à l'étudiant sont dans
<archivedir>/docetuds/<dept>/<etudid>/<YYYY-MM-DD-HH-MM-SS> <archivedir>/docetuds/<dept_id>/<etudid>/<YYYY-MM-DD-HH-MM-SS>
(etudid est ici soit Identite.scodoc7id, soit à défaut Identite.id) (etudid est ici Identite.id)
Les maquettes Apogée pour l'export des notes sont dans 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 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. qui est une description (humaine, format libre) de l'archive.
""" """
import os
import time
import datetime import datetime
import glob
import mimetypes
import os
import re import re
import shutil import shutil
import glob import time
import flask import flask
from flask import g from flask import g, request
from flask_login import current_user
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from config import Config from config import Config
from app import log from app import log
from app.models import Departement
from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_exceptions import ( from app.scodoc.sco_exceptions import (
AccessDenied, AccessDenied,
@ -108,7 +111,8 @@ class BaseArchiver(object):
If directory does not yet exist, create it. If directory does not yet exist, create it.
""" """
self.initialize() 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: try:
scu.GSL.acquire() scu.GSL.acquire()
if not os.path.isdir(dept_dir): if not os.path.isdir(dept_dir):
@ -127,7 +131,8 @@ class BaseArchiver(object):
:return: list of archive oids :return: list of archive oids
""" """
self.initialize() 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 + "*") dirs = glob.glob(base + "*")
return [os.path.split(x)[1] for x in dirs] return [os.path.split(x)[1] for x in dirs]
@ -244,31 +249,15 @@ class BaseArchiver(object):
log("reading archive file %s" % fname) log("reading archive file %s" % fname)
return open(fname, "rb").read() 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""" """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) archive_id = self.get_id_from_name(oid, archive_name)
data = self.get(archive_id, filename) data = self.get(archive_id, filename)
ext = os.path.splitext(filename.lower())[1] mime = mimetypes.guess_type(filename)[0]
if ext == ".html" or ext == ".htm": if mime is None:
return data mime = "application/octet-stream"
elif ext == ".xml":
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE) return scu.send_file(data, filename, mime=mime)
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
class SemsArchiver(BaseArchiver): class SemsArchiver(BaseArchiver):
@ -283,7 +272,6 @@ PVArchive = SemsArchiver()
def do_formsemestre_archive( def do_formsemestre_archive(
REQUEST,
formsemestre_id, formsemestre_id,
group_ids=[], # si indiqué, ne prend que ces groupes group_ids=[], # si indiqué, ne prend que ces groupes
description="", description="",
@ -305,7 +293,7 @@ def do_formsemestre_archive(
from app.scodoc.sco_recapcomplet import make_formsemestre_recapcomplet from app.scodoc.sco_recapcomplet import make_formsemestre_recapcomplet
sem = sco_formsemestre.get_formsemestre(formsemestre_id) 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) archive_id = PVArchive.create_obj_archive(sem_archive_id, description)
date = PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M") 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) data = data.encode(scu.SCO_ENCODING)
PVArchive.store(archive_id, "Bulletins.xml", data) PVArchive.store(archive_id, "Bulletins.xml", data)
# Decisions de jury, en XLS # Decisions de jury, en XLS
data = sco_pvjury.formsemestre_pvjury( data = sco_pvjury.formsemestre_pvjury(formsemestre_id, format="xls", publish=False)
formsemestre_id, format="xls", REQUEST=REQUEST, publish=False
)
if data: if data:
PVArchive.store(archive_id, "Decisions_Jury" + scu.XLSX_SUFFIX, data) PVArchive.store(archive_id, "Decisions_Jury" + scu.XLSX_SUFFIX, data)
# Classeur bulletins (PDF) # Classeur bulletins (PDF)
data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf( data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
formsemestre_id, REQUEST, version=bulVersion formsemestre_id, version=bulVersion
) )
if data: if data:
PVArchive.store(archive_id, "Bulletins.pdf", 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) 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. """Make and store new archive for this formsemestre.
(all students or only selected groups) (all students or only selected groups)
""" """
if not sco_permissions_check.can_edit_pv(formsemestre_id): if not sco_permissions_check.can_edit_pv(formsemestre_id):
raise AccessDenied( raise AccessDenied("opération non autorisée pour %s" % str(current_user))
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
)
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if not group_ids: if not group_ids:
@ -408,7 +392,6 @@ def formsemestre_archive(REQUEST, formsemestre_id, group_ids=[]):
H = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header(
REQUEST,
"Archiver les PV et résultats du semestre", "Archiver les PV et résultats du semestre",
sem=sem, sem=sem,
javascripts=sco_groups_view.JAVASCRIPTS, javascripts=sco_groups_view.JAVASCRIPTS,
@ -469,8 +452,8 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
) )
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
descr, descr,
cancelbutton="Annuler", cancelbutton="Annuler",
method="POST", method="POST",
@ -492,7 +475,6 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
else: else:
tf[2]["anonymous"] = False tf[2]["anonymous"] = False
do_formsemestre_archive( do_formsemestre_archive(
REQUEST,
formsemestre_id, formsemestre_id,
group_ids=group_ids, group_ids=group_ids,
description=tf[2]["description"], 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""" """Page listing archives"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
sem_archive_id = sem["scodoc7_id"] or formsemestre_id sem_archive_id = formsemestre_id
L = [] L = []
for archive_id in PVArchive.list_obj_archives(sem_archive_id): for archive_id in PVArchive.list_obj_archives(sem_archive_id):
a = { a = {
@ -530,7 +512,7 @@ def formsemestre_list_archives(REQUEST, formsemestre_id):
} }
L.append(a) 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: if not L:
H.append("<p>aucune archive enregistrée</p>") H.append("<p>aucune archive enregistrée</p>")
else: else:
@ -559,23 +541,19 @@ def formsemestre_list_archives(REQUEST, formsemestre_id):
return "\n".join(H) + html_sco_header.sco_footer() 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.""" """Send file to client."""
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
sem_archive_id = sem["scodoc7_id"] or formsemestre_id sem_archive_id = formsemestre_id
return PVArchive.get_archived_file(REQUEST, sem_archive_id, archive_name, filename) return PVArchive.get_archived_file(sem_archive_id, archive_name, filename)
def formsemestre_delete_archive( def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=False):
REQUEST, formsemestre_id, archive_name, dialog_confirmed=False
):
"""Delete an archive""" """Delete an archive"""
if not sco_permissions_check.can_edit_pv(formsemestre_id): if not sco_permissions_check.can_edit_pv(formsemestre_id):
raise AccessDenied( raise AccessDenied("opération non autorisée pour %s" % str(current_user))
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
)
sem = sco_formsemestre.get_formsemestre(formsemestre_id) 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) archive_id = PVArchive.get_id_from_name(sem_archive_id, archive_name)
dest_url = "formsemestre_list_archives?formsemestre_id=%s" % (formsemestre_id) 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. les dossiers d'admission et autres pièces utiles.
""" """
import flask 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 import app.scodoc.sco_utils as scu
from app.scodoc import sco_import_etuds from app.scodoc import sco_import_etuds
@ -58,14 +59,14 @@ def can_edit_etud_archive(authuser):
return authuser.has_permission(Permission.ScoEtudAddAnnotations) return authuser.has_permission(Permission.ScoEtudAddAnnotations)
def etud_list_archives_html(REQUEST, etudid): def etud_list_archives_html(etudid):
"""HTML snippet listing archives""" """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) etuds = sco_etud.get_etud_info(etudid=etudid)
if not etuds: if not etuds:
raise ScoValueError("étudiant inexistant") raise ScoValueError("étudiant inexistant")
etud = etuds[0] etud = etuds[0]
etud_archive_id = etud["scodoc7_id"] or etudid etud_archive_id = etudid
L = [] L = []
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id): for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
a = { a = {
@ -118,7 +119,7 @@ def add_archives_info_to_etud_list(etuds):
""" """
for etud in etuds: for etud in etuds:
l = [] 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): for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
l.append( l.append(
"%s (%s)" "%s (%s)"
@ -130,13 +131,11 @@ def add_archives_info_to_etud_list(etuds):
etud["etudarchive"] = ", ".join(l) 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.""" """Page with a form to choose and upload a file, with a description."""
# check permission # check permission
if not can_edit_etud_archive(REQUEST.AUTHENTICATED_USER): if not can_edit_etud_archive(current_user):
raise AccessDenied( raise AccessDenied("opération non autorisée pour %s" % current_user)
"opération non autorisée pour %s" % REQUEST.AUTHENTICATED_USER
)
etuds = sco_etud.get_etud_info(filled=True) etuds = sco_etud.get_etud_info(filled=True)
if not etuds: if not etuds:
raise ScoValueError("étudiant inexistant") raise ScoValueError("étudiant inexistant")
@ -153,8 +152,8 @@ def etud_upload_file_form(REQUEST, etudid):
% (scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)), % (scu.CONFIG.ETUD_MAX_FILE_SIZE // (1024 * 1024)),
] ]
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
( (
("etudid", {"default": etudid, "input_type": "hidden"}), ("etudid", {"default": etudid, "input_type": "hidden"}),
("datafile", {"input_type": "file", "title": "Fichier", "size": 30}), ("datafile", {"input_type": "file", "title": "Fichier", "size": 30}),
@ -181,7 +180,7 @@ def etud_upload_file_form(REQUEST, etudid):
data = tf[2]["datafile"].read() data = tf[2]["datafile"].read()
descr = tf[2]["description"] descr = tf[2]["description"]
filename = tf[2]["datafile"].filename 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( _store_etud_file_to_new_archive(
etud_archive_id, data, filename, description=descr 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) 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""" """Delete an archive"""
# check permission # check permission
if not can_edit_etud_archive(REQUEST.AUTHENTICATED_USER): if not can_edit_etud_archive(current_user):
raise AccessDenied( raise AccessDenied("opération non autorisée pour %s" % str(current_user))
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
)
etuds = sco_etud.get_etud_info(filled=True) etuds = sco_etud.get_etud_info(filled=True)
if not etuds: if not etuds:
raise ScoValueError("étudiant inexistant") raise ScoValueError("étudiant inexistant")
etud = etuds[0] 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) archive_id = EtudsArchive.get_id_from_name(etud_archive_id, archive_name)
if not dialog_confirmed: if not dialog_confirmed:
return scu.confirm_dialog( 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.""" """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: if not etuds:
raise ScoValueError("étudiant inexistant") raise ScoValueError("étudiant inexistant")
etud = etuds[0] etud = etuds[0]
etud_archive_id = etud["scodoc7_id"] or etud["etudid"] etud_archive_id = etud["etudid"]
return EtudsArchive.get_archived_file( return EtudsArchive.get_archived_file(etud_archive_id, archive_name, filename)
REQUEST, etud_archive_id, archive_name, filename
)
# --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants) # --- 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)""" """Feuille excel pour import fichiers etudiants (utilisé pour admissions)"""
fmt = sco_import_etuds.sco_import_format() fmt = sco_import_etuds.sco_import_format()
data = sco_import_etuds.sco_import_generate_excel_sample( 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"], extra_cols=["fichier_a_charger"],
) )
return sco_excel.send_excel_file( return scu.send_file(
REQUEST, data, "ImportFichiersEtudiants" + scu.XLSX_SUFFIX 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""" """Formulaire pour importation fichiers d'un groupe"""
H = [ H = [
html_sco_header.sco_header( html_sco_header.sco_header(
@ -310,8 +308,8 @@ def etudarchive_import_files_form(group_id, REQUEST=None):
] ]
F = html_sco_header.sco_footer() F = html_sco_header.sco_footer()
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
( (
("xlsfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}), ("xlsfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}),
("zipfile", {"title": "Fichier zip:", "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 """Génération des bulletins de notes
""" """
from app.models import formsemestre
import time import time
import pprint import pprint
import email import email
@ -35,11 +36,10 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.mime.base import MIMEBase from email.mime.base import MIMEBase
from email.header import Header from email.header import Header
from reportlab.lib.colors import Color 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 import url_for
from flask_login import current_user from flask_login import current_user
from flask_mail import Message from flask_mail import Message
@ -48,7 +48,7 @@ import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app import log from app import log
from app.scodoc.sco_permissions import Permission 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 html_sco_header
from app.scodoc import htmlutils from app.scodoc import htmlutils
from app.scodoc import sco_abs from app.scodoc import sco_abs
@ -121,9 +121,7 @@ def make_context_dict(sem, etud):
return C return C
def formsemestre_bulletinetud_dict( def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
formsemestre_id, etudid, version="long", REQUEST=None
):
"""Collecte informations pour bulletin de notes """Collecte informations pour bulletin de notes
Retourne un dictionnaire (avec valeur par défaut chaine vide). Retourne un dictionnaire (avec valeur par défaut chaine vide).
Le contenu du dictionnaire dépend des options (rangs, ...) Le contenu du dictionnaire dépend des options (rangs, ...)
@ -143,10 +141,7 @@ def formsemestre_bulletinetud_dict(
I["etudid"] = etudid I["etudid"] = etudid
I["formsemestre_id"] = formsemestre_id I["formsemestre_id"] = formsemestre_id
I["sem"] = nt.sem I["sem"] = nt.sem
if REQUEST: I["server_name"] = request.url_root
I["server_name"] = REQUEST.BASE0
else:
I["server_name"] = ""
# Formation et parcours # Formation et parcours
I["formation"] = sco_formations.formation_list( I["formation"] = sco_formations.formation_list(
@ -771,14 +766,16 @@ def formsemestre_bulletinetud(
xml_with_decisions=False, xml_with_decisions=False,
force_publishing=False, # force publication meme si semestre non publie sur "portail" force_publishing=False, # force publication meme si semestre non publie sur "portail"
prefer_mail_perso=False, prefer_mail_perso=False,
REQUEST=None,
): ):
"page bulletin de notes" "page bulletin de notes"
try: try:
etud = sco_etud.get_etud_info(filled=True)[0] etud = sco_etud.get_etud_info(filled=True)[0]
etudid = etud["etudid"] etudid = etud["etudid"]
except: 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( bulletin = do_formsemestre_bulletinetud(
formsemestre_id, formsemestre_id,
@ -788,15 +785,15 @@ def formsemestre_bulletinetud(
xml_with_decisions=xml_with_decisions, xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing, force_publishing=force_publishing,
prefer_mail_perso=prefer_mail_perso, prefer_mail_perso=prefer_mail_perso,
REQUEST=REQUEST,
)[0] )[0]
if format not in {"html", "pdfmail"}: 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) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [ H = [
_formsemestre_bulletinetud_header_html( _formsemestre_bulletinetud_header_html(
etud, etudid, sem, formsemestre_id, format, version, REQUEST etud, etudid, sem, formsemestre_id, format, version
), ),
bulletin, bulletin,
] ]
@ -854,7 +851,6 @@ def do_formsemestre_bulletinetud(
etudid, etudid,
version="long", # short, long, selectedevals version="long", # short, long, selectedevals
format="html", format="html",
REQUEST=None,
nohtml=False, nohtml=False,
xml_with_decisions=False, # force decisions dans XML xml_with_decisions=False, # force decisions dans XML
force_publishing=False, # force publication meme si semestre non publie sur "portail" 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é. """Génère le bulletin au format demandé.
Retourne: (bul, filigranne) 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"). et filigranne est un message à placer en "filigranne" (eg "Provisoire").
""" """
if format == "xml": if format == "xml":
bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud( bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud(
formsemestre_id, formsemestre_id,
etudid, etudid,
REQUEST=REQUEST,
xml_with_decisions=xml_with_decisions, xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing, force_publishing=force_publishing,
version=version, version=version,
@ -881,19 +876,18 @@ def do_formsemestre_bulletinetud(
bul = sco_bulletins_json.make_json_formsemestre_bulletinetud( bul = sco_bulletins_json.make_json_formsemestre_bulletinetud(
formsemestre_id, formsemestre_id,
etudid, etudid,
REQUEST=REQUEST,
xml_with_decisions=xml_with_decisions, xml_with_decisions=xml_with_decisions,
force_publishing=force_publishing, force_publishing=force_publishing,
version=version, version=version,
) )
return bul, "" return bul, ""
I = formsemestre_bulletinetud_dict(formsemestre_id, etudid, REQUEST=REQUEST) I = formsemestre_bulletinetud_dict(formsemestre_id, etudid)
etud = I["etud"] etud = I["etud"]
if format == "html": if format == "html":
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud( htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud(
I, version=version, format="html", REQUEST=REQUEST I, version=version, format="html"
) )
return htm, I["filigranne"] return htm, I["filigranne"]
@ -903,11 +897,10 @@ def do_formsemestre_bulletinetud(
version=version, version=version,
format="pdf", format="pdf",
stand_alone=(format != "pdfpart"), stand_alone=(format != "pdfpart"),
REQUEST=REQUEST,
) )
if format == "pdf": if format == "pdf":
return ( return (
scu.sendPDFFile(REQUEST, bul, filename), scu.sendPDFFile(bul, filename),
I["filigranne"], I["filigranne"],
) # unused ret. value ) # unused ret. value
else: else:
@ -923,11 +916,11 @@ def do_formsemestre_bulletinetud(
htm = "" # speed up if html version not needed htm = "" # speed up if html version not needed
else: else:
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud( 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( pdfdata, filename = sco_bulletins_generator.make_formsemestre_bulletinetud(
I, version=version, format="pdf", REQUEST=REQUEST I, version=version, format="pdf"
) )
if prefer_mail_perso: if prefer_mail_perso:
@ -998,7 +991,6 @@ def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr):
# Attach pdf # Attach pdf
msg.attach(filename, scu.PDF_MIMETYPE, pdfdata) msg.attach(filename, scu.PDF_MIMETYPE, pdfdata)
log("mail bulletin a %s" % recipient_addr) log("mail bulletin a %s" % recipient_addr)
email.send_message(msg) email.send_message(msg)
@ -1010,7 +1002,6 @@ def _formsemestre_bulletinetud_header_html(
formsemestre_id=None, formsemestre_id=None,
format=None, format=None,
version=None, version=None,
REQUEST=None,
): ):
H = [ H = [
html_sco_header.sco_header( html_sco_header.sco_header(
@ -1033,7 +1024,7 @@ def _formsemestre_bulletinetud_header_html(
), ),
""" """
<form name="f" method="GET" action="%s">""" <form name="f" method="GET" action="%s">"""
% REQUEST.URL0, % request.base_url,
f"""Bulletin <span class="bull_liensemestre"><a href="{ f"""Bulletin <span class="bull_liensemestre"><a href="{
url_for("notes.formsemestre_status", url_for("notes.formsemestre_status",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
@ -1063,14 +1054,20 @@ def _formsemestre_bulletinetud_header_html(
H.append("""</select></td>""") H.append("""</select></td>""")
# Menu # Menu
endpoint = "notes.formsemestre_bulletinetud" endpoint = "notes.formsemestre_bulletinetud"
url = REQUEST.URL0
qurl = six.moves.urllib.parse.quote_plus(url + "?" + REQUEST.QUERY_STRING)
menuBul = [ menuBul = [
{ {
"title": "Réglages bulletins", "title": "Réglages bulletins",
"endpoint": "notes.formsemestre_edit_options", "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"]) "enabled": (current_user.id in sem["responsables"])
or current_user.has_permission(Permission.ScoImplement), or current_user.has_permission(Permission.ScoImplement),
}, },
@ -1113,6 +1110,16 @@ def _formsemestre_bulletinetud_header_html(
"enabled": etud["emailperso"] "enabled": etud["emailperso"]
and can_send_bulletin_by_mail(formsemestre_id), 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", "title": "Version XML",
"endpoint": endpoint, "endpoint": endpoint,
@ -1188,9 +1195,14 @@ def _formsemestre_bulletinetud_header_html(
H.append( H.append(
'<td> <a href="%s">%s</a></td>' '<td> <a href="%s">%s</a></td>'
% ( % (
url url_for(
+ "?formsemestre_id=%s&etudid=%s&format=pdf&version=%s" "notes.formsemestre_bulletinetud",
% (formsemestre_id, etudid, version), scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
etudid=etudid,
format="pdf",
version=version,
),
scu.ICON_PDF, scu.ICON_PDF,
) )
) )
@ -1201,9 +1213,7 @@ def _formsemestre_bulletinetud_header_html(
""" """
% ( % (
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid), url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
sco_photos.etud_photo_html( sco_photos.etud_photo_html(etud, title="fiche de " + etud["nom"]),
etud, title="fiche de " + etud["nom"], REQUEST=REQUEST
),
) )
) )
H.append( H.append(

View File

@ -52,6 +52,9 @@ import reportlab
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame 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 import sco_utils as scu
from app.scodoc.sco_exceptions import NoteProcessError from app.scodoc.sco_exceptions import NoteProcessError
from app import log from app import log
@ -148,14 +151,7 @@ class BulletinGenerator(object):
def get_filename(self): def get_filename(self):
"""Build a filename to be proposed to the web client""" """Build a filename to be proposed to the web client"""
sem = sco_formsemestre.get_formsemestre(self.infos["formsemestre_id"]) sem = sco_formsemestre.get_formsemestre(self.infos["formsemestre_id"])
dt = time.strftime("%Y-%m-%d") return scu.bul_filename(sem, self.infos["etud"], "pdf")
filename = "bul-%s-%s-%s.pdf" % (
sem["titre_num"],
dt,
self.infos["etud"]["nom"],
)
filename = scu.unescape_html(filename).replace(" ", "_").replace("&", "")
return filename
def generate(self, format="", stand_alone=True): def generate(self, format="", stand_alone=True):
"""Return bulletin in specified format""" """Return bulletin in specified format"""
@ -260,7 +256,6 @@ def make_formsemestre_bulletinetud(
version="long", # short, long, selectedevals version="long", # short, long, selectedevals
format="pdf", # html, pdf format="pdf", # html, pdf
stand_alone=True, stand_alone=True,
REQUEST=None,
): ):
"""Bulletin de notes """Bulletin de notes
@ -286,10 +281,10 @@ def make_formsemestre_bulletinetud(
PDFLOCK.acquire() PDFLOCK.acquire()
bul_generator = gen_class( bul_generator = gen_class(
infos, infos,
authuser=REQUEST.AUTHENTICATED_USER, authuser=current_user,
version=version, version=version,
filigranne=infos["filigranne"], filigranne=infos["filigranne"],
server_name=REQUEST.BASE0, server_name=request.url_root,
) )
if format not in bul_generator.supported_formats: if format not in bul_generator.supported_formats:
# use standard generator # use standard generator
@ -301,10 +296,10 @@ def make_formsemestre_bulletinetud(
gen_class = bulletin_get_class(bul_class_name) gen_class = bulletin_get_class(bul_class_name)
bul_generator = gen_class( bul_generator = gen_class(
infos, infos,
authuser=REQUEST.AUTHENTICATED_USER, authuser=current_user,
version=version, version=version,
filigranne=infos["filigranne"], filigranne=infos["filigranne"],
server_name=REQUEST.BASE0, server_name=request.url_root,
) )
data = bul_generator.generate(format=format, stand_alone=stand_alone) 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( def make_json_formsemestre_bulletinetud(
formsemestre_id, formsemestre_id: int,
etudid, etudid: int,
REQUEST=None,
xml_with_decisions=False, xml_with_decisions=False,
version="long", version="long",
force_publishing=False, # force publication meme si semestre non publie sur "portail" force_publishing=False, # force publication meme si semestre non publie sur "portail"
): ) -> str:
"""Renvoie bulletin en chaine JSON""" """Renvoie bulletin en chaine JSON"""
d = formsemestre_bulletinetud_published_dict( d = formsemestre_bulletinetud_published_dict(
formsemestre_id, formsemestre_id,
etudid, etudid,
force_publishing=force_publishing, force_publishing=force_publishing,
REQUEST=REQUEST,
xml_with_decisions=xml_with_decisions, xml_with_decisions=xml_with_decisions,
version=version, version=version,
) )
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", scu.JSON_MIMETYPE)
return json.dumps(d, cls=scu.ScoDocJSONEncoder) return json.dumps(d, cls=scu.ScoDocJSONEncoder)
@ -79,7 +74,6 @@ def formsemestre_bulletinetud_published_dict(
etudid, etudid,
force_publishing=False, force_publishing=False,
xml_nodate=False, xml_nodate=False,
REQUEST=None,
xml_with_decisions=False, # inclue les decisions même si non publiées xml_with_decisions=False, # inclue les decisions même si non publiées
version="long", version="long",
): ):

View File

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

View File

@ -56,7 +56,7 @@ et sur page "réglages bulletin" (avec formsemestre_id)
# import os # 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""" # """Change pdf signature"""
# filename = _get_sig_existing_filename( # filename = _get_sig_existing_filename(
# side, formsemestre_id=formsemestre_id # 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") # raise ValueError("invalid value for 'side' parameter")
# signatureloc = get_bul_sig_img() # signatureloc = get_bul_sig_img()
# H = [ # 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> # """<h2>Changement de la signature bulletin de %(sidetxt)s</h2>
# """ # """
# % (sidetxt,), # % (sidetxt,),

View File

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

View File

@ -157,7 +157,7 @@ class EvaluationCache(ScoDocCache):
class AbsSemEtudCache(ScoDocCache): class AbsSemEtudCache(ScoDocCache):
"""Cache pour les comptes d'absences d'un étudiant dans un semestre. """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. change les dates début/fin d'un semestre.
C'est pourquoi il expire après timeout secondes. C'est pourquoi il expire après timeout secondes.
Le timeout evite aussi d'éliminer explicitement ces éléments cachés lors 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) (coût théorique en heures équivalent TD)
""" """
from flask import request
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
@ -45,7 +47,6 @@ def formsemestre_table_estim_cost(
n_group_tp=1, n_group_tp=1,
coef_tp=1, coef_tp=1,
coef_cours=1.5, coef_cours=1.5,
REQUEST=None,
): ):
""" """
Rapports estimation coût de formation basé sur le programme pédagogique Rapports estimation coût de formation basé sur le programme pédagogique
@ -156,7 +157,6 @@ def formsemestre_estim_cost(
coef_tp=1, coef_tp=1,
coef_cours=1.5, coef_cours=1.5,
format="html", format="html",
REQUEST=None,
): ):
"""Page (formulaire) estimation coûts""" """Page (formulaire) estimation coûts"""
@ -171,7 +171,6 @@ def formsemestre_estim_cost(
n_group_tp=n_group_tp, n_group_tp=n_group_tp,
coef_tp=coef_tp, coef_tp=coef_tp,
coef_cours=coef_cours, coef_cours=coef_cours,
REQUEST=REQUEST,
) )
h = """ h = """
<form name="f" method="get" action="%s"> <form name="f" method="get" action="%s">
@ -182,7 +181,7 @@ def formsemestre_estim_cost(
<br/> <br/>
</form> </form>
""" % ( """ % (
REQUEST.URL0, request.base_url,
formsemestre_id, formsemestre_id,
n_group_td, n_group_td,
n_group_tp, n_group_tp,
@ -190,11 +189,11 @@ def formsemestre_estim_cost(
) )
tab.html_before_table = h tab.html_before_table = h
tab.base_url = "%s?formsemestre_id=%s&n_group_td=%s&n_group_tp=%s&coef_tp=%s" % ( 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, formsemestre_id,
n_group_td, n_group_td,
n_group_tp, n_group_tp,
coef_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 Rapport (table) avec dernier semestre fréquenté et débouché de chaque étudiant
""" """
import http 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.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
@ -47,10 +47,10 @@ from app.scodoc import sco_etud
import sco_version 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.""" """Rapport (table) pour les débouchés des étudiants sortis à partir de l'année indiquée."""
if not start_year: if not start_year:
return report_debouche_ask_date(REQUEST=REQUEST) return report_debouche_ask_date()
if format == "xls": if format == "xls":
keep_numeric = True # pas de conversion des notes en strings keep_numeric = True # pas de conversion des notes en strings
else: 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() + "" "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.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( return tab.make_page(
title="""<h2 class="formsemestre">Débouchés étudiants </h2>""", title="""<h2 class="formsemestre">Débouchés étudiants </h2>""",
init_qtip=True, init_qtip=True,
javascripts=["js/etud_info.js"], javascripts=["js/etud_info.js"],
format=format, format=format,
REQUEST=REQUEST,
with_html_headers=True, with_html_headers=True,
) )
@ -194,7 +193,7 @@ def table_debouche_etudids(etudids, keep_numeric=True):
return tab return tab
def report_debouche_ask_date(REQUEST=None): def report_debouche_ask_date():
"""Formulaire demande date départ""" """Formulaire demande date départ"""
return ( return (
html_sco_header.sco_header() html_sco_header.sco_header()
@ -249,7 +248,7 @@ def itemsuivi_get(cnx, itemsuivi_id, ignore_errors=False):
return None return None
def itemsuivi_suppress(itemsuivi_id, REQUEST=None): def itemsuivi_suppress(itemsuivi_id):
"""Suppression d'un item""" """Suppression d'un item"""
if not sco_permissions_check.can_edit_suivi(): if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") 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) _itemsuivi_delete(cnx, itemsuivi_id)
logdb(cnx, method="itemsuivi_suppress", etudid=item["etudid"]) logdb(cnx, method="itemsuivi_suppress", etudid=item["etudid"])
log("suppressed itemsuivi %s" % (itemsuivi_id,)) 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""" """Creation d'un item"""
if not sco_permissions_check.can_edit_suivi(): if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") 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)) log("created itemsuivi %s for %s" % (itemsuivi_id, etudid))
item = itemsuivi_get(cnx, itemsuivi_id) item = itemsuivi_get(cnx, itemsuivi_id)
if format == "json": if format == "json":
return scu.sendJSON(REQUEST, item) return scu.sendJSON(item)
return item return item
def itemsuivi_set_date(itemsuivi_id, item_date, REQUEST=None): def itemsuivi_set_date(itemsuivi_id, item_date):
"""set item date """set item date
item_date is a string dd/mm/yyyy 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 = itemsuivi_get(cnx, itemsuivi_id)
item["item_date"] = item_date item["item_date"] = item_date
_itemsuivi_edit(cnx, item) _itemsuivi_edit(cnx, item)
return ("", 204)
def itemsuivi_set_situation(object, value, REQUEST=None): def itemsuivi_set_situation(object, value):
"""set situation""" """set situation"""
if not sco_permissions_check.can_edit_suivi(): if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") 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 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""" """Liste des items pour cet étudiant, avec tags"""
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
items = _itemsuivi_list(cnx, {"etudid": etudid}) items = _itemsuivi_list(cnx, {"etudid": etudid})
for it in items: for it in items:
it["tags"] = ", ".join(itemsuivi_tag_list(it["itemsuivi_id"])) it["tags"] = ", ".join(itemsuivi_tag_list(it["itemsuivi_id"]))
if format == "json": if format == "json":
return scu.sendJSON(REQUEST, items) return scu.sendJSON(items)
return items return items
@ -328,7 +329,7 @@ def itemsuivi_tag_list(itemsuivi_id):
return [x["title"] for x in r] 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)""" """List all used tag names (for auto-completion)"""
# restrict charset to avoid injections # restrict charset to avoid injections
if not scu.ALPHANUM_EXP.match(term): 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] 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: """taglist may either be:
a string with tag names separated by commas ("un;deux") a string with tag names separated by commas ("un;deux")
or a list of strings (["un", "deux"]) or a list of strings (["un", "deux"])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,11 +32,11 @@
Voir sco_apogee_csv.py pour la structure du fichier Apogée. Voir sco_apogee_csv.py pour la structure du fichier Apogée.
Stockage: utilise sco_archive.py 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 pour une maquette de l'année scolaire 2016, semestre 1, etape V3ASR
ou bien (à partir de ScoDoc 1678) : 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. 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. 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 from zipfile import ZipFile
import flask 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 # from werkzeug.utils import send_file
@ -62,7 +62,6 @@ def apo_semset_maq_status(
block_export_res_ues=False, block_export_res_ues=False,
block_export_res_modules=False, block_export_res_modules=False,
block_export_res_sdj=True, block_export_res_sdj=True,
REQUEST=None,
): ):
"""Page statut / tableau de bord""" """Page statut / tableau de bord"""
if not semset_id: if not semset_id:
@ -83,7 +82,7 @@ def apo_semset_maq_status(
prefs = sco_preferences.SemPreferences() prefs = sco_preferences.SemPreferences()
tab_archives = table_apo_csv_list(semset, REQUEST=REQUEST) tab_archives = table_apo_csv_list(semset)
( (
ok_for_export, ok_for_export,
@ -250,7 +249,7 @@ def apo_semset_maq_status(
"""<form name="f" method="get" action="%s"> """<form name="f" method="get" action="%s">
<input type="hidden" name="semset_id" value="%s"></input> <input type="hidden" name="semset_id" value="%s"></input>
<div><input type="checkbox" name="allow_missing_apo" value="1" onchange="document.f.submit()" """ <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: if allow_missing_apo:
H.append("checked") H.append("checked")
@ -430,7 +429,7 @@ def apo_semset_maq_status(
return "\n".join(H) 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)""" """Table des archives (triée par date d'archivage)"""
annee_scolaire = semset["annee_scolaire"] annee_scolaire = semset["annee_scolaire"]
sem_id = semset["sem_id"] sem_id = semset["sem_id"]
@ -476,7 +475,7 @@ def table_apo_csv_list(semset, REQUEST=None):
rows=T, rows=T,
html_class="table_leftalign apo_maq_list", html_class="table_leftalign apo_maq_list",
html_sortable=True, 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', # caption='Maquettes enregistrées',
preferences=sco_preferences.SemPreferences(), preferences=sco_preferences.SemPreferences(),
) )
@ -484,7 +483,7 @@ def table_apo_csv_list(semset, REQUEST=None):
return tab 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 """Table des étudiants Apogée par nips
nip_list est une chaine, codes nip séparés par des , 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()), etuds=list(etuds.values()),
keys=("nip", "etape_apo", "nom", "prenom", "inscriptions_scodoc"), keys=("nip", "etape_apo", "nom", "prenom", "inscriptions_scodoc"),
format=format, 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""" """Table des étudiants ScoDoc par nips ou etudids"""
if not isinstance(nip_list, str): if not isinstance(nip_list, str):
nip_list = str(nip_list) nip_list = str(nip_list)
@ -541,13 +539,10 @@ def view_scodoc_etuds(semset_id, title="", nip_list="", format="html", REQUEST=N
etuds=etuds, etuds=etuds,
keys=("code_nip", "nom", "prenom"), keys=("code_nip", "nom", "prenom"),
format=format, format=format,
REQUEST=REQUEST,
) )
def _view_etuds_page( def _view_etuds_page(semset_id, title="", etuds=[], keys=(), format="html"):
semset_id, title="", etuds=[], keys=(), format="html", REQUEST=None
):
# Tri les étudiants par nom: # Tri les étudiants par nom:
if etuds: if etuds:
etuds.sort(key=lambda x: (x["nom"], x["prenom"])) etuds.sort(key=lambda x: (x["nom"], x["prenom"]))
@ -578,7 +573,7 @@ def _view_etuds_page(
preferences=sco_preferences.SemPreferences(), preferences=sco_preferences.SemPreferences(),
) )
if format != "html": if format != "html":
return tab.make_page(format=format, REQUEST=REQUEST) return tab.make_page(format=format)
H.append(tab.html()) H.append(tab.html())
@ -590,9 +585,7 @@ def _view_etuds_page(
return "\n".join(H) + html_sco_header.sco_footer() return "\n".join(H) + html_sco_header.sco_footer()
def view_apo_csv_store( def view_apo_csv_store(semset_id="", csvfile=None, data="", autodetect=False):
semset_id="", csvfile=None, data="", autodetect=False, REQUEST=None
):
"""Store CSV data """Store CSV data
Le semset identifie l'annee scolaire et le semestre Le semset identifie l'annee scolaire et le semestre
Si csvfile, lit depuis FILE, sinon utilise data 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) 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""" """Download maquette and store it"""
if not semset_id: if not semset_id:
raise ValueError("invalid null 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 # here, data is utf8
# but we store and generate latin1 files, to ease further import in Apogée # 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 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( def view_apo_csv_delete(etape_apo="", semset_id="", dialog_confirmed=False):
etape_apo="", semset_id="", dialog_confirmed=False, REQUEST=None
):
"""Delete CSV file""" """Delete CSV file"""
if not semset_id: if not semset_id:
raise ValueError("invalid null 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") 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 """Visualise une maquette stockée
Si format="raw", renvoie le fichier maquette tel quel 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"] sem_id = semset["sem_id"]
csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id) csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id)
if format == "raw": 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"]) 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, rows=etuds,
html_sortable=True, html_sortable=True,
html_class="table_leftalign apo_maq_table", 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, filename="students_" + etape_apo,
caption="Etudiants Apogée en " + etape_apo, caption="Etudiants Apogée en " + etape_apo,
preferences=sco_preferences.SemPreferences(), preferences=sco_preferences.SemPreferences(),
) )
if format != "html": if format != "html":
return tab.make_page(format=format, REQUEST=REQUEST) return tab.make_page(format=format)
H += [ H += [
tab.html(), tab.html(),
@ -768,7 +761,7 @@ def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
return "\n".join(H) return "\n".join(H)
# called from Web # called from Web (GET)
def apo_csv_export_results( def apo_csv_export_results(
semset_id, semset_id,
block_export_res_etape=False, block_export_res_etape=False,

View File

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

View File

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

View File

@ -35,11 +35,11 @@ from enum import Enum
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
import openpyxl.utils.datetime 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 import Workbook, load_workbook
from openpyxl.cell import WriteOnlyCell from openpyxl.cell import WriteOnlyCell
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill 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 import app.scodoc.sco_utils as scu
from app.scodoc import notesdb from app.scodoc import notesdb
@ -59,24 +59,9 @@ class COLORS(Enum):
LIGHT_YELLOW = "FFFFFF99" 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: # 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): 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) 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: class ScoExcelBook:
"""Permet la génération d'un classeur xlsx composé de plusieurs feuilles. """Permet la génération d'un classeur xlsx composé de plusieurs feuilles.
usage: usage:
@ -98,13 +94,16 @@ class ScoExcelBook:
def __init__(self): def __init__(self):
self.sheets = [] # list of sheets self.sheets = [] # list of sheets
self.wb = Workbook(write_only=True)
def create_sheet(self, sheet_name="feuille", default_style=None): def create_sheet(self, sheet_name="feuille", default_style=None):
"""Crée une nouvelle feuille dans ce classeur """Crée une nouvelle feuille dans ce classeur
sheet_name -- le nom de la feuille sheet_name -- le nom de la feuille
default_style -- le style par défaut 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) self.sheets.append(sheet)
return sheet return sheet
@ -112,12 +111,12 @@ class ScoExcelBook:
"""génération d'un stream binaire représentant la totalité du classeur. """génération d'un stream binaire représentant la totalité du classeur.
retourne le flux retourne le flux
""" """
wb = Workbook(write_only=True)
for sheet in self.sheets: for sheet in self.sheets:
sheet.generate(self) sheet.prepare()
# construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream) # construction d'un flux
# (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
with NamedTemporaryFile() as tmp: with NamedTemporaryFile() as tmp:
wb.save(tmp.name) self.wb.save(tmp.name)
tmp.seek(0) tmp.seek(0)
return tmp.read() return tmp.read()
@ -125,6 +124,7 @@ class ScoExcelBook:
def excel_make_style( def excel_make_style(
bold=False, bold=False,
italic=False, italic=False,
outline=False,
color: COLORS = COLORS.BLACK, color: COLORS = COLORS.BLACK,
bgcolor: COLORS = None, bgcolor: COLORS = None,
halign=None, halign=None,
@ -145,7 +145,14 @@ def excel_make_style(
size -- taille de police size -- taille de police
""" """
style = {} 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 style["font"] = font
if bgcolor: if bgcolor:
style["fill"] = PatternFill(fill_type="solid", fgColor=bgcolor.value) 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): def __init__(self, sheet_name="feuille", default_style=None, wb=None):
"""Création de la feuille. """Création de la feuille. sheet_name
sheet_name -- le nom de la feuille -- le nom de la feuille default_style
default_style -- le style par défaut des cellules -- le style par défaut des cellules ws
wb -- le WorkBook dans laquelle se trouve la feuille. Si wb est None (cas d'un classeur mono-feuille), -- None si la feuille est autonome (dans ce cas ell crée son propre wb), sinon c'est la worksheet
un workbook est crée et associé à cette feuille. 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. # 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' ?) # si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)
self.sheet_name = sheet_name[ self.sheet_name = adjust_sheetname(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 }
if default_style is None: if default_style is None:
default_style = excel_make_style() default_style = excel_make_style()
self.default_style = default_style self.default_style = default_style
self.wb = wb or Workbook(write_only=True) # Création de workbook si nécessaire if wb is None:
self.ws = self.wb.create_sheet(title=self.sheet_name) 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.column_dimensions = {}
self.row_dimensions = {}
def set_column_dimension_width(self, cle, value): def excel_make_composite_style(
"""Détermine la largeur d'une colonne. self,
cle -- identifie la colonne ("A"n "B", ...) alignment=None,
value -- la dimension (unité : 7 pixels comme affiché dans Excel) 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)
""" """
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 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): def set_row_dimension_height(self, cle=None, value=21):
"""Masque ou affiche une colonne. """Détermine la hauteur d'une ligne. cle -- identifie la ligne (1, 2, ...) si None,
cle -- identifie la colonne ("A"n "B", ...) 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) 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): def make_cell(self, value: any = None, style=None, comment=None):
"""Construit une cellule. """Construit une cellule.
@ -232,8 +291,12 @@ class ScoExcelSheet:
style = self.default_style style = self.default_style
if "font" in style: if "font" in style:
cell.font = style["font"] cell.font = style["font"]
if "alignment" in style:
cell.alignment = style["alignment"]
if "border" in style: if "border" in style:
cell.border = style["border"] cell.border = style["border"]
if "fill" in style:
cell.fill = style["fill"]
if "number_format" in style: if "number_format" in style:
cell.number_format = style["number_format"] cell.number_format = style["number_format"]
if "fill" in style: if "fill" in style:
@ -272,73 +335,31 @@ class ScoExcelSheet:
"""ajoute une ligne déjà construite à la feuille.""" """ajoute une ligne déjà construite à la feuille."""
self.rows.append(row) self.rows.append(row)
# def set_style(self, style=None, li=None, co=None): def prepare(self):
# 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):
"""génére un flux décrivant la feuille. """génére un flux décrivant la feuille.
Ce flux pourra ensuite être repris dans send_excel_file (classeur mono feille) Ce flux pourra ensuite être repris dans send_excel_file (classeur mono feille)
ou pour la génération d'un classeur multi-feuilles ou pour la génération d'un classeur multi-feuilles
""" """
for col in self.column_dimensions.keys(): for row in self.column_dimensions.keys():
self.ws.column_dimensions[col] = self.column_dimensions[col] 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: for row in self.rows:
self.ws.append(row) self.ws.append(row)
def generate_standalone(self): def generate(self):
"""génération d'un classeur mono-feuille""" """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) # construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
self.prepare()
with NamedTemporaryFile() as tmp: with NamedTemporaryFile() as tmp:
self.wb.save(tmp.name) self.wb.save(tmp.name)
tmp.seek(0) tmp.seek(0)
return tmp.read() 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( def excel_simple_table(
titles=None, lines=None, sheet_name=b"feuille", titles_styles=None, comments=None 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 cell_style = text_style
cells.append(ws.make_cell(it, cell_style)) cells.append(ws.make_cell(it, cell_style))
ws.append_row(cells) ws.append_row(cells)
return ws.generate_standalone() return ws.generate()
def excel_feuille_saisie(e, titreannee, description, lines): 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), 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): def excel_bytes_to_list(bytes_content):
try:
filelike = io.BytesIO(bytes_content) filelike = io.BytesIO(bytes_content)
return _excel_to_list(filelike) 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): def excel_file_to_list(filename):
try:
return _excel_to_list(filename) 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 """returns list of list
convert_to_string is a conversion function applied to all non-string values (ie numbers) 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) wb = load_workbook(filename=filelike, read_only=True, data_only=True)
except: except:
log("Excel_to_list: failure to import document") 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( raise ScoValueError(
"Fichier illisible: assurez-vous qu'il s'agit bien d'un document Excel !" "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) cell_2 = ws.make_cell(("Liste éditée le " + dt), style1i)
ws.append_row([None, cell_2]) 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 # Exception qui stoque dest_url, utilisee dans Zope standard_error_message
class ScoValueError(ScoException): class ScoValueError(ScoException):
def __init__(self, msg, dest_url=None, REQUEST=None): def __init__(self, msg, dest_url=None):
ScoException.__init__(self, msg) ScoException.__init__(self, msg)
self.dest_url = dest_url self.dest_url = dest_url
if REQUEST and dest_url:
REQUEST.set("dest_url", dest_url)
class FormatError(ScoValueError): class FormatError(ScoValueError):
@ -79,7 +77,7 @@ class ScoConfigurationError(ScoValueError):
class ScoLockedFormError(ScoException): class ScoLockedFormError(ScoException):
def __init__(self, msg="", REQUEST=None): def __init__(self, msg=""):
msg = ( msg = (
"Cette formation est verrouillée (car il y a un semestre verrouillé qui s'y réfère). " "Cette formation est verrouillée (car il y a un semestre verrouillé qui s'y réfère). "
+ str(msg) + str(msg)
@ -90,7 +88,7 @@ class ScoLockedFormError(ScoException):
class ScoGenError(ScoException): class ScoGenError(ScoException):
"exception avec affichage d'une page explicative ad-hoc" "exception avec affichage d'une page explicative ad-hoc"
def __init__(self, msg="", REQUEST=None): def __init__(self, msg=""):
ScoException.__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 """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.notesdb as ndb
import app.scodoc.sco_utils as scu 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} return {x["id"] for x in s}
def scodoc_table_results( def scodoc_table_results(start_date="", end_date="", types_parcours=[], format="html"):
start_date="", end_date="", types_parcours=[], format="html", REQUEST=None
):
"""Page affichant la table des résultats """Page affichant la table des résultats
Les dates sont en dd/mm/yyyy (datepicker javascript) Les dates sont en dd/mm/yyyy (datepicker javascript)
types_parcours est la liste des types de parcours à afficher 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 start_date_iso, end_date_iso, types_parcours
) )
tab.base_url = "%s?start_date=%s&end_date=%s&types_parcours=%s" % ( tab.base_url = "%s?start_date=%s&end_date=%s&types_parcours=%s" % (
REQUEST.URL0, request.base_url,
start_date, start_date,
end_date, end_date,
"&types_parcours=".join([str(x) for x in types_parcours]), "&types_parcours=".join([str(x) for x in types_parcours]),
) )
if format != "html": if format != "html":
return tab.make_page( return tab.make_page(format=format, with_html_headers=False)
format=format, with_html_headers=False, REQUEST=REQUEST
)
tab_html = tab.html() tab_html = tab.html()
nb_rows = tab.get_nb_rows() nb_rows = tab.get_nb_rows()
else: else:

View File

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

View File

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

View File

@ -31,7 +31,7 @@ from app.scodoc.sco_exceptions import ScoValueError
import time import time
from operator import itemgetter from operator import itemgetter
from flask import g from flask import g, request
import app import app
from app.models import Departement from app.models import Departement
@ -61,6 +61,7 @@ _formsemestreEditor = ndb.EditableTable(
"gestion_semestrielle", "gestion_semestrielle",
"etat", "etat",
"bul_hide_xml", "bul_hide_xml",
"block_moyennes",
"bul_bgcolor", "bul_bgcolor",
"modalite", "modalite",
"resp_can_edit", "resp_can_edit",
@ -68,7 +69,6 @@ _formsemestreEditor = ndb.EditableTable(
"ens_can_edit_eval", "ens_can_edit_eval",
"elt_sem_apo", "elt_sem_apo",
"elt_annee_apo", "elt_annee_apo",
"scodoc7_id",
), ),
filter_dept=True, filter_dept=True,
sortkey="date_debut", sortkey="date_debut",
@ -82,6 +82,7 @@ _formsemestreEditor = ndb.EditableTable(
"etat": bool, "etat": bool,
"gestion_compensation": bool, "gestion_compensation": bool,
"bul_hide_xml": bool, "bul_hide_xml": bool,
"block_moyennes": bool,
"gestion_semestrielle": bool, "gestion_semestrielle": bool,
"gestion_compensation": bool, "gestion_compensation": bool,
"gestion_semestrielle": bool, "gestion_semestrielle": bool,
@ -95,9 +96,7 @@ _formsemestreEditor = ndb.EditableTable(
def get_formsemestre(formsemestre_id): def get_formsemestre(formsemestre_id):
"list ONE formsemestre" "list ONE formsemestre"
if not isinstance(formsemestre_id, int): if not isinstance(formsemestre_id, int):
raise ScoValueError( raise ValueError("formsemestre_id must be an integer !")
"""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"""
)
try: try:
sem = do_formsemestre_list(args={"formsemestre_id": formsemestre_id})[0] sem = do_formsemestre_list(args={"formsemestre_id": formsemestre_id})[0]
return sem return sem
@ -565,7 +564,7 @@ def list_formsemestre_by_etape(etape_apo=False, annee_scolaire=False):
return sems 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""" """Affiche table des semestres correspondants à l'étape"""
if etape_apo: if etape_apo:
html_title = ( 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> Etape: <input name="etape_apo" type="text" size="8"></input>
</form>""", </form>""",
) )
tab.base_url = "%s?etape_apo=%s" % (REQUEST.URL0, etape_apo or "") tab.base_url = "%s?etape_apo=%s" % (request.base_url, etape_apo or "")
return tab.make_page(format=format, REQUEST=REQUEST) return tab.make_page(format=format)
def sem_has_etape(sem, code_etape): def sem_has_etape(sem, code_etape):

View File

@ -28,7 +28,7 @@
"""Menu "custom" (défini par l'utilisateur) dans les semestres """Menu "custom" (défini par l'utilisateur) dans les semestres
""" """
import flask 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.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
@ -77,16 +77,14 @@ def formsemestre_custommenu_html(formsemestre_id):
return htmlutils.make_menu("Liens", menu) 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""" """Dialog to edit the custom menu"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
dest_url = ( dest_url = (
scu.NotesURL() + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id scu.NotesURL() + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id
) )
H = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header("Modification du menu du semestre ", sem),
REQUEST, "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">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>""", <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["title_" + str(item["custommenu_id"])] = item["title"]
initvalues["url_" + str(item["custommenu_id"])] = item["url"] initvalues["url_" + str(item["custommenu_id"])] = item["url"]
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
descr, descr,
initvalues=initvalues, initvalues=initvalues,
cancelbutton="Annuler", cancelbutton="Annuler",

View File

@ -28,7 +28,7 @@
"""Form choix modules / responsables et creation formsemestre """Form choix modules / responsables et creation formsemestre
""" """
import flask import flask
from flask import url_for, g from flask import url_for, g, request
from flask_login import current_user from flask_login import current_user
from app.auth.models import User from app.auth.models import User
@ -66,7 +66,7 @@ def _default_sem_title(F):
return F["titre"] return F["titre"]
def formsemestre_createwithmodules(REQUEST=None): def formsemestre_createwithmodules():
"""Page création d'un semestre""" """Page création d'un semestre"""
H = [ H = [
html_sco_header.sco_header( html_sco_header.sco_header(
@ -77,7 +77,7 @@ def formsemestre_createwithmodules(REQUEST=None):
), ),
"""<h2>Mise en place d'un semestre de formation</h2>""", """<h2>Mise en place d'un semestre de formation</h2>""",
] ]
r = do_formsemestre_createwithmodules(REQUEST=REQUEST) r = do_formsemestre_createwithmodules()
if isinstance(r, str): if isinstance(r, str):
H.append(r) H.append(r)
else: else:
@ -85,13 +85,12 @@ def formsemestre_createwithmodules(REQUEST=None):
return "\n".join(H) + html_sco_header.sco_footer() return "\n".join(H) + html_sco_header.sco_footer()
def formsemestre_editwithmodules(REQUEST, formsemestre_id): def formsemestre_editwithmodules(formsemestre_id):
"""Page modification semestre""" """Page modification semestre"""
# portage from dtml # portage from dtml
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header(
REQUEST,
"Modification du semestre", "Modification du semestre",
sem, sem,
javascripts=["libjs/AutoSuggest.js"], javascripts=["libjs/AutoSuggest.js"],
@ -105,12 +104,13 @@ def formsemestre_editwithmodules(REQUEST, formsemestre_id):
% scu.icontag("lock_img", border="0", title="Semestre verrouillé") % scu.icontag("lock_img", border="0", title="Semestre verrouillé")
) )
else: else:
r = do_formsemestre_createwithmodules(REQUEST=REQUEST, edit=1) r = do_formsemestre_createwithmodules(edit=1)
if isinstance(r, str): if isinstance(r, str):
H.append(r) H.append(r)
else: else:
return r # response redirect 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( 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 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> </p>
@ -121,7 +121,7 @@ def formsemestre_editwithmodules(REQUEST, formsemestre_id):
return "\n".join(H) + html_sco_header.sco_footer() 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""" """Return sem if user can edit it, False otherwise"""
sem = sem or sco_formsemestre.get_formsemestre(formsemestre_id) sem = sem or sco_formsemestre.get_formsemestre(formsemestre_id)
if not current_user.has_permission(Permission.ScoImplement): # pas chef 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 return sem
def do_formsemestre_createwithmodules(REQUEST=None, edit=False): def do_formsemestre_createwithmodules(edit=False):
"Form choix modules / responsables et creation formsemestre" "Form choix modules / responsables et creation formsemestre"
# Fonction accessible à tous, controle acces à la main: # Fonction accessible à tous, controle acces à la main:
vals = scu.get_request_args()
if edit: if edit:
formsemestre_id = int(REQUEST.form["formsemestre_id"]) formsemestre_id = int(vals["formsemestre_id"])
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if not current_user.has_permission(Permission.ScoImplement): if not current_user.has_permission(Permission.ScoImplement):
if not edit: if not edit:
@ -156,14 +157,14 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
uid2display[u.id] = u.get_nomplogin() uid2display[u.id] = u.get_nomplogin()
allowed_user_names = list(uid2display.values()) + [""] 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}) F = sco_formations.formation_list(args={"formation_id": formation_id})
if not F: if not F:
raise ScoValueError("Formation inexistante !") raise ScoValueError("Formation inexistante !")
F = F[0] F = F[0]
if not edit: if not edit:
initvalues = {"titre": _default_sem_title(F)} initvalues = {"titre": _default_sem_title(F)}
semestre_id = int(REQUEST.form["semestre_id"]) semestre_id = int(vals["semestre_id"])
sem_module_ids = set() sem_module_ids = set()
else: else:
# setup form init values # setup form init values
@ -309,7 +310,9 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
{ {
"size": 40, "size": 40,
"title": "Nom de ce semestre", "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), % _default_sem_title(F),
}, },
), ),
@ -501,6 +504,14 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
"labels": [""], "labels": [""],
}, },
), ),
(
"block_moyennes",
{
"input_type": "boolcheckbox",
"title": "Bloquer moyennes",
"explanation": "empêcher le calcul des moyennes d'UE et générale.",
},
),
( (
"sep", "sep",
{ {
@ -534,7 +545,7 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
select_name = "%s!group_id" % mod["module_id"] select_name = "%s!group_id" % mod["module_id"]
def opt_selected(gid): def opt_selected(gid):
if gid == REQUEST.form.get(select_name): if gid == vals.get(select_name):
return "selected" return "selected"
else: else:
return "" return ""
@ -623,38 +634,29 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
initvalues["gestion_compensation_lst"] = ["X"] initvalues["gestion_compensation_lst"] = ["X"]
else: else:
initvalues["gestion_compensation_lst"] = [] initvalues["gestion_compensation_lst"] = []
if ( if vals.get("tf_submitted", False) and "gestion_compensation_lst" not in vals:
REQUEST.form.get("tf_submitted", False) vals["gestion_compensation_lst"] = []
and "gestion_compensation_lst" not in REQUEST.form
):
REQUEST.form["gestion_compensation_lst"] = []
initvalues["gestion_semestrielle"] = initvalues.get("gestion_semestrielle", False) initvalues["gestion_semestrielle"] = initvalues.get("gestion_semestrielle", False)
if initvalues["gestion_semestrielle"]: if initvalues["gestion_semestrielle"]:
initvalues["gestion_semestrielle_lst"] = ["X"] initvalues["gestion_semestrielle_lst"] = ["X"]
else: else:
initvalues["gestion_semestrielle_lst"] = [] initvalues["gestion_semestrielle_lst"] = []
if ( if vals.get("tf_submitted", False) and "gestion_semestrielle_lst" not in vals:
REQUEST.form.get("tf_submitted", False) vals["gestion_semestrielle_lst"] = []
and "gestion_semestrielle_lst" not in REQUEST.form
):
REQUEST.form["gestion_semestrielle_lst"] = []
initvalues["bul_hide_xml"] = initvalues.get("bul_hide_xml", False) initvalues["bul_hide_xml"] = initvalues.get("bul_hide_xml", False)
if not initvalues["bul_hide_xml"]: if not initvalues["bul_hide_xml"]:
initvalues["bul_publish_xml_lst"] = ["X"] initvalues["bul_publish_xml_lst"] = ["X"]
else: else:
initvalues["bul_publish_xml_lst"] = [] initvalues["bul_publish_xml_lst"] = []
if ( if vals.get("tf_submitted", False) and "bul_publish_xml_lst" not in vals:
REQUEST.form.get("tf_submitted", False) vals["bul_publish_xml_lst"] = []
and "bul_publish_xml_lst" not in REQUEST.form
):
REQUEST.form["bul_publish_xml_lst"] = []
# #
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, vals,
modform, modform,
submitlabel=submitlabel, submitlabel=submitlabel,
cancelbutton="Annuler", cancelbutton="Annuler",
@ -693,7 +695,6 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
tf[2]["bul_hide_xml"] = False tf[2]["bul_hide_xml"] = False
else: else:
tf[2]["bul_hide_xml"] = True tf[2]["bul_hide_xml"] = True
# remap les identifiants de responsables: # remap les identifiants de responsables:
tf[2]["responsable_id"] = User.get_user_id_from_nomplogin( tf[2]["responsable_id"] = User.get_user_id_from_nomplogin(
tf[2]["responsable_id"] tf[2]["responsable_id"]
@ -786,7 +787,6 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
moduleimpl_id, moduleimpl_id,
formsemestre_id, formsemestre_id,
etudids, etudids,
REQUEST=REQUEST,
) )
msg += [ msg += [
"inscription de %d étudiants au module %s" "inscription de %d étudiants au module %s"
@ -867,7 +867,7 @@ def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
return ok, msg return ok, msg
def formsemestre_clone(formsemestre_id, REQUEST=None): def formsemestre_clone(formsemestre_id):
""" """
Formulaire clonage d'un semestre Formulaire clonage d'un semestre
""" """
@ -888,7 +888,6 @@ def formsemestre_clone(formsemestre_id, REQUEST=None):
H = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header(
REQUEST,
"Copie du semestre", "Copie du semestre",
sem, sem,
javascripts=["libjs/AutoSuggest.js"], javascripts=["libjs/AutoSuggest.js"],
@ -959,8 +958,8 @@ def formsemestre_clone(formsemestre_id, REQUEST=None):
), ),
] ]
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
descr, descr,
submitlabel="Dupliquer ce semestre", submitlabel="Dupliquer ce semestre",
cancelbutton="Annuler", cancelbutton="Annuler",
@ -985,7 +984,6 @@ def formsemestre_clone(formsemestre_id, REQUEST=None):
tf[2]["date_fin"], tf[2]["date_fin"],
clone_evaluations=tf[2]["clone_evaluations"], clone_evaluations=tf[2]["clone_evaluations"],
clone_partitions=tf[2]["clone_partitions"], clone_partitions=tf[2]["clone_partitions"],
REQUEST=REQUEST,
) )
return flask.redirect( return flask.redirect(
"formsemestre_status?formsemestre_id=%s&head_message=Nouveau%%20semestre%%20créé" "formsemestre_status?formsemestre_id=%s&head_message=Nouveau%%20semestre%%20créé"
@ -1000,7 +998,6 @@ def do_formsemestre_clone(
date_fin, # 'dd/mm/yyyy' date_fin, # 'dd/mm/yyyy'
clone_evaluations=False, clone_evaluations=False,
clone_partitions=False, clone_partitions=False,
REQUEST=None,
): ):
"""Clone a semestre: make copy, same modules, same options, same resps, same partitions. """Clone a semestre: make copy, same modules, same options, same resps, same partitions.
New dates, responsable_id New dates, responsable_id
@ -1040,7 +1037,7 @@ def do_formsemestre_clone(
args = e.copy() args = e.copy()
del args["jour"] # erase date del args["jour"] # erase date
args["moduleimpl_id"] = mid args["moduleimpl_id"] = mid
_ = sco_evaluations.do_evaluation_create(REQUEST=REQUEST, **args) _ = sco_evaluations.do_evaluation_create(**args)
# 3- copy uecoefs # 3- copy uecoefs
objs = sco_formsemestre.formsemestre_uecoef_list( objs = sco_formsemestre.formsemestre_uecoef_list(
@ -1113,10 +1110,11 @@ def do_formsemestre_clone(
def formsemestre_associate_new_version( def formsemestre_associate_new_version(
formsemestre_id, formsemestre_id,
other_formsemestre_ids=[], other_formsemestre_ids=[],
REQUEST=None,
dialog_confirmed=False, dialog_confirmed=False,
): ):
"""Formulaire changement formation d'un semestre""" """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: if not dialog_confirmed:
# dresse le liste des semestres de la meme formation et version # dresse le liste des semestres de la meme formation et version
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
@ -1161,15 +1159,19 @@ def formsemestre_associate_new_version(
) )
else: else:
do_formsemestres_associate_new_version( do_formsemestres_associate_new_version(
[formsemestre_id] + other_formsemestre_ids, REQUEST=REQUEST [formsemestre_id] + other_formsemestre_ids
) )
return flask.redirect( return flask.redirect(
"formsemestre_status?formsemestre_id=%s&head_message=Formation%%20dupliquée" url_for(
% formsemestre_id "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. """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 Tous les moduleimpl sont -associés à la nouvelle formation, ainsi que les decisions de jury
si elles existent (codes d'UE validées). 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: if not formsemestre_ids:
return return
# Check: tous de la même formation # Check: tous de la même formation
assert isinstance(formsemestre_ids[0], int)
sem = sco_formsemestre.get_formsemestre(formsemestre_ids[0]) sem = sco_formsemestre.get_formsemestre(formsemestre_ids[0])
formation_id = sem["formation_id"] formation_id = sem["formation_id"]
for formsemestre_id in formsemestre_ids[1:]: for formsemestre_id in formsemestre_ids[1:]:
assert isinstance(formsemestre_id, int)
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if formation_id != sem["formation_id"]: if formation_id != sem["formation_id"]:
raise ScoValueError("les semestres ne sont pas tous de la même formation !") 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, formation_id,
modules_old2new, modules_old2new,
ues_old2new, ues_old2new,
) = sco_formations.formation_create_new_version( ) = sco_formations.formation_create_new_version(formation_id, redirect=False)
formation_id, redirect=False, REQUEST=REQUEST
)
for formsemestre_id in formsemestre_ids: for formsemestre_id in formsemestre_ids:
sem = sco_formsemestre.get_formsemestre(formsemestre_id) 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) 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)""" """Delete a formsemestre (affiche avertissements)"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0] F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
H = [ 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> """<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, <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> <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: else:
submit_label = "Confirmer la suppression du semestre" submit_label = "Confirmer la suppression du semestre"
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
(("formsemestre_id", {"input_type": "hidden"}),), (("formsemestre_id", {"input_type": "hidden"}),),
initvalues=F, initvalues=F,
submitlabel=submit_label, 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 """dialog to change formsemestre options
(accessible par ScoImplement ou dir. etudes) (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) ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
if not ok: if not ok:
return err return err
return sco_preferences.SemPreferences(formsemestre_id).edit( return sco_preferences.SemPreferences(formsemestre_id).edit(categories=["bul"])
REQUEST=REQUEST, 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é) """Change etat (verrouille si ouvert, déverrouille si fermé)
nota: etat (1 ouvert, 0 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) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
etat = not sem["etat"] 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} args = {"formsemestre_id": formsemestre_id, "etat": etat}
sco_formsemestre.do_formsemestre_edit(args) sco_formsemestre.do_formsemestre_edit(args)
if REQUEST:
return flask.redirect(
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
)
def formsemestre_change_publication_bul( 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""" """Change etat publication bulletins sur portail"""
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id) 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) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
etat = not sem["bul_hide_xml"] etat = not sem["bul_hide_xml"]
if REQUEST and not dialog_confirmed: if not dialog_confirmed:
if etat: if etat:
msg = "non" msg = "non"
else: else:
@ -1499,14 +1477,14 @@ def formsemestre_change_publication_bul(
args = {"formsemestre_id": formsemestre_id, "bul_hide_xml": etat} args = {"formsemestre_id": formsemestre_id, "bul_hide_xml": etat}
sco_formsemestre.do_formsemestre_edit(args) sco_formsemestre.do_formsemestre_edit(args)
if REQUEST: if redirect:
return flask.redirect( return flask.redirect(
"formsemestre_status?formsemestre_id=%s" % formsemestre_id "formsemestre_status?formsemestre_id=%s" % formsemestre_id
) )
return None 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.""" """Changement manuel des coefficients des UE capitalisées."""
from app.scodoc import notes_table from app.scodoc import notes_table
@ -1538,9 +1516,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None, REQUEST=None):
</p> </p>
""" """
H = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header("Coefficients des UE du semestre", sem),
REQUEST, "Coefficients des UE du semestre", sem
),
help, 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)) form.append(("ue_" + str(ue["ue_id"]), descr))
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
form, form,
submitlabel="Changer les coefficients", submitlabel="Changer les coefficients",
cancelbutton="Annuler", cancelbutton="Annuler",
@ -1652,9 +1628,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None, REQUEST=None):
formsemestre_id=formsemestre_id formsemestre_id=formsemestre_id
) # > modif coef UE cap (modifs notes de _certains_ etudiants) ) # > modif coef UE cap (modifs notes de _certains_ etudiants)
header = html_sco_header.html_sem_header( header = html_sco_header.html_sem_header("Coefficients des UE du semestre", sem)
REQUEST, "Coefficients des UE du semestre", sem
)
return ( return (
header header
+ "\n".join(z) + "\n".join(z)

View File

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

View File

@ -30,7 +30,7 @@
import time import time
import flask 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.sco_utils as scu
from app import log from app import log
@ -55,6 +55,7 @@ _formsemestre_inscriptionEditor = ndb.EditableTable(
"formsemestre_inscription_id", "formsemestre_inscription_id",
("formsemestre_inscription_id", "etudid", "formsemestre_id", "etat", "etape"), ("formsemestre_inscription_id", "etudid", "formsemestre_id", "etat", "etape"),
sortkey="formsemestre_id", sortkey="formsemestre_id",
insert_ignore_conflicts=True,
) )
@ -248,7 +249,7 @@ def do_formsemestre_inscription_with_modules(
def formsemestre_inscription_with_modules_etud( 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. """Form. inscription d'un étudiant au semestre.
Si etudid n'est pas specifié, form. choix etudiant. Si etudid n'est pas specifié, form. choix etudiant.
@ -263,7 +264,7 @@ def formsemestre_inscription_with_modules_etud(
) )
return formsemestre_inscription_with_modules( 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( 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. 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] etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
H = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header(
REQUEST,
"Inscription de %s dans ce semestre" % etud["nomprenom"], "Inscription de %s dans ce semestre" % etud["nomprenom"],
sem, sem,
) )
@ -415,7 +415,7 @@ def formsemestre_inscription_with_modules(
<input type="hidden" name="etudid" value="%s"> <input type="hidden" name="etudid" value="%s">
<input type="hidden" name="formsemestre_id" 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)) 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 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.""" """Dialogue pour (dés)inscription à des modules optionnels."""
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if not sem["etat"]: if not sem["etat"]:
@ -468,7 +468,8 @@ def formsemestre_inscription_option(etudid, formsemestre_id, REQUEST=None):
modimpls_by_ue_names[ue_id].append( modimpls_by_ue_names[ue_id].append(
"%s %s" % (mod["module"]["code"], mod["module"]["titre"]) "%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 ? # inscrit ?
for ins in inscr: for ins in inscr:
if ins["moduleimpl_id"] == mod["moduleimpl_id"]: if ins["moduleimpl_id"] == mod["moduleimpl_id"]:
@ -533,8 +534,8 @@ function chkbx_select(field_id, state) {
""" """
) )
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
descr, descr,
initvalues, initvalues,
cancelbutton="Annuler", cancelbutton="Annuler",
@ -659,7 +660,7 @@ function chkbx_select(field_id, state) {
def do_moduleimpl_incription_options( 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 Effectue l'inscription et la description aux modules optionnels
@ -711,7 +712,6 @@ def do_moduleimpl_incription_options(
oid, formsemestre_id=mod["formsemestre_id"] oid, formsemestre_id=mod["formsemestre_id"]
) )
if REQUEST:
H = [ H = [
html_sco_header.sco_header(), html_sco_header.sco_header(),
"""<h3>Modifications effectuées</h3> """<h3>Modifications effectuées</h3>
@ -756,14 +756,13 @@ def list_inscrits_ailleurs(formsemestre_id):
return d 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 """Page listant les étudiants inscrits dans un autre semestre
dont les dates recouvrent le semestre indiqué. dont les dates recouvrent le semestre indiqué.
""" """
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header(
REQUEST,
"Inscriptions multiples parmi les étudiants du semestre ", "Inscriptions multiples parmi les étudiants du semestre ",
sem, sem,
) )

View File

@ -105,7 +105,7 @@ def _build_menu_stats(formsemestre_id):
"title": "Documents Avis Poursuite Etudes", "title": "Documents Avis Poursuite Etudes",
"endpoint": "notes.pe_view_sem_recap", "endpoint": "notes.pe_view_sem_recap",
"args": {"formsemestre_id": formsemestre_id}, "args": {"formsemestre_id": formsemestre_id},
"enabled": True, "enabled": current_app.config["TESTING"] or current_app.config["DEBUG"],
}, },
{ {
"title": 'Table "débouchés"', "title": 'Table "débouchés"',
@ -436,7 +436,7 @@ def formsemestre_status_menubar(sem):
return "\n".join(H) 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 """Cherche si on a de quoi déduire le semestre affiché à partir des
arguments de la requête: arguments de la requête:
formsemestre_id ou moduleimpl ou evaluation ou group_id ou partition_id formsemestre_id ou moduleimpl ou evaluation ou group_id ou partition_id
@ -447,6 +447,7 @@ def retreive_formsemestre_from_request():
args = request.form args = request.form
else: else:
return None return None
formsemestre_id = None
# Search formsemestre # Search formsemestre
group_ids = args.get("group_ids", []) group_ids = args.get("group_ids", [])
if "formsemestre_id" in args: if "formsemestre_id" in args:
@ -479,16 +480,17 @@ def retreive_formsemestre_from_request():
elif "partition_id" in args: elif "partition_id" in args:
partition = sco_groups.get_partition(args["partition_id"]) partition = sco_groups.get_partition(args["partition_id"])
formsemestre_id = partition["formsemestre_id"] formsemestre_id = partition["formsemestre_id"]
else:
if not formsemestre_id:
return None # no current formsemestre return None # no current formsemestre
return formsemestre_id return int(formsemestre_id)
# Element HTML decrivant un semestre (barre de menu et infos) # Element HTML decrivant un semestre (barre de menu et infos)
def formsemestre_page_title(): def formsemestre_page_title():
"""Element HTML decrivant un semestre (barre de menu et infos) """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() formsemestre_id = retreive_formsemestre_from_request()
# #
@ -568,7 +570,7 @@ def fill_formsemestre(sem):
# Description du semestre sous forme de table exportable # 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 """Description du semestre sous forme de table exportable
Liste des modules et de leurs coefficients 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"] moduleimpl_id=M["moduleimpl_id"]
) )
enseignants = ", ".join( 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 = { l = {
"UE": M["ue"]["acronyme"], "UE": M["ue"]["acronyme"],
@ -698,41 +700,37 @@ def formsemestre_description_table(formsemestre_id, REQUEST=None, with_evals=Fal
html_caption=title, html_caption=title,
html_class="table_leftalign formsemestre_description", html_class="table_leftalign formsemestre_description",
base_url="%s?formsemestre_id=%s&with_evals=%s" 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, page_title=title,
html_title=html_sco_header.html_sem_header( 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, pdf_title=title,
preferences=sco_preferences.SemPreferences(formsemestre_id), preferences=sco_preferences.SemPreferences(formsemestre_id),
) )
def formsemestre_description( def formsemestre_description(formsemestre_id, format="html", with_evals=False):
formsemestre_id, format="html", with_evals=False, REQUEST=None
):
"""Description du semestre sous forme de table exportable """Description du semestre sous forme de table exportable
Liste des modules et de leurs coefficients Liste des modules et de leurs coefficients
""" """
with_evals = int(with_evals) with_evals = int(with_evals)
tab = formsemestre_description_table( tab = formsemestre_description_table(formsemestre_id, with_evals=with_evals)
formsemestre_id, REQUEST, with_evals=with_evals
)
tab.html_before_table = """<form name="f" method="get" action="%s"> tab.html_before_table = """<form name="f" method="get" action="%s">
<input type="hidden" name="formsemestre_id" value="%s"></input> <input type="hidden" name="formsemestre_id" value="%s"></input>
<input type="checkbox" name="with_evals" value="1" onchange="document.f.submit()" """ % ( <input type="checkbox" name="with_evals" value="1" onchange="document.f.submit()" """ % (
REQUEST.URL0, request.base_url,
formsemestre_id, formsemestre_id,
) )
if with_evals: if with_evals:
tab.html_before_table += "checked" tab.html_before_table += "checked"
tab.html_before_table += ">indiquer les évaluations</input></form>" 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 # 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" # construit l'URL "destination"
# (a laquelle on revient apres saisie absences) # (a laquelle on revient apres saisie absences)
destination = url_for( destination = url_for(
@ -897,7 +895,7 @@ def html_expr_diagnostic(diagnostics):
return "".join(H) 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" """ """En-tête HTML des pages "semestre" """
semlist = sco_formsemestre.do_formsemestre_list( semlist = sco_formsemestre.do_formsemestre_list(
args={"formsemestre_id": formsemestre_id} args={"formsemestre_id": formsemestre_id}
@ -912,12 +910,12 @@ def formsemestre_status_head(formsemestre_id=None, REQUEST=None, page_title=None
H = [ H = [
html_sco_header.html_sem_header( 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> <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>""" <a href="{url_for('notes.ue_list', scodoc_dept=g.scodoc_dept, formation_id=F['formation_id'])}"
% F, class="discretelink" title="Formation {F['acronyme']}, v{F['version']}">{F['titre']}</a>""",
] ]
if sem["semestre_id"] >= 0: if sem["semestre_id"] >= 0:
H.append(", %s %s" % (parcours.SESSION_NAME, sem["semestre_id"])) 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>""" </td></tr>"""
) )
H.append("</table>") H.append("</table>")
sem_warning = ""
if sem["bul_hide_xml"]: if sem["bul_hide_xml"]:
H.append( sem_warning += "Bulletins non publiés sur le portail. "
'<p class="fontorange"><em>Bulletins non publiés sur le portail</em></p>' 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): if sem["semestre_id"] >= 0 and not sco_formsemestre.sem_une_annee(sem):
H.append( H.append(
'<p class="fontorange"><em>Attention: ce semestre couvre plusieurs années scolaires !</em></p>' '<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) return "".join(H)
def formsemestre_status(formsemestre_id=None, REQUEST=None): def formsemestre_status(formsemestre_id=None):
"""Tableau de bord semestre HTML""" """Tableau de bord semestre HTML"""
# porté du DTML # porté du DTML
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
@ -975,7 +976,7 @@ def formsemestre_status(formsemestre_id=None, REQUEST=None):
# ) # )
prev_ue_id = 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 = [ H = [
html_sco_header.sco_header(page_title="Semestre %s" % sem["titreannee"]), 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( ModInscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
moduleimpl_id=M["moduleimpl_id"] moduleimpl_id=M["moduleimpl_id"]
) )
mails_enseignants.add( mails_enseignants.add(sco_users.user_info(M["responsable_id"])["email"])
sco_users.user_info(M["responsable_id"], REQUEST)["email"]
)
mails_enseignants |= set( 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"] ue = M["ue"]
if prev_ue_id != ue["ue_id"]: if prev_ue_id != ue["ue_id"]:
@ -1147,7 +1146,7 @@ def formsemestre_status(formsemestre_id=None, REQUEST=None):
# --- LISTE DES ETUDIANTS # --- LISTE DES ETUDIANTS
H += [ H += [
'<div id="groupes">', '<div id="groupes">',
_make_listes_sem(sem, REQUEST), _make_listes_sem(sem),
"</div>", "</div>",
] ]
# --- Lien mail enseignants: # --- 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 six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error, time, datetime
import flask import flask
from flask import url_for, g from flask import url_for, g, request
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -64,7 +64,6 @@ def formsemestre_validation_etud_form(
desturl=None, desturl=None,
sortcol=None, sortcol=None,
readonly=True, readonly=True,
REQUEST=None,
): ):
nt = sco_cache.NotesTableCache.get( nt = sco_cache.NotesTableCache.get(
formsemestre_id 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>' '</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), url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
sco_photos.etud_photo_html( sco_photos.etud_photo_html(etud, title="fiche de %s" % etud["nom"]),
etud, title="fiche de %s" % etud["nom"], REQUEST=REQUEST
),
) )
) )
@ -163,10 +160,11 @@ def formsemestre_validation_etud_form(
if etud_etat != "I": if etud_etat != "I":
H.append( H.append(
tf_error_message( tf_error_message(
"""Impossible de statuer sur cet étudiant: f"""Impossible de statuer sur cet étudiant:
il est démissionnaire ou défaillant (voir <a href="%s">sa fiche</a>) 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) return "\n".join(H + Footer)
@ -178,16 +176,19 @@ def formsemestre_validation_etud_form(
) )
if check: if check:
if not desturl: if not desturl:
desturl = ( desturl = url_for(
"formsemestre_recapcomplet?modejury=1&hidemodules=1&hidebac=1&pref_override=0&formsemestre_id=" "notes.formsemestre_recapcomplet",
+ str(formsemestre_id) 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: H.append(f'<ul><li><a href="{desturl}">Continuer</a></li></ul>')
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)
return "\n".join(H + Footer) return "\n".join(H + Footer)
@ -197,8 +198,12 @@ def formsemestre_validation_etud_form(
if nt.etud_has_notes_attente(etudid): if nt.etud_has_notes_attente(etudid):
H.append( H.append(
tf_error_message( 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>)""" f"""Impossible de statuer sur cet étudiant: il a des notes en
% formsemestre_id 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) return "\n".join(H + Footer)
@ -213,14 +218,24 @@ def formsemestre_validation_etud_form(
if not Se.prev_decision: if not Se.prev_decision:
H.append( H.append(
tf_error_message( 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>)""" f"""Le jury n'a pas statué sur le semestre précédent ! (<a href="{
% (Se.prev["formsemestre_id"], etudid) 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: if decision_jury:
H.append( H.append(
'<a href="formsemestre_validation_suppress_etud?etudid=%s&formsemestre_id=%s" class="stdlink">Supprimer décision existante</a>' f"""<a href="{
% (etudid, formsemestre_id) 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()) H.append(html_sco_header.sco_footer())
return "\n".join(H) return "\n".join(H)
@ -338,7 +353,6 @@ def formsemestre_validation_etud(
codechoice=None, # required codechoice=None, # required
desturl="", desturl="",
sortcol=None, sortcol=None,
REQUEST=None,
): ):
"""Enregistre validation""" """Enregistre validation"""
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
@ -354,9 +368,9 @@ def formsemestre_validation_etud(
if not selected_choice: if not selected_choice:
raise ValueError("code choix invalide ! (%s)" % codechoice) raise ValueError("code choix invalide ! (%s)" % codechoice)
# #
Se.valide_decision(selected_choice, REQUEST) # enregistre Se.valide_decision(selected_choice) # enregistre
return _redirect_valid_choice( 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, assidu=False,
desturl="", desturl="",
sortcol=None, sortcol=None,
REQUEST=None,
redirect=True, redirect=True,
): ):
"""Enregistre validation""" """Enregistre validation"""
@ -399,22 +412,20 @@ def formsemestre_validation_etud_manu(
formsemestre_id_utilise_pour_compenser=formsemestre_id_utilise_pour_compenser, formsemestre_id_utilise_pour_compenser=formsemestre_id_utilise_pour_compenser,
) )
# #
Se.valide_decision(choice, REQUEST) # enregistre Se.valide_decision(choice) # enregistre
if redirect: if redirect:
return _redirect_valid_choice( return _redirect_valid_choice(
formsemestre_id, etudid, Se, choice, desturl, sortcol, REQUEST formsemestre_id, etudid, Se, choice, desturl, sortcol
) )
def _redirect_valid_choice( def _redirect_valid_choice(formsemestre_id, etudid, Se, choice, desturl, sortcol):
formsemestre_id, etudid, Se, choice, desturl, sortcol, REQUEST
):
adr = "formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&check=1" % ( adr = "formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&check=1" % (
formsemestre_id, formsemestre_id,
etudid, etudid,
) )
if sortcol: if sortcol:
adr += "&sortcol=" + sortcol adr += "&sortcol=" + str(sortcol)
# if desturl: # if desturl:
# desturl += "&desturl=" + desturl # desturl += "&desturl=" + desturl
return flask.redirect(adr) 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" "Formulaire saisie automatisee des decisions d'un semestre"
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header(
REQUEST, "Saisie automatique des décisions du semestre", sem "Saisie automatique des décisions du semestre", sem
), ),
""" """
<ul> <ul>
@ -851,7 +862,7 @@ def formsemestre_validation_auto(formsemestre_id, REQUEST):
return "\n".join(H) 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" "Saisie automatisee des decisions d'un semestre"
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
next_semestre_id = sem["semestre_id"] + 1 next_semestre_id = sem["semestre_id"] + 1
@ -907,7 +918,6 @@ def do_formsemestre_validation_auto(formsemestre_id, REQUEST):
code_etat=ADM, code_etat=ADM,
devenir="NEXT", devenir="NEXT",
assidu=True, assidu=True,
REQUEST=REQUEST,
redirect=False, redirect=False,
) )
nb_valid += 1 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) ) # > 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 """Form. saisie UE validée hors ScoDoc
(pour étudiants arrivant avec un UE antérieurement validée). (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>' '</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), url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
sco_photos.etud_photo_html( sco_photos.etud_photo_html(etud, title="fiche de %s" % etud["nom"]),
etud, title="fiche de %s" % etud["nom"], REQUEST=REQUEST
),
) )
), ),
"""<p class="help">Utiliser cette page pour enregistrer une UE validée antérieurement, """<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_names = ["Choisir..."] + ["%(acronyme)s %(titre)s" % ue for ue in ues]
ue_ids = [""] + [ue["ue_id"] for ue in ues] ue_ids = [""] + [ue["ue_id"] for ue in ues]
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
( (
("etudid", {"input_type": "hidden"}), ("etudid", {"input_type": "hidden"}),
("formsemestre_id", {"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]["moy_ue"],
tf[2]["date"], tf[2]["date"],
semestre_id=semestre_id, semestre_id=semestre_id,
REQUEST=REQUEST,
) )
return flask.redirect( return flask.redirect(
scu.ScoURL() scu.ScoURL()
@ -1109,7 +1116,6 @@ def do_formsemestre_validate_previous_ue(
code=ADM, code=ADM,
semestre_id=None, semestre_id=None,
ue_coefficient=None, ue_coefficient=None,
REQUEST=None,
): ):
"""Enregistre (ou modifie) validation d'UE (obtenue hors ScoDoc). """Enregistre (ou modifie) validation d'UE (obtenue hors ScoDoc).
Si le coefficient est spécifié, modifie le coefficient de 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) ) # > 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""" """Ramene bout de HTML pour pouvoir supprimer une validation de cette UE"""
valids = ndb.SimpleDictFetch( valids = ndb.SimpleDictFetch(
"""SELECT SFV.* """SELECT SFV.*
@ -1201,7 +1207,7 @@ def get_etud_ue_cap_html(etudid, formsemestre_id, ue_id, REQUEST=None):
return "\n".join(H) 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""" """Suppress a validation (ue_id, etudid) and redirect to formsemestre"""
log("etud_ue_suppress_validation( %s, %s, %s)" % (etudid, formsemestre_id, ue_id)) log("etud_ue_suppress_validation( %s, %s, %s)" % (etudid, formsemestre_id, ue_id))
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()

View File

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

View File

@ -33,7 +33,7 @@ from app.scodoc import sco_groups
from app.scodoc.sco_exceptions import AccessDenied 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. """Formulaire affectation des etudiants aux groupes de la partition.
Permet aussi la creation et la suppression de groupes. Permet aussi la creation et la suppression de groupes.
""" """

View File

@ -40,6 +40,7 @@ import time
from flask import url_for, g, request from flask import url_for, g, request
from flask_login import current_user
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
@ -55,7 +56,6 @@ from app.scodoc import sco_etud
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from six.moves import range
JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + [ JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + [
"js/etud_info.js", "js/etud_info.js",
@ -64,11 +64,10 @@ JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + [
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
# view:
def groups_view( def groups_view(
group_ids=[], group_ids=(),
format="html", format="html",
REQUEST=None,
# Options pour listes: # Options pour listes:
with_codes=0, with_codes=0,
etat=None, etat=None,
@ -93,7 +92,6 @@ def groups_view(
return groups_table( return groups_table(
groups_infos=groups_infos, groups_infos=groups_infos,
format=format, format=format,
REQUEST=REQUEST,
with_codes=with_codes, with_codes=with_codes,
etat=etat, etat=etat,
with_paiement=with_paiement, with_paiement=with_paiement,
@ -133,7 +131,6 @@ def groups_view(
groups_table( groups_table(
groups_infos=groups_infos, groups_infos=groups_infos,
format=format, format=format,
REQUEST=REQUEST,
with_codes=with_codes, with_codes=with_codes,
etat=etat, etat=etat,
with_paiement=with_paiement, with_paiement=with_paiement,
@ -142,11 +139,11 @@ def groups_view(
), ),
"</div>", "</div>",
"""<div class="tab-pane" id="tab-photos">""", """<div class="tab-pane" id="tab-photos">""",
tab_photos_html(groups_infos, etat=etat, REQUEST=REQUEST), tab_photos_html(groups_infos, etat=etat),
#'<p>hello</p>', #'<p>hello</p>',
"</div>", "</div>",
'<div class="tab-pane" id="tab-abs">', '<div class="tab-pane" id="tab-abs">',
tab_absences_html(groups_infos, etat=etat, REQUEST=REQUEST), tab_absences_html(groups_infos, etat=etat),
"</div>", "</div>",
) )
) )
@ -295,7 +292,7 @@ class DisplayedGroupsInfos(object):
def __init__( def __init__(
self, self,
group_ids=[], # groupes specifies dans l'URL, ou un seul int group_ids=(), # groupes specifies dans l'URL, ou un seul int
formsemestre_id=None, formsemestre_id=None,
etat=None, etat=None,
select_all_when_unspecified=False, select_all_when_unspecified=False,
@ -305,7 +302,7 @@ class DisplayedGroupsInfos(object):
if group_ids: if group_ids:
group_ids = [group_ids] # cas ou un seul parametre, pas de liste group_ids = [group_ids] # cas ou un seul parametre, pas de liste
else: else:
group_ids = [] group_ids = [int(g) for g in group_ids]
if not formsemestre_id and moduleimpl_id: if not formsemestre_id and moduleimpl_id:
mods = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id) mods = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)
if len(mods) != 1: if len(mods) != 1:
@ -420,7 +417,6 @@ class DisplayedGroupsInfos(object):
# Ancien ZScolar.group_list renommé ici en group_table # Ancien ZScolar.group_list renommé ici en group_table
def groups_table( def groups_table(
REQUEST=None,
groups_infos=None, # instance of DisplayedGroupsInfos groups_infos=None, # instance of DisplayedGroupsInfos
with_codes=0, with_codes=0,
etat=None, etat=None,
@ -477,6 +473,9 @@ def groups_table(
[(p["partition_id"], p["partition_name"]) for p in groups_infos.partitions] [(p["partition_id"], p["partition_name"]) for p in groups_infos.partitions]
) )
) )
partitions_name = {
p["partition_id"]: p["partition_name"] for p in groups_infos.partitions
}
if format != "html": # ne mentionne l'état que en Excel (style en html) if format != "html": # ne mentionne l'état que en Excel (style en html)
columns_ids.append("etat") columns_ids.append("etat")
@ -500,11 +499,7 @@ def groups_table(
if with_annotations: if with_annotations:
sco_etud.add_annotations_to_etud_list(groups_infos.members) sco_etud.add_annotations_to_etud_list(groups_infos.members)
columns_ids += ["annotations_str"] columns_ids += ["annotations_str"]
moodle_sem_name = groups_infos.formsemestre["session_id"]
if groups_infos.formsemestre["semestre_id"] >= 0:
moodle_sem_name = "S%d" % groups_infos.formsemestre["semestre_id"]
else:
moodle_sem_name = "A" # pas de semestre spécifié, que faire ?
moodle_groupenames = set() moodle_groupenames = set()
# ajoute liens # ajoute liens
for etud in groups_infos.members: for etud in groups_infos.members:
@ -529,23 +524,28 @@ def groups_table(
# et groupes: # et groupes:
for partition_id in etud["partitions"]: for partition_id in etud["partitions"]:
etud[partition_id] = etud["partitions"][partition_id]["group_name"] etud[partition_id] = etud["partitions"][partition_id]["group_name"]
# Ajoute colonne pour moodle: semestre_groupe, de la forme S1-NomgroupeXXX # Ajoute colonne pour moodle: semestre_groupe, de la forme
# RT-DUT-FI-S3-2021-PARTITION-GROUPE
moodle_groupename = [] moodle_groupename = []
if groups_infos.selected_partitions: if groups_infos.selected_partitions:
# il y a des groupes selectionnes, utilise leurs partitions # il y a des groupes selectionnes, utilise leurs partitions
for partition_id in groups_infos.selected_partitions: for partition_id in groups_infos.selected_partitions:
if partition_id in etud["partitions"]: if partition_id in etud["partitions"]:
moodle_groupename.append( moodle_groupename.append(
etud["partitions"][partition_id]["group_name"] partitions_name[partition_id]
+ "-"
+ etud["partitions"][partition_id]["group_name"]
) )
else: else:
# pas de groupes sélectionnés: prend le premier s'il y en a un # pas de groupes sélectionnés: prend le premier s'il y en a un
moodle_groupename = ["tous"]
if etud["partitions"]: if etud["partitions"]:
for p in etud["partitions"].items(): # partitions is an OrderedDict for p in etud["partitions"].items(): # partitions is an OrderedDict
moodle_groupename = [
partitions_name[p[0]] + "-" + p[1]["group_name"]
]
break break
moodle_groupename = [p[1]["group_name"]]
else:
moodle_groupename = ["tous"]
moodle_groupenames |= set(moodle_groupename) moodle_groupenames |= set(moodle_groupename)
etud["semestre_groupe"] = moodle_sem_name + "-" + "+".join(moodle_groupename) etud["semestre_groupe"] = moodle_sem_name + "-" + "+".join(moodle_groupename)
@ -634,8 +634,7 @@ def groups_table(
[ [
"""<span style="margin-left: 2em;"><select name="group_list_options" id="group_list_options" class="multiselect" multiple="multiple">""", """<span style="margin-left: 2em;"><select name="group_list_options" id="group_list_options" class="multiselect" multiple="multiple">""",
"\n".join(Of), "\n".join(Of),
""" """</select></span>
</select></span>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
$('#group_list_options').multiselect( $('#group_list_options').multiselect(
@ -706,7 +705,7 @@ def groups_table(
): ):
if format == "moodlecsv": if format == "moodlecsv":
format = "csv" format = "csv"
return tab.make_page(format=format, REQUEST=REQUEST) return tab.make_page(format=format)
elif format == "xlsappel": elif format == "xlsappel":
xls = sco_excel.excel_feuille_listeappel( xls = sco_excel.excel_feuille_listeappel(
@ -716,10 +715,10 @@ def groups_table(
partitions=groups_infos.partitions, partitions=groups_infos.partitions,
with_codes=with_codes, with_codes=with_codes,
with_paiement=with_paiement, with_paiement=with_paiement,
server_name=REQUEST.BASE0, server_name=request.url_root,
) )
filename = "liste_%s" % groups_infos.groups_filename + ".xlsx" filename = "liste_%s" % groups_infos.groups_filename
return sco_excel.send_excel_file(REQUEST, xls, filename) return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
elif format == "allxls": elif format == "allxls":
# feuille Excel avec toutes les infos etudiants # feuille Excel avec toutes les infos etudiants
if not groups_infos.members: if not groups_infos.members:
@ -787,15 +786,15 @@ def groups_table(
L = [dicttakestr(m, keys) for m in groups_infos.members] L = [dicttakestr(m, keys) for m in groups_infos.members]
title = "etudiants_%s" % groups_infos.groups_filename title = "etudiants_%s" % groups_infos.groups_filename
xls = sco_excel.excel_simple_table(titles=titles, lines=L, sheet_name=title) xls = sco_excel.excel_simple_table(titles=titles, lines=L, sheet_name=title)
filename = title + scu.XLSX_SUFFIX filename = title
return sco_excel.send_excel_file(REQUEST, xls, filename) return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
else: else:
raise ValueError("unsupported format") raise ValueError("unsupported format")
def tab_absences_html(groups_infos, etat=None, REQUEST=None): def tab_absences_html(groups_infos, etat=None):
"""contenu du tab "absences et feuilles diverses" """ """contenu du tab "absences et feuilles diverses" """
authuser = REQUEST.AUTHENTICATED_USER authuser = current_user
H = ['<div class="tab-content">'] H = ['<div class="tab-content">']
if not groups_infos.members: if not groups_infos.members:
return "".join(H) + "<h3>Aucun étudiant !</h3></div>" return "".join(H) + "<h3>Aucun étudiant !</h3></div>"
@ -804,10 +803,10 @@ def tab_absences_html(groups_infos, etat=None, REQUEST=None):
"<h3>Absences</h3>", "<h3>Absences</h3>",
'<ul class="ul_abs">', '<ul class="ul_abs">',
"<li>", "<li>",
form_choix_saisie_semaine(groups_infos, REQUEST=REQUEST), # Ajout Le Havre form_choix_saisie_semaine(groups_infos), # Ajout Le Havre
"</li>", "</li>",
"<li>", "<li>",
form_choix_jour_saisie_hebdo(groups_infos, REQUEST=REQUEST), form_choix_jour_saisie_hebdo(groups_infos),
"</li>", "</li>",
"""<li><a class="stdlink" href="Absences/EtatAbsencesGr?%s&debut=%s&fin=%s">État des absences du groupe</a></li>""" """<li><a class="stdlink" href="Absences/EtatAbsencesGr?%s&debut=%s&fin=%s">État des absences du groupe</a></li>"""
% ( % (
@ -852,19 +851,19 @@ def tab_absences_html(groups_infos, etat=None, REQUEST=None):
return "".join(H) return "".join(H)
def tab_photos_html(groups_infos, etat=None, REQUEST=None): def tab_photos_html(groups_infos, etat=None):
"""contenu du tab "photos" """ """contenu du tab "photos" """
from app.scodoc import sco_trombino from app.scodoc import sco_trombino
if not groups_infos.members: if not groups_infos.members:
return '<div class="tab-content"><h3>Aucun étudiant !</h3></div>' return '<div class="tab-content"><h3>Aucun étudiant !</h3></div>'
return sco_trombino.trombino_html(groups_infos, REQUEST=REQUEST) return sco_trombino.trombino_html(groups_infos)
def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None, REQUEST=None): def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None):
"""Formulaire choix jour semaine pour saisie.""" """Formulaire choix jour semaine pour saisie."""
authuser = REQUEST.AUTHENTICATED_USER authuser = current_user
if not authuser.has_permission(Permission.ScoAbsChange): if not authuser.has_permission(Permission.ScoAbsChange):
return "" return ""
sem = groups_infos.formsemestre sem = groups_infos.formsemestre
@ -904,18 +903,18 @@ def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None, REQUEST=None)
# Ajout Le Havre # Ajout Le Havre
# Formulaire saisie absences semaine # Formulaire saisie absences semaine
def form_choix_saisie_semaine(groups_infos, REQUEST=None): def form_choix_saisie_semaine(groups_infos):
authuser = REQUEST.AUTHENTICATED_USER authuser = current_user
if not authuser.has_permission(Permission.ScoAbsChange): if not authuser.has_permission(Permission.ScoAbsChange):
return "" return ""
# construit l'URL "destination" # construit l'URL "destination"
# (a laquelle on revient apres saisie absences) # (a laquelle on revient apres saisie absences)
query_args = parse_qs(REQUEST.QUERY_STRING) query_args = parse_qs(request.query_string)
moduleimpl_id = query_args.get("moduleimpl_id", [""])[0] moduleimpl_id = query_args.get("moduleimpl_id", [""])[0]
if "head_message" in query_args: if "head_message" in query_args:
del query_args["head_message"] del query_args["head_message"]
destination = "%s?%s" % ( destination = "%s?%s" % (
REQUEST.URL, request.base_url,
urllib.parse.urlencode(query_args, True), urllib.parse.urlencode(query_args, True),
) )
destination = destination.replace( destination = destination.replace(
@ -935,7 +934,7 @@ def form_choix_saisie_semaine(groups_infos, REQUEST=None):
return "\n".join(FA) return "\n".join(FA)
def export_groups_as_moodle_csv(formsemestre_id=None, REQUEST=None): def export_groups_as_moodle_csv(formsemestre_id=None):
"""Export all students/groups, in a CSV format suitable for Moodle """Export all students/groups, in a CSV format suitable for Moodle
Each (student,group) will be listed on a separate line Each (student,group) will be listed on a separate line
jo@univ.fr,S3-A jo@univ.fr,S3-A
@ -977,4 +976,4 @@ def export_groups_as_moodle_csv(formsemestre_id=None, REQUEST=None):
text_with_titles=prefs["moodle_csv_with_headerline"], text_with_titles=prefs["moodle_csv_with_headerline"],
preferences=prefs, preferences=prefs,
) )
return tab.make_page(format="csv", REQUEST=REQUEST) return tab.make_page(format="csv")

View File

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

View File

@ -31,7 +31,7 @@
import datetime import datetime
from operator import itemgetter 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.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -81,6 +81,7 @@ def list_authorized_etuds_by_sem(sem, delai=274):
"title": src["titreannee"], "title": src["titreannee"],
"title_target": "formsemestre_status?formsemestre_id=%s" "title_target": "formsemestre_status?formsemestre_id=%s"
% src["formsemestre_id"], % src["formsemestre_id"],
"filename": "etud_autorises",
}, },
} }
# ajoute attribut inscrit qui indique si l'étudiant est déjà inscrit dans le semestre dest. # 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"], % sem["formsemestre_id"],
"comment": " actuellement inscrits dans ce semestre", "comment": " actuellement inscrits dans ce semestre",
"help": "Ces étudiants sont actuellement inscrits dans ce semestre. Si vous les décochez, il seront désinscrits.", "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) cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"]) sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
cursor.execute( cursor.execute(
"""SELECT I.etudid """SELECT ins.etudid
FROM FROM
notes_formsemestre_inscription ins, notes_formsemestre_inscription ins,
notes_formsemestre S, notes_formsemestre S
identite i
WHERE ins.formsemestre_id = S.id WHERE ins.formsemestre_id = S.id
AND S.id != %(formsemestre_id)s AND S.id != %(formsemestre_id)s
AND S.date_debut <= %(date_debut_iso)s AND S.date_debut <= %(date_debut_iso)s
AND S.date_fin >= %(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, sem,
) )
@ -264,7 +265,6 @@ def formsemestre_inscr_passage(
inscrit_groupes=False, inscrit_groupes=False,
submitted=False, submitted=False,
dialog_confirmed=False, dialog_confirmed=False,
REQUEST=None,
): ):
"""Form. pour inscription des etudiants d'un semestre dans un autre """Form. pour inscription des etudiants d'un semestre dans un autre
(donné par formsemestre_id). (donné par formsemestre_id).
@ -287,9 +287,11 @@ def formsemestre_inscr_passage(
header = html_sco_header.sco_header(page_title="Passage des étudiants") header = html_sco_header.sco_header(page_title="Passage des étudiants")
footer = html_sco_header.sco_footer() footer = html_sco_header.sco_footer()
H = [header] H = [header]
if type(etuds) == type(""): if isinstance(etuds, str):
etuds = etuds.split(",") # vient du form de confirmation 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) auth_etuds_by_sem, inscrits, candidats = list_authorized_etuds_by_sem(sem)
etuds_set = set(etuds) etuds_set = set(etuds)
candidats_set = set(candidats) candidats_set = set(candidats)
@ -312,7 +314,6 @@ def formsemestre_inscr_passage(
if not submitted: if not submitted:
H += build_page( H += build_page(
REQUEST,
sem, sem,
auth_etuds_by_sem, auth_etuds_by_sem,
inscrits, inscrits,
@ -342,18 +343,22 @@ def formsemestre_inscr_passage(
% inscrits[etudid] % inscrits[etudid]
) )
H.append("</ol>") 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("""<h3>Il n'y a rien à modifier !</h3>""")
H.append( H.append(
scu.confirm_dialog( 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, add_headers=False,
cancel_url="formsemestre_inscr_passage?formsemestre_id=" cancel_url="formsemestre_inscr_passage?formsemestre_id="
+ str(formsemestre_id), + str(formsemestre_id),
OK="Effectuer l'opération", OK="Effectuer l'opération" if todo else "",
parameters={ parameters={
"formsemestre_id": formsemestre_id, "formsemestre_id": formsemestre_id,
"etuds": ",".join(etuds), "etuds": ",".join([str(x) for x in etuds]),
"inscrit_groupes": inscrit_groupes, "inscrit_groupes": inscrit_groupes,
"submitted": 1, "submitted": 1,
}, },
@ -397,7 +402,6 @@ def formsemestre_inscr_passage(
def build_page( def build_page(
REQUEST,
sem, sem,
auth_etuds_by_sem, auth_etuds_by_sem,
inscrits, inscrits,
@ -413,9 +417,9 @@ def build_page(
H = [ H = [
html_sco_header.html_sem_header( 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="hidden" name="formsemestre_id" value="%(formsemestre_id)s"/>
<input type="submit" name="submitted" value="Appliquer les modifications"/> <input type="submit" name="submitted" value="Appliquer les modifications"/>
&nbsp;<a href="#help">aide</a> &nbsp;<a href="#help">aide</a>
@ -507,7 +511,12 @@ def etuds_select_boxes(
</script> </script>
<div class="etuds_select_boxes">""" <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(): for src_cat in auth_etuds_by_cat.keys():
infos = auth_etuds_by_cat[src_cat]["infos"] infos = auth_etuds_by_cat[src_cat]["infos"]
infos["comment"] = infos.get("comment", "") # commentaire dans sous-titre boite infos["comment"] = infos.get("comment", "") # commentaire dans sous-titre boite
@ -550,10 +559,8 @@ def etuds_select_boxes(
if with_checkbox or sel_inscrits: if with_checkbox or sel_inscrits:
H.append(")") H.append(")")
if base_url and etuds: if base_url and etuds:
H.append( url = scu.build_url_query(base_url, export_cat_xls=src_cat)
'<a href="%s&export_cat_xls=%s">%s</a>&nbsp;' H.append(f'<a href="{url}">{scu.ICON_XLS}</a>&nbsp;')
% (base_url, src_cat, scu.ICON_XLS)
)
H.append("</div>") H.append("</div>")
for etud in etuds: for etud in etuds:
if etud.get("inscrit", False): if etud.get("inscrit", False):
@ -633,4 +640,4 @@ def etuds_select_box_xls(src_cat):
caption="%(title)s. %(help)s" % src_cat["infos"], caption="%(title)s. %(help)s" % src_cat["infos"],
preferences=sco_preferences.SemPreferences(), 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 """Liste des notes d'une évaluation
""" """
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
from operator import itemgetter from operator import itemgetter
import urllib
import flask 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.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
@ -56,7 +56,7 @@ from app.scodoc.gen_tables import GenTable
from app.scodoc.htmlutils import histogram_notes from app.scodoc.htmlutils import histogram_notes
def do_evaluation_listenotes(REQUEST): def do_evaluation_listenotes():
""" """
Affichage des notes d'une évaluation Affichage des notes d'une évaluation
@ -64,12 +64,13 @@ def do_evaluation_listenotes(REQUEST):
(si moduleimpl_id, affiche toutes les évaluatons du module) (si moduleimpl_id, affiche toutes les évaluatons du module)
""" """
mode = None mode = None
if "evaluation_id" in REQUEST.form: vals = scu.get_request_args()
evaluation_id = int(REQUEST.form["evaluation_id"]) if "evaluation_id" in vals:
evaluation_id = int(vals["evaluation_id"])
mode = "eval" mode = "eval"
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id}) evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
if "moduleimpl_id" in REQUEST.form: if "moduleimpl_id" in vals:
moduleimpl_id = int(REQUEST.form["moduleimpl_id"]) moduleimpl_id = int(vals["moduleimpl_id"])
mode = "module" mode = "module"
evals = sco_evaluations.do_evaluation_list({"moduleimpl_id": moduleimpl_id}) evals = sco_evaluations.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
if not mode: if not mode:
@ -77,7 +78,7 @@ def do_evaluation_listenotes(REQUEST):
if not evals: if not evals:
return "<p>Aucune évaluation !</p>" 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 E = evals[0] # il y a au moins une evaluation
# description de l'evaluation # description de l'evaluation
if mode == "eval": if mode == "eval":
@ -177,8 +178,8 @@ def do_evaluation_listenotes(REQUEST):
), ),
] ]
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
descr, descr,
cancelbutton=None, cancelbutton=None,
submitbutton=None, submitbutton=None,
@ -201,7 +202,6 @@ def do_evaluation_listenotes(REQUEST):
hide_groups = tf[2]["hide_groups"] hide_groups = tf[2]["hide_groups"]
with_emails = tf[2]["with_emails"] with_emails = tf[2]["with_emails"]
return _make_table_notes( return _make_table_notes(
REQUEST,
tf[1], tf[1],
evals, evals,
format=format, format=format,
@ -214,7 +214,6 @@ def do_evaluation_listenotes(REQUEST):
def _make_table_notes( def _make_table_notes(
REQUEST,
html_form, html_form,
evals, evals,
format="", format="",
@ -482,7 +481,7 @@ def _make_table_notes(
# html_generate_cells=False # la derniere ligne (moyennes) est incomplete # 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": if format != "html":
return t return t
@ -760,9 +759,7 @@ def evaluation_check_absences(evaluation_id):
return ValButAbs, AbsNonSignalee, ExcNonSignalee, ExcNonJust, AbsButExc return ValButAbs, AbsNonSignalee, ExcNonSignalee, ExcNonJust, AbsButExc
def evaluation_check_absences_html( def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True):
evaluation_id, with_header=True, show_ok=True, REQUEST=None
):
"""Affiche etat verification absences d'une evaluation""" """Affiche etat verification absences d'une evaluation"""
E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0] E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})[0]
@ -778,9 +775,7 @@ def evaluation_check_absences_html(
if with_header: if with_header:
H = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header("Vérification absences à l'évaluation"),
REQUEST, "Vérification absences à l'évaluation"
),
sco_evaluations.evaluation_describe(evaluation_id=evaluation_id), 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>""", """<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>' '<a class="stdlink" href="Absences/doSignaleAbsence?etudid=%s&datedebut=%s&datefin=%s&demijournee=%s&moduleimpl_id=%s">signaler cette absence</a>'
% ( % (
etud["etudid"], etud["etudid"],
six.moves.urllib.parse.quote(E["jour"]), urllib.parse.quote(E["jour"]),
six.moves.urllib.parse.quote(E["jour"]), urllib.parse.quote(E["jour"]),
demijournee, demijournee,
E["moduleimpl_id"], E["moduleimpl_id"],
) )
@ -861,12 +856,11 @@ def evaluation_check_absences_html(
return "\n".join(H) 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 !""" """Affiche etat verification absences pour toutes les evaluations du semestre !"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
H = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header(
REQUEST,
"Vérification absences aux évaluations de ce semestre", "Vérification absences aux évaluations de ce semestre",
sem, sem,
), ),
@ -894,7 +888,6 @@ def formsemestre_check_absences_html(formsemestre_id, REQUEST=None):
E["evaluation_id"], E["evaluation_id"],
with_header=False, with_header=False,
show_ok=False, show_ok=False,
REQUEST=REQUEST,
) )
) )
if evals: if evals:

View File

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

View File

@ -88,14 +88,14 @@ def do_modalite_list(*args, **kw):
return _modaliteEditor.list(cnx, *args, **kw) return _modaliteEditor.list(cnx, *args, **kw)
def do_modalite_create(args, REQUEST=None): def do_modalite_create(args):
"create a modalite" "create a modalite"
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
r = _modaliteEditor.create(cnx, args) r = _modaliteEditor.create(cnx, args)
return r return r
def do_modalite_delete(oid, REQUEST=None): def do_modalite_delete(oid):
"delete a modalite" "delete a modalite"
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
log("do_modalite_delete: form_modalite_id=%s" % oid) 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 ) # > moduleimpl_delete
def do_moduleimpl_list( def do_moduleimpl_list(moduleimpl_id=None, formsemestre_id=None, module_id=None):
moduleimpl_id=None, formsemestre_id=None, module_id=None, REQUEST=None
):
"list moduleimpls" "list moduleimpls"
args = locals() args = locals()
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
@ -110,7 +108,7 @@ def do_moduleimpl_list(
# Ajoute la liste des enseignants # Ajoute la liste des enseignants
for mo in modimpls: for mo in modimpls:
mo["ens"] = do_ens_list(args={"moduleimpl_id": mo["moduleimpl_id"]}) 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): 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( 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 """Liste les moduleimpls et ajoute dans chacun le module correspondant
Tri la liste par semestre/UE/numero_matiere/numero_module. 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 from app.scodoc import sco_edit_module
args = locals() args = locals()
del args["REQUEST"]
modimpls = do_moduleimpl_list( modimpls = do_moduleimpl_list(
**{ **{
"moduleimpl_id": moduleimpl_id, "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" "list moduleimpl_inscriptions"
args = locals() args = locals()
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
return scu.return_text_if_published( return _moduleimpl_inscriptionEditor.list(cnx, args)
_moduleimpl_inscriptionEditor.list(cnx, args), REQUEST
)
def do_moduleimpl_listeetuds(moduleimpl_id): def do_moduleimpl_listeetuds(moduleimpl_id):
@ -244,9 +239,7 @@ def do_moduleimpl_inscription_delete(oid, formsemestre_id=None):
) # > moduleimpl_inscription ) # > moduleimpl_inscription
def do_moduleimpl_inscrit_etuds( def do_moduleimpl_inscrit_etuds(moduleimpl_id, formsemestre_id, etudids, reset=False):
moduleimpl_id, formsemestre_id, etudids, reset=False, REQUEST=None
):
"""Inscrit les etudiants (liste d'etudids) a ce module. """Inscrit les etudiants (liste d'etudids) a ce module.
Si reset, desinscrit tous les autres. Si reset, desinscrit tous les autres.
""" """
@ -309,7 +302,7 @@ def do_ens_create(args):
return r 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). """Check if current user can modify module resp. (raise exception if not).
= Admin, et dir des etud. (si option l'y autorise) = Admin, et dir des etud. (si option l'y autorise)
""" """

View File

@ -30,7 +30,8 @@
from operator import itemgetter from operator import itemgetter
import flask 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.notesdb as ndb
import app.scodoc.sco_utils as scu 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 from app.scodoc.sco_permissions import Permission
def moduleimpl_inscriptions_edit( def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
moduleimpl_id, etuds=[], submitted=False, REQUEST=None
):
"""Formulaire inscription des etudiants a ce module """Formulaire inscription des etudiants a ce module
* Gestion des inscriptions * Gestion des inscriptions
Nom TD TA TP (triable) Nom TD TA TP (triable)
@ -137,7 +136,7 @@ def moduleimpl_inscriptions_edit(
</script>""" </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( H.append(
""" """
<input type="hidden" name="moduleimpl_id" value="%(moduleimpl_id)s"/> <input type="hidden" name="moduleimpl_id" value="%(moduleimpl_id)s"/>
@ -199,7 +198,7 @@ def moduleimpl_inscriptions_edit(
else: # SUBMISSION else: # SUBMISSION
# inscrit a ce module tous les etuds selectionnes # inscrit a ce module tous les etuds selectionnes
sco_moduleimpl.do_moduleimpl_inscrit_etuds( 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)) 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 """Affiche quelques informations sur les inscriptions
aux modules de ce semestre. 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> tous sauf <liste d'au plus 7 noms>
""" """
authuser = REQUEST.AUTHENTICATED_USER authuser = current_user
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( 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 mod["nb_inscrits"] = nb_inscrits
options.append(mod) options.append(mod)
# Page HTML: # Page HTML:
H = [ H = [html_sco_header.html_sem_header("Inscriptions aux modules du semestre")]
html_sco_header.html_sem_header(REQUEST, "Inscriptions aux modules du semestre")
]
H.append("<h3>Inscrits au semestre: %d étudiants</h3>" % len(inscrits)) 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 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.""" """Desincrit l'etudiant de tous les modules de cette UE dans ce semestre."""
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
@ -544,7 +541,6 @@ def do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id, REQUEST=None):
""", """,
{"etudid": etudid, "formsemestre_id": formsemestre_id, "ue_id": ue_id}, {"etudid": etudid, "formsemestre_id": formsemestre_id, "ue_id": ue_id},
) )
if REQUEST:
logdb( logdb(
cnx, cnx,
method="etud_desinscrit_ue", method="etud_desinscrit_ue",
@ -557,7 +553,7 @@ def do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id, REQUEST=None):
) # > desinscription etudiant des modules ) # > 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.""" """Incrit l'etudiant de tous les modules de cette UE dans ce semestre."""
# Verifie qu'il est bien inscrit au semestre # Verifie qu'il est bien inscrit au semestre
insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(

View File

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

View File

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

View File

@ -535,7 +535,7 @@ class SituationEtudParcoursGeneric(object):
validated = True validated = True
return s return s
def valide_decision(self, decision, REQUEST): def valide_decision(self, decision):
"""Enregistre la decision (instance de DecisionSem) """Enregistre la decision (instance de DecisionSem)
Enregistre codes semestre et UE, et autorisations inscription. Enregistre codes semestre et UE, et autorisations inscription.
""" """
@ -588,7 +588,6 @@ class SituationEtudParcoursGeneric(object):
self.etudid, self.etudid,
decision.code_etat, decision.code_etat,
decision.assiduite, decision.assiduite,
REQUEST=REQUEST,
) )
# -- modification du code du semestre precedent # -- modification du code du semestre precedent
if self.prev and decision.new_code_prev: if self.prev and decision.new_code_prev:
@ -619,7 +618,6 @@ class SituationEtudParcoursGeneric(object):
self.etudid, self.etudid,
decision.new_code_prev, 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... 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( sco_cache.invalidate_formsemestre(
@ -897,9 +895,7 @@ def formsemestre_update_validation_sem(
return to_invalidate return to_invalidate
def formsemestre_validate_ues( def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite):
formsemestre_id, etudid, code_etat_sem, assiduite, REQUEST=None
):
"""Enregistre codes UE, selon état semestre. """Enregistre codes UE, selon état semestre.
Les codes UE sont toujours calculés ici, et non passés en paramètres 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. car ils ne dépendent que de la note d'UE et de la validation ou non du semestre.
@ -933,7 +929,6 @@ def formsemestre_validate_ues(
cnx, nt, formsemestre_id, etudid, ue_id, code_ue cnx, nt, formsemestre_id, etudid, ue_id, code_ue
) )
if REQUEST:
logdb( logdb(
cnx, cnx,
method="validate_ue", method="validate_ue",

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 datetime
import glob import glob
import io import io
@ -52,6 +54,7 @@ import requests
import time import time
import traceback import traceback
import PIL
from PIL import Image as PILImage from PIL import Image as PILImage
from flask import request, g from flask import request, g
@ -118,7 +121,7 @@ def etud_photo_url(etud, size="small", fast=False):
return photo_url 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) """Returns photo image (HTTP response)
If not etudid, use "unknown" image 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) filename = photo_pathname(etud, size=size)
if not filename: if not filename:
filename = UNKNOWN_IMAGE_PATH filename = UNKNOWN_IMAGE_PATH
return _http_jpeg_file(filename, REQUEST=REQUEST) return _http_jpeg_file(filename)
def _http_jpeg_file(filename, REQUEST=None): def _http_jpeg_file(filename):
"""returns an image. """returns an image as a Flask response"""
This function will be modified when we kill #zope
"""
st = os.stat(filename) st = os.stat(filename)
last_modified = st.st_mtime # float timestamp 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 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") header = request.headers.get("If-Modified-Since")
if header is not None: if header is not None:
header = header.split(";")[0] header = header.split(";")[0]
@ -159,20 +152,27 @@ def _http_jpeg_file(filename, REQUEST=None):
try: try:
dt = datetime.datetime.strptime(header, "%a, %d %b %Y %H:%M:%S GMT") dt = datetime.datetime.strptime(header, "%a, %d %b %Y %H:%M:%S GMT")
mod_since = dt.timestamp() mod_since = dt.timestamp()
except: except ValueError:
mod_since = None mod_since = None
if (mod_since is not None) and last_modified <= mod_since: if (mod_since is not None) and last_modified <= mod_since:
RESPONSE.setStatus(304) # not modified return "", 304 # not modified
return "" #
last_modified_str = time.strftime(
return open(filename, mode="rb").read() "%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"): def etud_photo_is_local(etud, size="small"):
return photo_pathname(etud, size=size) 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) """HTML img tag for the photo, either in small size (h90)
or original size (size=="orig") 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. """HTML img tag for the photo, in full size.
Full-size images are always stored locally in the filesystem. Full-size images are always stored locally in the filesystem.
They are the original uploaded images, converted in jpeg. They are the original uploaded images, converted in jpeg.
""" """
return etud_photo_html( return etud_photo_html(etud=etud, etudid=etudid, title=title, size="orig")
etud=etud, etudid=etudid, title=title, size="orig", REQUEST=REQUEST
)
def photo_pathname(etud, size="orig"): def photo_pathname(etud, size="orig"):
@ -246,7 +244,10 @@ def store_photo(etud, data):
filesize = len(data) filesize = len(data)
if filesize < 10 or filesize > MAX_FILE_SIZE: if filesize < 10 or filesize > MAX_FILE_SIZE:
return 0, "Fichier image de taille invalide ! (%d)" % filesize return 0, "Fichier image de taille invalide ! (%d)" % filesize
try:
filename = save_image(etud["etudid"], data) filename = save_image(etud["etudid"], data)
except PIL.UnidentifiedImageError:
raise ScoGenError(msg="Fichier d'image invalide ou non format non supporté")
# update database: # update database:
etud["photo_filename"] = filename etud["photo_filename"] = filename
etud["foto"] = None etud["foto"] = None
@ -260,7 +261,7 @@ def store_photo(etud, data):
return 1, "ok" return 1, "ok"
def suppress_photo(etud, REQUEST=None): def suppress_photo(etud):
"""Suppress a photo""" """Suppress a photo"""
log("suppress_photo etudid=%s" % etud["etudid"]) log("suppress_photo etudid=%s" % etud["etudid"])
rel_path = photo_pathname(etud) rel_path = photo_pathname(etud)
@ -278,7 +279,6 @@ def suppress_photo(etud, REQUEST=None):
log("removing file %s" % filename) log("removing file %s" % filename)
os.remove(filename) os.remove(filename)
# 3- log # 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) filename = get_new_filename(etudid)
path = os.path.join(PHOTO_DIR, filename) path = os.path.join(PHOTO_DIR, filename)
log("saving %dx%d jpeg to %s" % (img.size[0], img.size[1], path)) 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) img.save(path + IMAGE_EXT, format="JPEG", quality=92)
# resize: # resize:
img = scale_height(img) img = scale_height(img)
@ -341,7 +342,7 @@ def find_new_dir():
def copy_portal_photo_to_fs(etud): def copy_portal_photo_to_fs(etud):
"""Copy the photo from portal (distant website) to local fs. """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) sco_etud.format_etud_ident(etud)
url = photo_portal_url(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) log("copy_portal_photo_to_fs: getting %s" % url)
r = requests.get(url, timeout=portal_timeout) r = requests.get(url, timeout=portal_timeout)
except: except:
log("download failed: exception:\n%s" % traceback.format_exc()) # log("download failed: exception:\n%s" % traceback.format_exc())
log("called from:\n" + "".join(traceback.format_stack())) # 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) return None, "%s: erreur chargement de %s" % (etud["nomprenom"], url)
if r.status_code != 200: 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) return None, "%s: erreur chargement de %s" % (etud["nomprenom"], url)
data = r.content # image bytes data = r.content # image bytes
try: 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 import collections
from flask import url_for, g from flask import url_for, g, request
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc import sco_abs from app.scodoc import sco_abs
@ -164,7 +164,7 @@ def _getEtudInfoGroupes(group_ids, etat=None):
return etuds return etuds
def formsemestre_poursuite_report(formsemestre_id, format="html", REQUEST=None): def formsemestre_poursuite_report(formsemestre_id, format="html"):
"""Table avec informations "poursuite" """ """Table avec informations "poursuite" """
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
etuds = _getEtudInfoGroupes([sco_groups.get_default_group(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.caption = "Récapitulatif %s." % sem["titreannee"]
tab.html_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( return tab.make_page(
title="""<h2 class="formsemestre">Poursuite d'études</h2>""", title="""<h2 class="formsemestre">Poursuite d'études</h2>""",
init_qtip=True, init_qtip=True,
javascripts=["js/etud_info.js"], javascripts=["js/etud_info.js"],
format=format, format=format,
REQUEST=REQUEST,
with_html_headers=True, 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: - avoir un mapping (read only) de toutes les valeurs:
sco_preferences.SemPreferences(formsemestre_id) sco_preferences.SemPreferences(formsemestre_id)
- editer les preferences globales: - 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: - editer les preferences d'un semestre:
SemPreferences(formsemestre_id).edit() SemPreferences(formsemestre_id).edit()
@ -111,7 +111,8 @@ get_base_preferences(formsemestre_id)
""" """
import flask 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.models import Departement
from app.scodoc import sco_cache 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): def _get_pref_default_value_from_config(name, pref_spec):
"""get default value store in application level config. """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 # XXX va changer avec la nouvelle base
# search in scu.CONFIG # search in scu.CONFIG
@ -1408,7 +1409,7 @@ class BasePreferences(object):
{ {
"initvalue": 1, "initvalue": 1,
"title": "Indique si les bulletins sont publiés", "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", "input_type": "boolcheckbox",
"labels": ["non", "oui"], "labels": ["non", "oui"],
"category": "bul", "category": "bul",
@ -1891,7 +1892,7 @@ class BasePreferences(object):
def get(self, formsemestre_id, name): def get(self, formsemestre_id, name):
"""Returns preference value. """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 = { params = {
"dept_id": self.dept_id, "dept_id": self.dept_id,
@ -1901,7 +1902,7 @@ class BasePreferences(object):
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
plist = self._editor.list(cnx, params) plist = self._editor.list(cnx, params)
if not plist: if not plist:
del params["formsemestre_id"] params["formsemestre_id"] = None
plist = self._editor.list(cnx, params) plist = self._editor.list(cnx, params)
if not plist: if not plist:
return self.default[name] return self.default[name]
@ -2013,7 +2014,7 @@ class BasePreferences(object):
self._editor.delete(cnx, pdb[0]["pref_id"]) self._editor.delete(cnx, pdb[0]["pref_id"])
sco_cache.invalidate_formsemestre() # > modif preferences sco_cache.invalidate_formsemestre() # > modif preferences
def edit(self, REQUEST): def edit(self):
"""HTML dialog: edit global preferences""" """HTML dialog: edit global preferences"""
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
@ -2022,15 +2023,17 @@ class BasePreferences(object):
html_sco_header.sco_header(page_title="Préférences"), html_sco_header.sco_header(page_title="Préférences"),
"<h2>Préférences globales pour %s</h2>" % scu.ScoURL(), "<h2>Préférences globales pour %s</h2>" % scu.ScoURL(),
f"""<p><a href="{url_for("scolar.config_logos", scodoc_dept=g.scodoc_dept) 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="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> <p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
""", """,
] ]
form = self.build_tf_form() form = self.build_tf_form()
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
form, form,
initvalues=self.prefs[None], initvalues=self.prefs[None],
submitlabel="Enregistrer les modifications", submitlabel="Enregistrer les modifications",
@ -2140,7 +2143,7 @@ class SemPreferences(object):
return self.base_prefs.is_global(self.formsemestre_id, name) return self.base_prefs.is_global(self.formsemestre_id, name)
# The dialog # The dialog
def edit(self, categories=[], REQUEST=None): def edit(self, categories=[]):
"""Dialog to edit semestre preferences in given categories""" """Dialog to edit semestre preferences in given categories"""
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
@ -2151,7 +2154,7 @@ class SemPreferences(object):
) # a bug ! ) # a bug !
sem = sco_formsemestre.get_formsemestre(self.formsemestre_id) sem = sco_formsemestre.get_formsemestre(self.formsemestre_id)
H = [ 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="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> <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(("destination", {"input_type": "hidden"}))
form.append(("formsemestre_id", {"input_type": "hidden"})) form.append(("formsemestre_id", {"input_type": "hidden"}))
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
form, form,
initvalues=self, initvalues=self,
cssclass="sco_pref", 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") return flask.redirect(dest_url + "&head_message=Préférences modifiées")
elif destination == "again": elif destination == "again":
return flask.redirect( return flask.redirect(
REQUEST.URL0 + "?formsemestre_id=" + str(self.formsemestre_id) request.base_url + "?formsemestre_id=" + str(self.formsemestre_id)
) )
elif destination == "global": elif destination == "global":
return flask.redirect(scu.ScoURL() + "/edit_preferences") return flask.redirect(scu.ScoURL() + "/edit_preferences")
@ -2253,7 +2256,7 @@ function set_global_pref(el, pref_name) {
# #
def doc_preferences(): def doc_preferences():
""" Liste les preferences en MarkDown, pour la documentation""" """Liste les preferences en MarkDown, pour la documentation"""
L = [] L = []
for cat, cat_descr in PREF_CATEGORIES: for cat, cat_descr in PREF_CATEGORIES:
L.append([""]) L.append([""])

View File

@ -31,6 +31,9 @@ import time
from openpyxl.styles.numbers import FORMAT_NUMBER_00 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 import app.scodoc.sco_utils as scu
from app.scodoc import sco_abs from app.scodoc import sco_abs
from app.scodoc import sco_groups from app.scodoc import sco_groups
@ -45,7 +48,7 @@ from app.scodoc import sco_preferences
from app.scodoc.sco_excel import ScoExcelSheet 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" "Feuille excel pour preparation des jurys"
nt = sco_cache.NotesTableCache.get( nt = sco_cache.NotesTableCache.get(
formsemestre_id formsemestre_id
@ -318,9 +321,14 @@ def feuille_preparation_jury(formsemestre_id, REQUEST):
% ( % (
sco_version.SCONAME, sco_version.SCONAME,
time.strftime("%d/%m/%Y"), time.strftime("%d/%m/%Y"),
REQUEST.BASE0, request.url_root,
REQUEST.AUTHENTICATED_USER, current_user,
) )
) )
xls = ws.generate_standalone() xls = ws.generate()
return sco_excel.send_excel_file(REQUEST, xls, f"PrepaJury{sn}{scu.XLSX_SUFFIX}") 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 from reportlab.lib import styles
import flask 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.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
@ -495,7 +495,7 @@ def pvjury_table(
return lines, titles, columns_ids 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 """Page récapitulant les décisions de jury
dpv: result of dict_pvjury dpv: result of dict_pvjury
""" """
@ -535,13 +535,11 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True, REQUEST=No
return tab.make_page( return tab.make_page(
format=format, format=format,
with_html_headers=False, with_html_headers=False,
REQUEST=REQUEST,
publish=publish, 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 = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header(
REQUEST,
"Décisions du jury pour le semestre", "Décisions du jury pour le semestre",
sem, sem,
init_qtip=True, 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 """Generation PV jury en PDF: saisie des paramètres
Si etudid, PV pour un seul etudiant. Sinon, tout les inscrits au groupe indiqué. 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 = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header(
REQUEST,
"Edition du PV de jury %s" % etuddescr, "Edition du PV de jury %s" % etuddescr,
sem=sem, sem=sem,
javascripts=sco_groups_view.JAVASCRIPTS, javascripts=sco_groups_view.JAVASCRIPTS,
@ -658,8 +655,8 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None, REQUEST=
else: else:
menu_choix_groupe = "" # un seul etudiant à editer menu_choix_groupe = "" # un seul etudiant à editer
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
descr, descr,
cancelbutton="Annuler", cancelbutton="Annuler",
method="get", method="get",
@ -707,7 +704,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None, REQUEST=
else: else:
groups_filename = "" groups_filename = ""
filename = "PV-%s%s-%s.pdf" % (sem["titre_num"], groups_filename, dt) 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): 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" "Lettres avis jury en PDF"
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
if not group_ids: if not group_ids:
@ -806,8 +803,7 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[], REQUEST=No
H = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header(
REQUEST, "Édition des lettres individuelles",
"Edition des lettres individuelles",
sem=sem, sem=sem,
javascripts=sco_groups_view.JAVASCRIPTS, javascripts=sco_groups_view.JAVASCRIPTS,
cssstyles=sco_groups_view.CSSSTYLES, cssstyles=sco_groups_view.CSSSTYLES,
@ -827,8 +823,8 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[], REQUEST=No
) )
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
descr, descr,
cancelbutton="Annuler", cancelbutton="Annuler",
method="POST", method="POST",
@ -868,7 +864,7 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[], REQUEST=No
dt = time.strftime("%Y-%m-%d") dt = time.strftime("%Y-%m-%d")
groups_filename = "-" + groups_infos.groups_filename groups_filename = "-" + groups_infos.groups_filename
filename = "lettres-%s%s-%s.pdf" % (sem["titre_num"], groups_filename, dt) 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(): def descrform_lettres_individuelles():

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@ import time
import pprint import pprint
from operator import itemgetter from operator import itemgetter
from flask import g, url_for, send_file from flask import g, url_for
from flask_login import current_user from flask_login import current_user
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -112,6 +112,7 @@ def formsemestre_synchro_etuds(
base_url = url_for( base_url = url_for(
"notes.formsemestre_synchro_etuds", "notes.formsemestre_synchro_etuds",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
anneeapogee=anneeapogee or None, # si None, le param n'est pas dans l'URL 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 etuds = etuds.split(",") # vient du form de confirmation
elif isinstance(etuds, int): elif isinstance(etuds, int):
etuds = [etuds] 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(",") 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, etuds_by_cat,
a_importer, a_importer,
@ -146,11 +151,11 @@ def formsemestre_synchro_etuds(
base_url=base_url, base_url=base_url,
read_only=read_only, read_only=read_only,
) )
return send_file( return scu.send_file(
xls, xls,
mimetype=scu.XLS_MIMETYPE, mime=scu.XLS_MIMETYPE,
download_name=scu.sanitize_filename(filename + scu.XLSX_SUFFIX), filename=filename,
as_attachment=True, suffix=scu.XLSX_SUFFIX,
) )
H = [header] H = [header]
@ -199,31 +204,38 @@ def formsemestre_synchro_etuds(
) )
H.append("</ol>") H.append("</ol>")
if a_desinscrire or a_desinscrire_without_key: if a_desinscrire:
H.append("<h3>Etudiants à désinscrire :</h3><ol>") H.append("<h3>Etudiants à désinscrire :</h3><ol>")
for key in a_desinscrire: for key in a_desinscrire:
etud = sco_etud.get_etud_info(filled=True, code_nip=key)[0] etud = sco_etud.get_etud_info(filled=True, code_nip=key)[0]
H.append('<li class="desinscription">%(nomprenom)s</li>' % etud) 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: for etudid in a_desinscrire_without_key:
etud = inscrits_without_key_all[etudid] etud = inscrits_without_key_all[etudid]
sco_etud.format_etud_ident(etud) sco_etud.format_etud_ident(etud)
H.append('<li class="desinscription">%(nomprenom)s</li>' % etud) H.append('<li class="desinscription">%(nomprenom)s</li>' % etud)
H.append("</ol>") 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("""<h3>Il n'y a rien à modifier !</h3>""")
H.append( H.append(
scu.confirm_dialog( scu.confirm_dialog(
dest_url="formsemestre_synchro_etuds", dest_url="formsemestre_synchro_etuds",
add_headers=False, add_headers=False,
cancel_url="formsemestre_synchro_etuds?formsemestre_id=" cancel_url="formsemestre_synchro_etuds?formsemestre_id="
+ str(formsemestre_id), + str(formsemestre_id),
OK="Effectuer l'opération", OK="Effectuer l'opération" if todo else "OK",
parameters={ parameters={
"formsemestre_id": formsemestre_id, "formsemestre_id": formsemestre_id,
"etuds": ",".join(etuds), "etuds": ",".join(etuds),
"inscrits_without_key": ",".join(inscrits_without_key), "inscrits_without_key": ",".join(
[str(x) for x in inscrits_without_key]
),
"submitted": 1, "submitted": 1,
"anneeapogee": anneeapogee, "anneeapogee": anneeapogee,
}, },
@ -317,7 +329,8 @@ def build_page(
""" """
% sem, % 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"]), % (sem["formsemestre_id"]),
"\n".join(options), "\n".join(options),
""" """
@ -430,17 +443,20 @@ def list_synch(sem, anneeapogee=None):
return etuds return etuds
# #
r = { boites = {
"etuds_ok": { "etuds_a_importer": {
"etuds": set_to_sorted_list(etuds_ok, is_inscrit=True), "etuds": set_to_sorted_list(a_importer, is_inscrit=True, etud_apo=True),
"infos": { "infos": {
"id": "etuds_ok", "id": "etuds_a_importer",
"title": "Etudiants dans Apogée et déjà inscrits", "title": "Etudiants dans Apogée à importer",
"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.", "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": "", "title_target": "",
"with_checkbox": True, "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_noninscrits": {
"etuds": set_to_sorted_list(etuds_noninscrits, is_inscrit=True), "etuds": set_to_sorted_list(etuds_noninscrits, is_inscrit=True),
@ -453,20 +469,9 @@ def list_synch(sem, anneeapogee=None):
"title_target": "", "title_target": "",
"with_checkbox": True, "with_checkbox": True,
"etud_key": EKEY_SCO, "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_nonapogee": {
"etuds": set_to_sorted_list(etuds_nonapogee, is_inscrit=True), "etuds": set_to_sorted_list(etuds_nonapogee, is_inscrit=True),
"infos": { "infos": {
@ -478,6 +483,7 @@ def list_synch(sem, anneeapogee=None):
"title_target": "", "title_target": "",
"with_checkbox": True, "with_checkbox": True,
"etud_key": EKEY_SCO, "etud_key": EKEY_SCO,
"filename": "etuds_non_apogee",
}, },
}, },
"inscrits_without_key": { "inscrits_without_key": {
@ -489,11 +495,25 @@ def list_synch(sem, anneeapogee=None):
"title_target": "", "title_target": "",
"with_checkbox": True, "with_checkbox": True,
"checkbox_name": "inscrits_without_key", "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 ( return (
r, boites,
a_importer, a_importer,
etuds_noninscrits, etuds_noninscrits,
inscrits_set, inscrits_set,

View File

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

View File

@ -44,7 +44,7 @@ from reportlab.lib import colors
from PIL import Image as PILImage from PIL import Image as PILImage
import flask import flask
from flask import url_for, g, send_file from flask import url_for, g, send_file, request
from app import log from app import log
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -66,7 +66,6 @@ from app.scodoc import sco_etud
def trombino( def trombino(
REQUEST=None,
group_ids=[], # liste des groupes à afficher group_ids=[], # liste des groupes à afficher
formsemestre_id=None, # utilisé si pas de groupes selectionné formsemestre_id=None, # utilisé si pas de groupes selectionné
etat=None, etat=None,
@ -83,28 +82,26 @@ def trombino(
# #
if format != "html" and not dialog_confirmed: if format != "html" and not dialog_confirmed:
ok, dialog = check_local_photos_availability( ok, dialog = check_local_photos_availability(groups_infos, format=format)
groups_infos, REQUEST, format=format
)
if not ok: if not ok:
return dialog return dialog
if format == "zip": if format == "zip":
return _trombino_zip(groups_infos) return _trombino_zip(groups_infos)
elif format == "pdf": elif format == "pdf":
return _trombino_pdf(groups_infos, REQUEST) return _trombino_pdf(groups_infos)
elif format == "pdflist": elif format == "pdflist":
return _listeappel_photos_pdf(groups_infos, REQUEST) return _listeappel_photos_pdf(groups_infos)
else: else:
raise Exception("invalid format") 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(): def _trombino_html_header():
return html_sco_header.sco_header(javascripts=["js/trombino.js"]) 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)" "HTML snippet for trombino (with title and menu)"
menuTrombi = [ menuTrombi = [
{ {
@ -150,7 +147,7 @@ def trombino_html(groups_infos, REQUEST=None):
% t["etudid"] % t["etudid"]
) )
if sco_photos.etud_photo_is_local(t, size="small"): 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 else: # la photo n'est pas immédiatement dispo
foto = ( foto = (
'<span class="unloaded_img" id="%s"><img border="0" height="90" alt="en cours" src="/ScoDoc/static/icons/loading.jpg"/></span>' '<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) 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 """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 dans ScoDoc (seules les photos dont nous disposons localement peuvent être exportées
en pdf ou en zip). en pdf ou en zip).
@ -245,7 +242,7 @@ def _trombino_zip(groups_infos):
# Copy photos from portal to ScoDoc # 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)" "Copy photos from portal to ScoDoc (overwriting local copy)"
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids) groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
back_url = "groups_view?%s&curtab=tab-photos" % groups_infos.groups_query_args 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 raise
def _trombino_pdf(groups_infos, REQUEST): def _trombino_pdf(groups_infos):
"Send photos as pdf page" "Send photos as pdf page"
# Generate PDF page # Generate PDF page
filename = "trombino_%s" % groups_infos.groups_filename + ".pdf" 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: # --------------------- 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" "Doc pdf pour liste d'appel avec photos"
filename = "trombino_%s" % groups_infos.groups_filename + ".pdf" filename = "trombino_%s" % groups_infos.groups_filename + ".pdf"
sem = groups_infos.formsemestre # suppose 1 seul semestre sem = groups_infos.formsemestre # suppose 1 seul semestre
@ -466,11 +463,11 @@ def _listeappel_photos_pdf(groups_infos, REQUEST):
document.build(objects) document.build(objects)
data = report.getvalue() data = report.getvalue()
return scu.sendPDFFile(REQUEST, data, filename) return scu.sendPDFFile(data, filename)
# --------------------- Upload des photos de tout un groupe # --------------------- 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""" """Feuille excel pour import fichiers photos"""
fmt = sco_import_etuds.sco_import_format() fmt = sco_import_etuds.sco_import_format()
data = sco_import_etuds.sco_import_generate_excel_sample( 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"], 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""" """Formulaire pour importation photos"""
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids) groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
back_url = "groups_view?%s&curtab=tab-photos" % groups_infos.groups_query_args 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, % groups_infos.groups_query_args,
] ]
F = html_sco_header.sco_footer() 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( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, vals,
( (
("xlsfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}), ("xlsfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}),
("zipfile", {"title": "Fichier zip:", "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"], group_ids=tf[2]["group_ids"],
xlsfile=tf[2]["xlsfile"], xlsfile=tf[2]["xlsfile"],
zipfile=tf[2]["zipfile"], 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""" """Importation des photos"""
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids) groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
back_url = "groups_view?%s&curtab=tab-photos" % groups_infos.groups_query_args 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( def pdf_trombino_tours(
group_ids=[], # liste des groupes à afficher group_ids=[], # liste des groupes à afficher
formsemestre_id=None, # utilisé si pas de groupes selectionné formsemestre_id=None, # utilisé si pas de groupes selectionné
REQUEST=None,
): ):
"""Generation du trombinoscope en fichier PDF""" """Generation du trombinoscope en fichier PDF"""
# Informations sur les groupes à afficher: # Informations sur les groupes à afficher:
@ -272,7 +271,7 @@ def pdf_trombino_tours(
document.build(objects) document.build(objects)
data = report.getvalue() data = report.getvalue()
return scu.sendPDFFile(REQUEST, data, filename) return scu.sendPDFFile(data, filename)
# Feuille d'absences en pdf avec photos: # Feuille d'absences en pdf avec photos:
@ -281,7 +280,6 @@ def pdf_trombino_tours(
def pdf_feuille_releve_absences( def pdf_feuille_releve_absences(
group_ids=[], # liste des groupes à afficher group_ids=[], # liste des groupes à afficher
formsemestre_id=None, # utilisé si pas de groupes selectionné formsemestre_id=None, # utilisé si pas de groupes selectionné
REQUEST=None,
): ):
"""Generation de la feuille d'absence en fichier PDF, avec photos""" """Generation de la feuille d'absence en fichier PDF, avec photos"""
@ -466,4 +464,4 @@ def pdf_feuille_releve_absences(
document.build(objects) document.build(objects)
data = report.getvalue() 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 import flask
from flask import request
from flask_login import current_user from flask_login import current_user
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
@ -134,9 +135,7 @@ def external_ue_create(
return moduleimpl_id return moduleimpl_id
def external_ue_inscrit_et_note( def external_ue_inscrit_et_note(moduleimpl_id, formsemestre_id, notes_etuds):
moduleimpl_id, formsemestre_id, notes_etuds, REQUEST=None
):
log( log(
"external_ue_inscrit_et_note(moduleimpl_id=%s, notes_etuds=%s)" "external_ue_inscrit_et_note(moduleimpl_id=%s, notes_etuds=%s)"
% (moduleimpl_id, notes_etuds) % (moduleimpl_id, notes_etuds)
@ -146,7 +145,6 @@ def external_ue_inscrit_et_note(
moduleimpl_id, moduleimpl_id,
formsemestre_id, formsemestre_id,
list(notes_etuds.keys()), list(notes_etuds.keys()),
REQUEST=REQUEST,
) )
# Création d'une évaluation si il n'y en a pas déjà: # 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: else:
# crée une évaluation: # crée une évaluation:
evaluation_id = sco_evaluations.do_evaluation_create( evaluation_id = sco_evaluations.do_evaluation_create(
REQUEST=REQUEST,
moduleimpl_id=moduleimpl_id, moduleimpl_id=moduleimpl_id,
note_max=20.0, note_max=20.0,
coefficient=1.0, coefficient=1.0,
@ -168,7 +165,7 @@ def external_ue_inscrit_et_note(
) )
# Saisie des notes # Saisie des notes
_, _, _ = sco_saisie_notes._notes_add( _, _, _ = sco_saisie_notes._notes_add(
REQUEST.AUTHENTICATED_USER, current_user,
evaluation_id, evaluation_id,
list(notes_etuds.items()), list(notes_etuds.items()),
do_it=True, do_it=True,
@ -200,7 +197,7 @@ def get_external_moduleimpl_id(formsemestre_id, ue_id):
# Web function # 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 """Formulaire création UE externe + inscription étudiant et saisie note
- Demande UE: peut-être existante (liste les UE externes de cette formation), - Demande UE: peut-être existante (liste les UE externes de cette formation),
ou sinon spécifier titre, acronyme, type, ECTS ou sinon spécifier titre, acronyme, type, ECTS
@ -221,7 +218,6 @@ def external_ue_create_form(formsemestre_id, etudid, REQUEST=None):
H = [ H = [
html_sco_header.html_sem_header( html_sco_header.html_sem_header(
REQUEST,
"Ajout d'une UE externe pour %(nomprenom)s" % etud, "Ajout d'une UE externe pour %(nomprenom)s" % etud,
sem, sem,
javascripts=["js/sco_ue_external.js"], 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" default_label = "Aucune UE externe existante"
tf = TrivialFormulator( tf = TrivialFormulator(
REQUEST.URL0, request.base_url,
REQUEST.form, scu.get_request_args(),
( (
("formsemestre_id", {"input_type": "hidden"}), ("formsemestre_id", {"input_type": "hidden"}),
("etudid", {"input_type": "hidden"}), ("etudid", {"input_type": "hidden"}),
@ -365,6 +361,5 @@ def external_ue_create_form(formsemestre_id, etudid, REQUEST=None):
moduleimpl_id, moduleimpl_id,
formsemestre_id, formsemestre_id,
{etudid: note_value}, {etudid: note_value},
REQUEST=REQUEST,
) )
return flask.redirect(bull_url + "&head_message=Ajout%20effectué") return flask.redirect(bull_url + "&head_message=Ajout%20effectué")

View File

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

View File

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

View File

@ -39,21 +39,16 @@ import os
import pydot import pydot
import re import re
import requests import requests
import six import _thread
import six.moves._thread
import sys
import time import time
import traceback
import types
import unicodedata import unicodedata
import urllib import urllib
from xml.etree import ElementTree from urllib.parse import urlparse, parse_qsl, urlunparse, urlencode
from flask import g, current_app
from PIL import Image as PILImage 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 config import Config
from app import log from app import log
@ -217,7 +212,7 @@ def group_by_key(d, key):
# ----- Global lock for critical sections (except notes_tables caches) # ----- 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 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") SCODOC_VERSION_DIR = os.path.join(SCODOC_CFG_DIR, "version")
# ----- Repertoire tmp : /opt/scodoc-data/tmp # ----- Repertoire tmp : /opt/scodoc-data/tmp
SCO_TMP_DIR = os.path.join(Config.SCODOC_VAR_DIR, "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) os.mkdir(SCO_TMP_DIR, 0o755)
# ----- Les logos: /opt/scodoc-data/config/logos # ----- Les logos: /opt/scodoc-data/config/logos
SCODOC_LOGOS_DIR = os.path.join(SCODOC_CFG_DIR, "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): # Adresse pour l'envoi des dumps (pour assistance technnique):
# ne pas changer (ou vous perdez le support) # 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 = "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_FIELDSEP = ";"
CSV_LINESEP = "\n" CSV_LINESEP = "\n"
CSV_MIMETYPE = "text/comma-separated-values" 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_MIMETYPE = "application/vnd.ms-excel"
XLS_SUFFIX = ".xls"
XLSX_MIMETYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" XLSX_MIMETYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
XLSX_SUFFIX = ".xlsx" XLSX_SUFFIX = ".xlsx"
PDF_MIMETYPE = "application/pdf"
XML_MIMETYPE = "text/xml" 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 # Admissions des étudiants
# Différents types de voies d'admission: # Différents types de voies d'admission:
@ -388,6 +406,18 @@ def unescape_html(s):
return 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) # test if obj is iterable (but not a string)
isiterable = lambda obj: getattr(obj, "__iter__", False) isiterable = lambda obj: getattr(obj, "__iter__", False)
@ -445,6 +475,22 @@ def suppress_accents(s):
return s # may be int 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): def sanitize_string(s):
"""s is an ordinary string, encoding given by SCO_ENCODING" """s is an ordinary string, encoding given by SCO_ENCODING"
suppress accents and chars interpreted in XML suppress accents and chars interpreted in XML
@ -457,14 +503,17 @@ def sanitize_string(s):
return suppress_accents(s.translate(trans)).replace(" ", "_").replace("\t", "_") return suppress_accents(s.translate(trans)).replace(" ", "_").replace("\t", "_")
_BAD_FILENAME_CHARS = str.maketrans("", "", ":/\\") _BAD_FILENAME_CHARS = str.maketrans("", "", ":/\\&[]*?'")
def make_filename(name): def make_filename(name):
"""Try to convert name to a reasonable filename """Try to convert name to a reasonable filename
without spaces, (back)slashes, : and without accents 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 = ( VALID_CARS = (
@ -490,30 +539,21 @@ def is_valid_filename(filename):
return VALID_EXP.match(filename) return VALID_EXP.match(filename)
def sendCSVFile(REQUEST, data, filename): def bul_filename(sem, etud, format):
"""publication fichier. """Build a filename for this bulletin"""
(on ne doit rien avoir émis avant, car ici sont générés les entetes) dt = time.strftime("%Y-%m-%d")
""" filename = f"bul-{sem['titre_num']}-{dt}-{etud['nom']}.{format}"
filename = ( filename = make_filename(filename)
unescape_html(suppress_accents(filename)).replace("&", "").replace(" ", "_") return filename
)
REQUEST.RESPONSE.setHeader("content-type", CSV_MIMETYPE)
REQUEST.RESPONSE.setHeader(
"content-disposition", 'attachment; filename="%s"' % filename
)
return data
def sendPDFFile(REQUEST, data, filename): def sendCSVFile(data, filename): # DEPRECATED utiliser send_file
filename = ( """publication fichier CSV."""
unescape_html(suppress_accents(filename)).replace("&", "").replace(" ", "_") return send_file(data, filename=filename, mime=CSV_MIMETYPE, attached=True)
)
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", PDF_MIMETYPE) def sendPDFFile(data, filename): # DEPRECATED utiliser send_file
REQUEST.RESPONSE.setHeader( return send_file(data, filename=filename, mime=PDF_MIMETYPE, attached=True)
"content-disposition", 'attachment; filename="%s"' % filename
)
return data
class ScoDocJSONEncoder(json.JSONEncoder): class ScoDocJSONEncoder(json.JSONEncoder):
@ -526,38 +566,88 @@ class ScoDocJSONEncoder(json.JSONEncoder):
return json.JSONEncoder.default(self, o) return json.JSONEncoder.default(self, o)
def sendJSON(REQUEST, data): def sendJSON(data, attached=False):
js = json.dumps(data, indent=1, cls=ScoDocJSONEncoder) js = json.dumps(data, indent=1, cls=ScoDocJSONEncoder)
if REQUEST: return send_file(
REQUEST.RESPONSE.setHeader("content-type", JSON_MIMETYPE) js, filename="sco_data.json", mime=JSON_MIMETYPE, attached=attached
return js )
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: if type(data) != list:
data = [data] # always list-of-dicts data = [data] # always list-of-dicts
if force_outer_xml_tag: if force_outer_xml_tag:
data = [{tagname: data}] data = [{tagname: data}]
tagname += "_list" tagname += "_list"
doc = sco_xml.simple_dictlist2xml(data, tagname=tagname) doc = sco_xml.simple_dictlist2xml(data, tagname=tagname)
if REQUEST: return send_file(doc, filename="sco_data.xml", mime=XML_MIMETYPE, attached=attached)
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
return doc
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"): if (format is None) or (format == "html"):
return data return data
elif format == "xml": # name is outer tagname elif format == "xml": # name is outer tagname
return sendXML( 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": elif format == "json":
return sendJSON(REQUEST, data) return sendJSON(data, attached=attached)
else: else:
raise ValueError("invalid format: %s" % format) 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(): def get_scodoc_version():
"return a string identifying ScoDoc version" "return a string identifying ScoDoc version"
return sco_version.SCOVERSION return sco_version.SCOVERSION
@ -759,45 +849,6 @@ def AnneeScolaire(sco_year=None):
return year 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( def confirm_dialog(
message="<p>Confirmer ?</p>", message="<p>Confirmer ?</p>",
OK="OK", OK="OK",
@ -823,10 +874,12 @@ def confirm_dialog(
action = f'action="{dest_url}"' action = f'action="{dest_url}"'
H = [ H = [
f"""<form {action} method="post">""", f"""<form {action} method="POST">
message, {message}
"""<input type="submit" value="%s"/>""" % OK, """,
] ]
if OK or not cancel_url:
H.append(f'<input type="submit" value="{OK}"/>')
if cancel_url: if cancel_url:
H.append( H.append(
"""<input type ="button" value="%s" """<input type ="button" value="%s"

View File

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

View File

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