forked from ScoDoc/ScoDoc
Merge branch 'master' into clean
This commit is contained in:
commit
37484b7fc9
@ -117,6 +117,7 @@ def create_app(config_class=DevConfig):
|
|||||||
from app.views import notes_bp
|
from app.views import notes_bp
|
||||||
from app.views import users_bp
|
from app.views import users_bp
|
||||||
from app.views import absences_bp
|
from app.views import absences_bp
|
||||||
|
from app.api import bp as api_bp
|
||||||
|
|
||||||
# https://scodoc.fr/ScoDoc
|
# https://scodoc.fr/ScoDoc
|
||||||
app.register_blueprint(scodoc_bp)
|
app.register_blueprint(scodoc_bp)
|
||||||
@ -130,6 +131,7 @@ def create_app(config_class=DevConfig):
|
|||||||
app.register_blueprint(
|
app.register_blueprint(
|
||||||
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")
|
||||||
scodoc_exc_formatter = RequestFormatter(
|
scodoc_exc_formatter = RequestFormatter(
|
||||||
"[%(asctime)s] %(remote_addr)s requested %(url)s\n"
|
"[%(asctime)s] %(remote_addr)s requested %(url)s\n"
|
||||||
"%(levelname)s in %(module)s: %(message)s"
|
"%(levelname)s in %(module)s: %(message)s"
|
||||||
@ -190,9 +192,7 @@ def create_app(config_class=DevConfig):
|
|||||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
|
||||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
|
||||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC)
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC)
|
||||||
app.logger.info(
|
|
||||||
f"registered bulletin classes {[ k for k in sco_bulletins_generator.BULLETIN_CLASSES ]}"
|
|
||||||
)
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
8
app/api/__init__.py
Normal file
8
app/api/__init__.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
"""api.__init__
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint("api", __name__)
|
||||||
|
|
||||||
|
from app.api import sco_api
|
53
app/api/auth.py
Normal file
53
app/api/auth.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# -*- coding: UTF-8 -*
|
||||||
|
# Authentication code borrowed from Miguel Grinberg's Mega Tutorial
|
||||||
|
# (see https://github.com/miguelgrinberg/microblog)
|
||||||
|
|
||||||
|
# Under The MIT License (MIT)
|
||||||
|
|
||||||
|
# Copyright (c) 2017 Miguel Grinberg
|
||||||
|
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
# this software and associated documentation files (the "Software"), to deal in
|
||||||
|
# the Software without restriction, including without limitation the rights to
|
||||||
|
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
# the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
# subject to the following conditions:
|
||||||
|
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth
|
||||||
|
from app.auth.models import User
|
||||||
|
from app.api.errors import error_response
|
||||||
|
|
||||||
|
basic_auth = HTTPBasicAuth()
|
||||||
|
token_auth = HTTPTokenAuth()
|
||||||
|
|
||||||
|
|
||||||
|
@basic_auth.verify_password
|
||||||
|
def verify_password(username, password):
|
||||||
|
user = User.query.filter_by(username=username).first()
|
||||||
|
if user and user.check_password(password):
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@basic_auth.error_handler
|
||||||
|
def basic_auth_error(status):
|
||||||
|
return error_response(status)
|
||||||
|
|
||||||
|
|
||||||
|
@token_auth.verify_token
|
||||||
|
def verify_token(token):
|
||||||
|
return User.check_token(token) if token else None
|
||||||
|
|
||||||
|
|
||||||
|
@token_auth.error_handler
|
||||||
|
def token_auth_error(status):
|
||||||
|
return error_response(status)
|
37
app/api/errors.py
Normal file
37
app/api/errors.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Authentication code borrowed from Miguel Grinberg's Mega Tutorial
|
||||||
|
# (see https://github.com/miguelgrinberg/microblog)
|
||||||
|
|
||||||
|
# Under The MIT License (MIT)
|
||||||
|
|
||||||
|
# Copyright (c) 2017 Miguel Grinberg
|
||||||
|
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
# this software and associated documentation files (the "Software"), to deal in
|
||||||
|
# the Software without restriction, including without limitation the rights to
|
||||||
|
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
# the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
# subject to the following conditions:
|
||||||
|
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.from flask import jsonify
|
||||||
|
from werkzeug.http import HTTP_STATUS_CODES
|
||||||
|
|
||||||
|
|
||||||
|
def error_response(status_code, message=None):
|
||||||
|
payload = {"error": HTTP_STATUS_CODES.get(status_code, "Unknown error")}
|
||||||
|
if message:
|
||||||
|
payload["message"] = message
|
||||||
|
response = jsonify(payload)
|
||||||
|
response.status_code = status_code
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def bad_request(message):
|
||||||
|
return error_response(400, message)
|
56
app/api/sco_api.py
Normal file
56
app/api/sco_api.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gestion scolarite IUT
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""API ScoDoc 9
|
||||||
|
"""
|
||||||
|
# PAS ENCORE IMPLEMENTEE, juste un essai
|
||||||
|
# Pour P. Bouron, il faudrait en priorité l'équivalent de
|
||||||
|
# Scolarite/Notes/do_moduleimpl_withmodule_list
|
||||||
|
# Scolarite/Notes/evaluation_create
|
||||||
|
# Scolarite/Notes/evaluation_delete
|
||||||
|
# Scolarite/Notes/formation_list
|
||||||
|
# Scolarite/Notes/formsemestre_list
|
||||||
|
# Scolarite/Notes/formsemestre_partition_list
|
||||||
|
# Scolarite/Notes/groups_view
|
||||||
|
# Scolarite/Notes/moduleimpl_status
|
||||||
|
# Scolarite/setGroups
|
||||||
|
|
||||||
|
from flask import jsonify, request, url_for, abort
|
||||||
|
from app import db
|
||||||
|
from app.api import bp
|
||||||
|
from app.api.auth import token_auth
|
||||||
|
from app.api.errors import bad_request
|
||||||
|
|
||||||
|
from app import models
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/ScoDoc/api/list_depts", methods=["GET"])
|
||||||
|
@token_auth.login_required
|
||||||
|
def list_depts():
|
||||||
|
depts = models.Departement.query.filter_by(visible=True).all()
|
||||||
|
data = {"items": [d.to_dict() for d in depts]}
|
||||||
|
return jsonify(data)
|
20
app/api/tokens.py
Normal file
20
app/api/tokens.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from flask import jsonify
|
||||||
|
from app import db
|
||||||
|
from app.api import bp
|
||||||
|
from app.api.auth import basic_auth, token_auth
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/tokens", methods=["POST"])
|
||||||
|
@basic_auth.login_required
|
||||||
|
def get_token():
|
||||||
|
token = basic_auth.current_user().get_token()
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify({"token": token})
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/tokens", methods=["DELETE"])
|
||||||
|
@token_auth.login_required
|
||||||
|
def revoke_token():
|
||||||
|
token_auth.current_user().revoke_token()
|
||||||
|
db.session.commit()
|
||||||
|
return "", 204
|
@ -37,9 +37,11 @@ def login():
|
|||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
user = User.query.filter_by(user_name=form.user_name.data).first()
|
user = User.query.filter_by(user_name=form.user_name.data).first()
|
||||||
if user is None or not user.check_password(form.password.data):
|
if user is None or not user.check_password(form.password.data):
|
||||||
|
current_app.logger.info("login: invalid (%s)", form.user_name.data)
|
||||||
flash(_("Invalid user name or password"))
|
flash(_("Invalid user name or password"))
|
||||||
return redirect(url_for("auth.login"))
|
return redirect(url_for("auth.login"))
|
||||||
login_user(user, remember=form.remember_me.data)
|
login_user(user, remember=form.remember_me.data)
|
||||||
|
current_app.logger.info("login: success (%s)", form.user_name.data)
|
||||||
next_page = request.args.get("next")
|
next_page = request.args.get("next")
|
||||||
if not next_page or url_parse(next_page).netloc != "":
|
if not next_page or url_parse(next_page).netloc != "":
|
||||||
next_page = url_for("scodoc.index")
|
next_page = url_for("scodoc.index")
|
||||||
|
@ -16,8 +16,10 @@ from flask import request
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
import flask_login
|
||||||
|
|
||||||
import app
|
import app
|
||||||
|
from app.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
class ZUser(object):
|
class ZUser(object):
|
||||||
@ -141,6 +143,48 @@ def permission_required(permission):
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def permission_required_compat_scodoc7(permission):
|
||||||
|
"""Décorateur pour les fonctions utilisée comme API dans ScoDoc 7
|
||||||
|
Comme @permission_required mais autorise de passer directement
|
||||||
|
les informations d'auth en paramètres:
|
||||||
|
__ac_name, __ac_password
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
# current_app.logger.warning("PERMISSION; kwargs=%s" % str(kwargs))
|
||||||
|
# cherche les paramètre d'auth:
|
||||||
|
auth_ok = False
|
||||||
|
if request.method == "GET":
|
||||||
|
user_name = request.args.get("__ac_name")
|
||||||
|
user_password = request.args.get("__ac_password")
|
||||||
|
elif request.method == "POST":
|
||||||
|
user_name = request.form.get("__ac_name")
|
||||||
|
user_password = request.form.get("__ac_password")
|
||||||
|
else:
|
||||||
|
abort(405) # method not allowed
|
||||||
|
if user_name and user_password:
|
||||||
|
u = User.query.filter_by(user_name=user_name).first()
|
||||||
|
if u and u.check_password(user_password):
|
||||||
|
auth_ok = True
|
||||||
|
flask_login.login_user(u)
|
||||||
|
|
||||||
|
# reprend le chemin classique:
|
||||||
|
scodoc_dept = getattr(g, "scodoc_dept", None)
|
||||||
|
|
||||||
|
if not current_user.has_permission(permission, scodoc_dept):
|
||||||
|
abort(403)
|
||||||
|
if auth_ok:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
return login_required(f)(*args, **kwargs)
|
||||||
|
|
||||||
|
return decorated_function
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def admin_required(f):
|
def admin_required(f):
|
||||||
from app.auth.models import Permission
|
from app.auth.models import Permission
|
||||||
|
|
||||||
|
@ -34,3 +34,13 @@ class Departement(db.Model):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Departement {self.acronym}>"
|
return f"<Departement {self.acronym}>"
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
data = {
|
||||||
|
"id": self.id,
|
||||||
|
"acronym": self.acronym,
|
||||||
|
"description": self.description,
|
||||||
|
"visible": self.visible,
|
||||||
|
"date_creation": self.date_creation,
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
@ -325,10 +325,15 @@ class NotesSemSet(db.Model):
|
|||||||
sem_id = db.Column(db.Integer, nullable=True, default=None)
|
sem_id = db.Column(db.Integer, nullable=True, default=None)
|
||||||
|
|
||||||
|
|
||||||
# Association:
|
# Association: many to many
|
||||||
notes_semset_formsemestre = db.Table(
|
notes_semset_formsemestre = db.Table(
|
||||||
"notes_semset_formsemestre",
|
"notes_semset_formsemestre",
|
||||||
db.Column("formsemestre_id", db.Integer, db.ForeignKey("notes_formsemestre.id")),
|
db.Column("formsemestre_id", db.Integer, db.ForeignKey("notes_formsemestre.id")),
|
||||||
db.Column("semset_id", db.Integer, db.ForeignKey("notes_semset.id")),
|
db.Column(
|
||||||
|
"semset_id",
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("notes_semset.id", ondelete="CASCADE"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
db.UniqueConstraint("formsemestre_id", "semset_id"),
|
db.UniqueConstraint("formsemestre_id", "semset_id"),
|
||||||
)
|
)
|
||||||
|
@ -33,6 +33,17 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
value = db.Column(db.Text())
|
value = db.Column(db.Text())
|
||||||
|
|
||||||
BONUS_SPORT = "bonus_sport_func_name"
|
BONUS_SPORT = "bonus_sport_func_name"
|
||||||
|
NAMES = {
|
||||||
|
BONUS_SPORT: str,
|
||||||
|
"always_require_ine": bool,
|
||||||
|
"SCOLAR_FONT": str,
|
||||||
|
"SCOLAR_FONT_SIZE": str,
|
||||||
|
"SCOLAR_FONT_SIZE_FOOT": str,
|
||||||
|
"INSTITUTION_NAME": str,
|
||||||
|
"INSTITUTION_ADDRESS": str,
|
||||||
|
"INSTITUTION_CITY": str,
|
||||||
|
"DEFAULT_PDF_FOOTER_TEMPLATE": str,
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -41,6 +52,13 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__}('{self.name}', '{self.value}')>"
|
return f"<{self.__class__.__name__}('{self.name}', '{self.value}')>"
|
||||||
|
|
||||||
|
def get_dict(self) -> dict:
|
||||||
|
"Returns all data as a dict name = value"
|
||||||
|
return {
|
||||||
|
c.name: self.NAMES.get(c.name, lambda x: x)(c.value)
|
||||||
|
for c in ScoDocSiteConfig.query.all()
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_bonus_sport_func(cls, func_name):
|
def set_bonus_sport_func(cls, func_name):
|
||||||
"""Record bonus_sport config.
|
"""Record bonus_sport config.
|
||||||
|
@ -83,22 +83,19 @@ def sidebar():
|
|||||||
from app.scodoc import sco_abs
|
from app.scodoc import sco_abs
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
|
|
||||||
params = {
|
params = {}
|
||||||
"ScoURL": scu.ScoURL(),
|
|
||||||
"SCO_USER_MANUAL": scu.SCO_USER_MANUAL,
|
|
||||||
}
|
|
||||||
|
|
||||||
H = ['<div class="sidebar">', sidebar_common()]
|
H = [
|
||||||
|
f"""<div class="sidebar">
|
||||||
H.append(
|
{ sidebar_common() }
|
||||||
"""<div class="box-chercheetud">Chercher étudiant:<br/>
|
<div class="box-chercheetud">Chercher étudiant:<br/>
|
||||||
<form method="get" id="form-chercheetud" action="%(ScoURL)s/search_etud_in_dept">
|
<form method="get" id="form-chercheetud"
|
||||||
|
action="{ url_for('scolar.search_etud_in_dept', scodoc_dept=g.scodoc_dept) }">
|
||||||
<div><input type="text" size="12" id="in-expnom" name="expnom" spellcheck="false"></input></div>
|
<div><input type="text" size="12" id="in-expnom" name="expnom" spellcheck="false"></input></div>
|
||||||
</form></div>
|
</form></div>
|
||||||
<div class="etud-insidebar">
|
<div class="etud-insidebar">
|
||||||
"""
|
"""
|
||||||
% params
|
]
|
||||||
)
|
|
||||||
# ---- Il y-a-t-il un etudiant selectionné ?
|
# ---- Il y-a-t-il un etudiant selectionné ?
|
||||||
etudid = None
|
etudid = None
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
@ -121,59 +118,50 @@ def sidebar():
|
|||||||
% params
|
% params
|
||||||
)
|
)
|
||||||
if etud["cursem"]:
|
if etud["cursem"]:
|
||||||
params["nbabs"], params["nbabsjust"] = sco_abs.get_abs_count(
|
cur_sem = etud["cursem"]
|
||||||
etudid, etud["cursem"]
|
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, cur_sem)
|
||||||
)
|
nbabsnj = nbabs - nbabsjust
|
||||||
params["nbabsnj"] = params["nbabs"] - params["nbabsjust"]
|
|
||||||
params["date_debut"] = etud["cursem"]["date_debut"]
|
|
||||||
params["date_fin"] = etud["cursem"]["date_fin"]
|
|
||||||
H.append(
|
H.append(
|
||||||
"""<span title="absences du %(date_debut)s au %(date_fin)s">(1/2 j.)<br/>%(nbabsjust)s J., %(nbabsnj)s N.J.</span>"""
|
f"""<span title="absences du { cur_sem["date_debut"] } au { cur_sem["date_fin"] }">(1/2 j.)
|
||||||
% params
|
<br/>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
|
||||||
)
|
)
|
||||||
|
|
||||||
H.append("<ul>")
|
H.append("<ul>")
|
||||||
if current_user.has_permission(Permission.ScoAbsChange):
|
if current_user.has_permission(Permission.ScoAbsChange):
|
||||||
H.append(
|
H.append(
|
||||||
|
f"""
|
||||||
|
<li><a href="{ url_for('absences.SignaleAbsenceEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Ajouter</a></li>
|
||||||
|
<li><a href="{ url_for('absences.JustifAbsenceEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Justifier</a></li>
|
||||||
|
<li><a href="{ url_for('absences.AnnuleAbsenceEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Supprimer</a></li>
|
||||||
"""
|
"""
|
||||||
<li> <a href="%(ScoURL)s/Absences/SignaleAbsenceEtud?etudid=%(etudid)s">Ajouter</a></li>
|
|
||||||
<li> <a href="%(ScoURL)s/Absences/JustifAbsenceEtud?etudid=%(etudid)s">Justifier</a></li>
|
|
||||||
<li> <a href="%(ScoURL)s/Absences/AnnuleAbsenceEtud?etudid=%(etudid)s">Supprimer</a></li>
|
|
||||||
"""
|
|
||||||
% params
|
|
||||||
)
|
)
|
||||||
if sco_preferences.get_preference("handle_billets_abs"):
|
if sco_preferences.get_preference("handle_billets_abs"):
|
||||||
H.append(
|
H.append(
|
||||||
"""<li> <a href="%(ScoURL)s/Absences/listeBilletsEtud?etudid=%(etudid)s">Billets</a></li>"""
|
f"""<li><a href="{ url_for('absences.listeBilletsEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Billets</a></li>"""
|
||||||
% params
|
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
"""
|
f"""
|
||||||
<li> <a href="%(ScoURL)s/Absences/CalAbs?etudid=%(etudid)s">Calendrier</a></li>
|
<li><a href="{ url_for('absences.CalAbs', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Calendrier</a></li>
|
||||||
<li> <a href="%(ScoURL)s/Absences/ListeAbsEtud?etudid=%(etudid)s">Liste</a></li>
|
<li><a href="{ url_for('absences.ListeAbsEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Liste</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
"""
|
"""
|
||||||
% params
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
pass # H.append("(pas d'étudiant en cours)")
|
pass # H.append("(pas d'étudiant en cours)")
|
||||||
# ---------
|
# ---------
|
||||||
H.append("</div>") # /etud-insidebar
|
H.append("</div>") # /etud-insidebar
|
||||||
# Logo
|
# Logo
|
||||||
scologo_img = scu.icontag("scologo_img")
|
|
||||||
H.append(
|
H.append(
|
||||||
'<div class="logo-insidebar"><div class="logo-logo">%s</div>' % scologo_img
|
f"""<div class="logo-insidebar">
|
||||||
)
|
<div class="sidebar-bottom"><a href="{ url_for( 'scolar.about', scodoc_dept=g.scodoc_dept ) }" class="sidebar">À propos</a><br/>
|
||||||
H.append(
|
<a href="{ scu.SCO_USER_MANUAL }" target="_blank" class="sidebar">Aide</a>
|
||||||
"""<div class="logo-logo"><a href="%(ScoURL)s/about" class="sidebar">A propos</a><br/>
|
|
||||||
<a href="%(SCO_USER_MANUAL)s" class="sidebar">Aide</a><br/>
|
|
||||||
</div></div>
|
</div></div>
|
||||||
|
<div class="logo-logo"><a href= { url_for( 'scolar.about', scodoc_dept=g.scodoc_dept ) }
|
||||||
</div> <!-- end of sidebar -->
|
">{ scu.icontag("scologo_img", no_size=True) }</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end of sidebar -->
|
||||||
"""
|
"""
|
||||||
% params
|
)
|
||||||
) # '
|
|
||||||
#
|
|
||||||
return "".join(H)
|
return "".join(H)
|
||||||
|
|
||||||
|
|
||||||
|
@ -287,7 +287,7 @@ class EditableTable(object):
|
|||||||
input_formators={},
|
input_formators={},
|
||||||
aux_tables=[],
|
aux_tables=[],
|
||||||
convert_null_outputs_to_empty=True,
|
convert_null_outputs_to_empty=True,
|
||||||
html_quote=True,
|
html_quote=False, # changed in 9.0.10
|
||||||
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
|
||||||
@ -321,8 +321,10 @@ class EditableTable(object):
|
|||||||
del vals["id"]
|
del vals["id"]
|
||||||
if self.filter_dept:
|
if self.filter_dept:
|
||||||
vals["dept_id"] = g.scodoc_dept_id
|
vals["dept_id"] = g.scodoc_dept_id
|
||||||
if self.html_quote:
|
if (
|
||||||
quote_dict(vals) # quote all HTML markup
|
self.html_quote
|
||||||
|
): # quote all HTML markup (une bien mauvaise idée venue des ages obscurs)
|
||||||
|
quote_dict(vals)
|
||||||
# format value
|
# format value
|
||||||
for title in vals:
|
for title in vals:
|
||||||
if title in self.input_formators:
|
if title in self.input_formators:
|
||||||
|
@ -163,7 +163,7 @@ class TableTag(object):
|
|||||||
# *****************************************************************************************************************
|
# *****************************************************************************************************************
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------
|
||||||
def add_moyennesTag(self, tag, listMoyEtCoeff):
|
def add_moyennesTag(self, tag, listMoyEtCoeff) -> bool:
|
||||||
"""
|
"""
|
||||||
Mémorise les moyennes, les coeffs de pondération et les etudid dans resultats
|
Mémorise les moyennes, les coeffs de pondération et les etudid dans resultats
|
||||||
avec calcul du rang
|
avec calcul du rang
|
||||||
@ -181,7 +181,9 @@ class TableTag(object):
|
|||||||
lesMoyennesTriees = sorted(
|
lesMoyennesTriees = sorted(
|
||||||
listMoyEtCoeff,
|
listMoyEtCoeff,
|
||||||
reverse=True,
|
reverse=True,
|
||||||
key=lambda col: col[0] or 0, # remplace les None par des zéros
|
key=lambda col: col[0]
|
||||||
|
if isinstance(col[0], float)
|
||||||
|
else 0, # remplace les None et autres chaines par des zéros
|
||||||
) # triées
|
) # triées
|
||||||
self.rangs[tag] = notes_table.comp_ranks(lesMoyennesTriees) # les rangs
|
self.rangs[tag] = notes_table.comp_ranks(lesMoyennesTriees) # les rangs
|
||||||
|
|
||||||
|
@ -313,7 +313,7 @@ def do_formsemestre_archive(
|
|||||||
# tous les inscrits du semestre
|
# tous les inscrits du semestre
|
||||||
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
group_ids, formsemestre_id=formsemestre_id, REQUEST=REQUEST
|
group_ids, formsemestre_id=formsemestre_id
|
||||||
)
|
)
|
||||||
groups_filename = "-" + groups_infos.groups_filename
|
groups_filename = "-" + groups_infos.groups_filename
|
||||||
etudids = [m["etudid"] for m in groups_infos.members]
|
etudids = [m["etudid"] for m in groups_infos.members]
|
||||||
@ -403,7 +403,7 @@ def formsemestre_archive(REQUEST, formsemestre_id, group_ids=[]):
|
|||||||
# tous les inscrits du semestre
|
# tous les inscrits du semestre
|
||||||
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
group_ids, formsemestre_id=formsemestre_id, REQUEST=REQUEST
|
group_ids, formsemestre_id=formsemestre_id
|
||||||
)
|
)
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
|
@ -183,7 +183,7 @@ def _sem_table(sems):
|
|||||||
"""Affiche liste des semestres, utilisée pour semestres en cours"""
|
"""Affiche liste des semestres, utilisée pour semestres en cours"""
|
||||||
tmpl = """<tr class="%(trclass)s">%(tmpcode)s
|
tmpl = """<tr class="%(trclass)s">%(tmpcode)s
|
||||||
<td class="semicon">%(lockimg)s <a href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td>
|
<td class="semicon">%(lockimg)s <a href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td>
|
||||||
<td class="datesem">%(mois_debut)s</td><td class="datesem"><a title="%(session_id)s">-</a> %(mois_fin)s</td>
|
<td class="datesem">%(mois_debut)s <a title="%(session_id)s">-</a> %(mois_fin)s</td>
|
||||||
<td><a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
|
<td><a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
|
||||||
<span class="respsem">(%(responsable_name)s)</span>
|
<span class="respsem">(%(responsable_name)s)</span>
|
||||||
</td>
|
</td>
|
||||||
@ -196,7 +196,7 @@ def _sem_table(sems):
|
|||||||
H = ['<table class="listesems">']
|
H = ['<table class="listesems">']
|
||||||
for modalite in modalites:
|
for modalite in modalites:
|
||||||
if len(modalites) > 1:
|
if len(modalites) > 1:
|
||||||
H.append('<tr><th colspan="4">%s</th></tr>' % modalite["titre"])
|
H.append('<tr><th colspan="3">%s</th></tr>' % modalite["titre"])
|
||||||
|
|
||||||
if sems_by_mod[modalite["modalite"]]:
|
if sems_by_mod[modalite["modalite"]]:
|
||||||
cur_idx = sems_by_mod[modalite["modalite"]][0]["semestre_id"]
|
cur_idx = sems_by_mod[modalite["modalite"]][0]["semestre_id"]
|
||||||
|
@ -85,6 +85,9 @@ def send_excel_file(request, data, filename, mime=scu.XLSX_MIMETYPE):
|
|||||||
|
|
||||||
|
|
||||||
def xldate_as_datetime(xldate, datemode=0):
|
def xldate_as_datetime(xldate, datemode=0):
|
||||||
|
"""Conversion d'une date Excel en date
|
||||||
|
Peut lever une ValueError
|
||||||
|
"""
|
||||||
return openpyxl.utils.datetime.from_ISO8601(xldate)
|
return openpyxl.utils.datetime.from_ISO8601(xldate)
|
||||||
|
|
||||||
|
|
||||||
|
@ -120,10 +120,11 @@ def search_etud_in_dept(expnom=""):
|
|||||||
if etudid is not None:
|
if etudid is not None:
|
||||||
etuds = sco_etud.get_etud_info(filled=True, etudid=expnom)
|
etuds = sco_etud.get_etud_info(filled=True, etudid=expnom)
|
||||||
if (etudid is None) or len(etuds) != 1:
|
if (etudid is None) or len(etuds) != 1:
|
||||||
if scu.is_valid_code_nip(expnom):
|
expnom_str = str(expnom)
|
||||||
etuds = search_etuds_infos(code_nip=expnom)
|
if scu.is_valid_code_nip(expnom_str):
|
||||||
|
etuds = search_etuds_infos(code_nip=expnom_str)
|
||||||
else:
|
else:
|
||||||
etuds = search_etuds_infos(expnom=expnom)
|
etuds = search_etuds_infos(expnom=expnom_str)
|
||||||
else:
|
else:
|
||||||
etuds = [] # si expnom est trop court, n'affiche rien
|
etuds = [] # si expnom est trop court, n'affiche rien
|
||||||
|
|
||||||
@ -151,7 +152,7 @@ def search_etud_in_dept(expnom=""):
|
|||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
page_title="Recherche d'un étudiant",
|
page_title="Recherche d'un étudiant",
|
||||||
no_side_bar=True,
|
no_side_bar=False,
|
||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
javascripts=["js/etud_info.js"],
|
javascripts=["js/etud_info.js"],
|
||||||
)
|
)
|
||||||
@ -250,10 +251,12 @@ def search_etud_by_name(term: str) -> list:
|
|||||||
r = ndb.SimpleDictFetch(
|
r = ndb.SimpleDictFetch(
|
||||||
"""SELECT nom, prenom, code_nip
|
"""SELECT nom, prenom, code_nip
|
||||||
FROM identite
|
FROM identite
|
||||||
WHERE code_nip
|
WHERE
|
||||||
LIKE %(beginning)s ORDER BY nom
|
dept_id = %(dept_id)s
|
||||||
|
AND code_nip LIKE %(beginning)s
|
||||||
|
ORDER BY nom
|
||||||
""",
|
""",
|
||||||
{"beginning": term + "%"},
|
{"beginning": term + "%", "dept_id": g.scodoc_dept_id},
|
||||||
)
|
)
|
||||||
data = [
|
data = [
|
||||||
{
|
{
|
||||||
@ -267,10 +270,12 @@ def search_etud_by_name(term: str) -> list:
|
|||||||
r = ndb.SimpleDictFetch(
|
r = ndb.SimpleDictFetch(
|
||||||
"""SELECT id AS etudid, nom, prenom
|
"""SELECT id AS etudid, nom, prenom
|
||||||
FROM identite
|
FROM identite
|
||||||
WHERE nom LIKE %(beginning)s
|
WHERE
|
||||||
|
dept_id = %(dept_id)s
|
||||||
|
AND nom LIKE %(beginning)s
|
||||||
ORDER BY nom
|
ORDER BY nom
|
||||||
""",
|
""",
|
||||||
{"beginning": term + "%"},
|
{"beginning": term + "%", "dept_id": g.scodoc_dept_id},
|
||||||
)
|
)
|
||||||
|
|
||||||
data = [
|
data = [
|
||||||
|
@ -34,12 +34,12 @@
|
|||||||
import collections
|
import collections
|
||||||
import datetime
|
import datetime
|
||||||
import operator
|
import operator
|
||||||
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
|
import urllib
|
||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
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 html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
@ -86,7 +86,6 @@ def groups_view(
|
|||||||
group_ids,
|
group_ids,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
etat=etat,
|
etat=etat,
|
||||||
REQUEST=REQUEST,
|
|
||||||
select_all_when_unspecified=True,
|
select_all_when_unspecified=True,
|
||||||
)
|
)
|
||||||
# Formats spéciaux: download direct
|
# Formats spéciaux: download direct
|
||||||
@ -301,7 +300,6 @@ class DisplayedGroupsInfos(object):
|
|||||||
etat=None,
|
etat=None,
|
||||||
select_all_when_unspecified=False,
|
select_all_when_unspecified=False,
|
||||||
moduleimpl_id=None, # used to find formsemestre when unspecified
|
moduleimpl_id=None, # used to find formsemestre when unspecified
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
if isinstance(group_ids, int):
|
if isinstance(group_ids, int):
|
||||||
if group_ids:
|
if group_ids:
|
||||||
@ -334,7 +332,7 @@ class DisplayedGroupsInfos(object):
|
|||||||
for group_id in group_ids:
|
for group_id in group_ids:
|
||||||
gq.append("group_ids=" + str(group_id))
|
gq.append("group_ids=" + str(group_id))
|
||||||
self.groups_query_args = "&".join(gq)
|
self.groups_query_args = "&".join(gq)
|
||||||
self.base_url = REQUEST.URL0 + "?" + self.groups_query_args
|
self.base_url = request.base_url + "?" + self.groups_query_args
|
||||||
self.group_ids = group_ids
|
self.group_ids = group_ids
|
||||||
self.groups = []
|
self.groups = []
|
||||||
groups_titles = []
|
groups_titles = []
|
||||||
@ -918,7 +916,7 @@ def form_choix_saisie_semaine(groups_infos, REQUEST=None):
|
|||||||
del query_args["head_message"]
|
del query_args["head_message"]
|
||||||
destination = "%s?%s" % (
|
destination = "%s?%s" % (
|
||||||
REQUEST.URL,
|
REQUEST.URL,
|
||||||
six.moves.urllib.parse.urlencode(query_args, True),
|
urllib.parse.urlencode(query_args, True),
|
||||||
)
|
)
|
||||||
destination = destination.replace(
|
destination = destination.replace(
|
||||||
"%", "%%"
|
"%", "%%"
|
||||||
|
@ -157,7 +157,6 @@ def sco_import_generate_excel_sample(
|
|||||||
exclude_cols=[],
|
exclude_cols=[],
|
||||||
extra_cols=[],
|
extra_cols=[],
|
||||||
group_ids=[],
|
group_ids=[],
|
||||||
REQUEST=None,
|
|
||||||
):
|
):
|
||||||
"""Generates an excel document based on format fmt
|
"""Generates an excel document based on format fmt
|
||||||
(format is the result of sco_import_format())
|
(format is the result of sco_import_format())
|
||||||
@ -188,7 +187,7 @@ def sco_import_generate_excel_sample(
|
|||||||
titles += extra_cols
|
titles += extra_cols
|
||||||
titlesStyles += [style] * len(extra_cols)
|
titlesStyles += [style] * len(extra_cols)
|
||||||
if group_ids:
|
if group_ids:
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids, REQUEST=REQUEST)
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
||||||
members = groups_infos.members
|
members = groups_infos.members
|
||||||
log(
|
log(
|
||||||
"sco_import_generate_excel_sample: group_ids=%s %d members"
|
"sco_import_generate_excel_sample: group_ids=%s %d members"
|
||||||
@ -378,8 +377,12 @@ def scolars_import_excel_file(
|
|||||||
# Excel date conversion:
|
# Excel date conversion:
|
||||||
if titleslist[i].lower() == "date_naissance":
|
if titleslist[i].lower() == "date_naissance":
|
||||||
if val:
|
if val:
|
||||||
# if re.match(r"^[0-9]*\.?[0-9]*$", str(val)):
|
try:
|
||||||
val = sco_excel.xldate_as_datetime(val)
|
val = sco_excel.xldate_as_datetime(val)
|
||||||
|
except ValueError:
|
||||||
|
raise ScoValueError(
|
||||||
|
f"date invalide ({val}) sur ligne {linenum}, colonne {titleslist[i]}"
|
||||||
|
)
|
||||||
# INE
|
# INE
|
||||||
if (
|
if (
|
||||||
titleslist[i].lower() == "code_ine"
|
titleslist[i].lower() == "code_ine"
|
||||||
|
95
app/scodoc/sco_logos.py
Normal file
95
app/scodoc/sco_logos.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gestion scolarite IUT
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Gestion des images logos (nouveau ScoDoc 9)
|
||||||
|
|
||||||
|
Les logos sont `logo_header.<ext>` et `logo_footer.<ext>`
|
||||||
|
avec `ext` membre de LOGOS_IMAGES_ALLOWED_TYPES (= jpg, png)
|
||||||
|
|
||||||
|
SCODOC_LOGOS_DIR /opt/scodoc-data/config/logos
|
||||||
|
"""
|
||||||
|
import imghdr
|
||||||
|
import os
|
||||||
|
|
||||||
|
from flask import abort, current_app
|
||||||
|
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
|
def get_logo_filename(logo_type: str, scodoc_dept: str) -> str:
|
||||||
|
"""return full filename for this logo, or "" if not found
|
||||||
|
an existing file with extension.
|
||||||
|
logo_type: "header" or "footer"
|
||||||
|
scodoc-dept: acronym
|
||||||
|
"""
|
||||||
|
# Search logos in dept specific dir (/opt/scodoc-data/config/logos/logos_<dept>),
|
||||||
|
# then in config dir /opt/scodoc-data/config/logos/
|
||||||
|
for image_dir in (
|
||||||
|
scu.SCODOC_LOGOS_DIR + "/logos_" + scodoc_dept,
|
||||||
|
scu.SCODOC_LOGOS_DIR, # global logos
|
||||||
|
):
|
||||||
|
for suffix in scu.LOGOS_IMAGES_ALLOWED_TYPES:
|
||||||
|
filename = os.path.join(image_dir, f"logo_{logo_type}.{suffix}")
|
||||||
|
if os.path.isfile(filename) and os.access(filename, os.R_OK):
|
||||||
|
return filename
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def guess_image_type(stream) -> str:
|
||||||
|
"guess image type from header in stream"
|
||||||
|
header = stream.read(512)
|
||||||
|
stream.seek(0)
|
||||||
|
fmt = imghdr.what(None, header)
|
||||||
|
if not fmt:
|
||||||
|
return None
|
||||||
|
return fmt if fmt != "jpeg" else "jpg"
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_directory_exists(filename):
|
||||||
|
"create enclosing directory if necessary"
|
||||||
|
directory = os.path.split(filename)[0]
|
||||||
|
if not os.path.exists(directory):
|
||||||
|
current_app.logger.info(f"sco_logos creating directory %s", directory)
|
||||||
|
os.mkdir(directory)
|
||||||
|
|
||||||
|
|
||||||
|
def store_image(stream, basename):
|
||||||
|
img_type = guess_image_type(stream)
|
||||||
|
if img_type not in scu.LOGOS_IMAGES_ALLOWED_TYPES:
|
||||||
|
abort(400, "type d'image invalide")
|
||||||
|
filename = basename + "." + img_type
|
||||||
|
_ensure_directory_exists(filename)
|
||||||
|
with open(filename, "wb") as f:
|
||||||
|
f.write(stream.read())
|
||||||
|
current_app.logger.info(f"sco_logos.store_image %s", filename)
|
||||||
|
# erase other formats if they exists
|
||||||
|
for extension in set(scu.LOGOS_IMAGES_ALLOWED_TYPES) - set([img_type]):
|
||||||
|
try:
|
||||||
|
os.unlink(basename + "." + extension)
|
||||||
|
except IOError:
|
||||||
|
pass
|
@ -221,7 +221,6 @@ _moduleimpl_inscriptionEditor = ndb.EditableTable(
|
|||||||
def do_moduleimpl_inscription_create(args, formsemestre_id=None):
|
def do_moduleimpl_inscription_create(args, formsemestre_id=None):
|
||||||
"create a moduleimpl_inscription"
|
"create a moduleimpl_inscription"
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
log("do_moduleimpl_inscription_create: " + str(args))
|
|
||||||
r = _moduleimpl_inscriptionEditor.create(cnx, args)
|
r = _moduleimpl_inscriptionEditor.create(cnx, args)
|
||||||
sco_cache.invalidate_formsemestre(
|
sco_cache.invalidate_formsemestre(
|
||||||
formsemestre_id=formsemestre_id
|
formsemestre_id=formsemestre_id
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
|
|
||||||
En ScoDoc 9, ce n'est pas nécessaire car on est multiptocessus / monothread.
|
En ScoDoc 9, ce n'est pas nécessaire car on est multiptocessus / monothread.
|
||||||
"""
|
"""
|
||||||
|
import html
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import queue
|
import queue
|
||||||
@ -85,7 +86,11 @@ def SU(s):
|
|||||||
# car les "combining accents" ne sont pas traités par ReportLab mais peuvent
|
# car les "combining accents" ne sont pas traités par ReportLab mais peuvent
|
||||||
# nous être envoyés par certains navigateurs ou imports
|
# nous être envoyés par certains navigateurs ou imports
|
||||||
# (on en a dans les bases de données)
|
# (on en a dans les bases de données)
|
||||||
return unicodedata.normalize("NFC", s)
|
s = unicodedata.normalize("NFC", s)
|
||||||
|
# Remplace les entité XML/HTML
|
||||||
|
# reportlab ne les supporte pas non plus.
|
||||||
|
s = html.unescape(s)
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
def _splitPara(txt):
|
def _splitPara(txt):
|
||||||
|
@ -111,7 +111,7 @@ get_base_preferences(formsemestre_id)
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import flask
|
import flask
|
||||||
from flask import g
|
from flask import g, url_for
|
||||||
|
|
||||||
from app.models import Departement
|
from app.models import Departement
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
@ -147,6 +147,52 @@ def get_preference(name, formsemestre_id=None):
|
|||||||
return get_base_preferences().get(formsemestre_id, name)
|
return get_base_preferences().get(formsemestre_id, name)
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_pref_type(p, pref_spec):
|
||||||
|
"""p est une ligne de la bd
|
||||||
|
{'id': , 'dept_id': , 'name': '', 'value': '', 'formsemestre_id': }
|
||||||
|
converti la valeur chane en le type désiré spécifié par pref_spec
|
||||||
|
"""
|
||||||
|
if "type" in pref_spec:
|
||||||
|
typ = pref_spec["type"]
|
||||||
|
if typ == "float":
|
||||||
|
# special case for float values (where NULL means 0)
|
||||||
|
if p["value"]:
|
||||||
|
p["value"] = float(p["value"])
|
||||||
|
else:
|
||||||
|
p["value"] = 0.0
|
||||||
|
else:
|
||||||
|
func = eval(typ)
|
||||||
|
p["value"] = func(p["value"])
|
||||||
|
if pref_spec.get("input_type", None) == "boolcheckbox":
|
||||||
|
# boolcheckbox: la valeur stockée en base est une chaine "0" ou "1"
|
||||||
|
# que l'on ressort en True|False
|
||||||
|
if p["value"]:
|
||||||
|
try:
|
||||||
|
p["value"] = bool(int(p["value"]))
|
||||||
|
except ValueError:
|
||||||
|
log(
|
||||||
|
f"""Warning: invalid value for boolean pref in db: '{p["value"]}'"""
|
||||||
|
)
|
||||||
|
p["value"] = False
|
||||||
|
else:
|
||||||
|
p["value"] = False # NULL (backward compat)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_pref_default_value_from_config(name, pref_spec):
|
||||||
|
"""get default value store in application level config.
|
||||||
|
If not found, use defalut value hardcoded in pref_spec.
|
||||||
|
"""
|
||||||
|
# XXX va changer avec la nouvelle base
|
||||||
|
# search in scu.CONFIG
|
||||||
|
if hasattr(scu.CONFIG, name):
|
||||||
|
value = getattr(scu.CONFIG, name)
|
||||||
|
log("sco_preferences: found default value in config for %s=%s" % (name, value))
|
||||||
|
else:
|
||||||
|
# uses hardcoded default
|
||||||
|
value = pref_spec["initvalue"]
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
PREF_CATEGORIES = (
|
PREF_CATEGORIES = (
|
||||||
# sur page "Paramètres"
|
# sur page "Paramètres"
|
||||||
("general", {}),
|
("general", {}),
|
||||||
@ -472,9 +518,15 @@ class BasePreferences(object):
|
|||||||
--- Ceci est un message de notification automatique issu de ScoDoc ---
|
--- Ceci est un message de notification automatique issu de ScoDoc ---
|
||||||
|
|
||||||
L'étudiant %(nomprenom)s
|
L'étudiant %(nomprenom)s
|
||||||
|
L'étudiant %(nomprenom)s
|
||||||
|
L'étudiant %(nomprenom)s
|
||||||
|
inscrit en %(inscription)s)
|
||||||
|
inscrit en %(inscription)s)
|
||||||
inscrit en %(inscription)s)
|
inscrit en %(inscription)s)
|
||||||
|
|
||||||
a cumulé %(nbabsjust)s absences justifiées
|
a cumulé %(nbabsjust)s absences justifiées
|
||||||
|
a cumulé %(nbabsjust)s absences justifiées
|
||||||
|
a cumulé %(nbabsjust)s absences justifiées
|
||||||
et %(nbabsnonjust)s absences NON justifiées.
|
et %(nbabsnonjust)s absences NON justifiées.
|
||||||
|
|
||||||
Le compte a pu changer depuis cet envoi, voir la fiche sur %(url_ficheetud)s.
|
Le compte a pu changer depuis cet envoi, voir la fiche sur %(url_ficheetud)s.
|
||||||
@ -829,9 +881,13 @@ class BasePreferences(object):
|
|||||||
Vu l'arrêté du 3 août 2005 relatif au diplôme universitaire de technologie et notamment son article 4 et 6;
|
Vu l'arrêté du 3 août 2005 relatif au diplôme universitaire de technologie et notamment son article 4 et 6;
|
||||||
</para>
|
</para>
|
||||||
<para><bullet>-</bullet>
|
<para><bullet>-</bullet>
|
||||||
|
<para><bullet>-</bullet>
|
||||||
|
<para><bullet>-</bullet>
|
||||||
vu l'arrêté n° %(Decnum)s du Président de l'%(UnivName)s;
|
vu l'arrêté n° %(Decnum)s du Président de l'%(UnivName)s;
|
||||||
</para>
|
</para>
|
||||||
<para><bullet>-</bullet>
|
<para><bullet>-</bullet>
|
||||||
|
<para><bullet>-</bullet>
|
||||||
|
<para><bullet>-</bullet>
|
||||||
vu la délibération de la commission %(Type)s en date du %(Date)s présidée par le Chef du département;
|
vu la délibération de la commission %(Type)s en date du %(Date)s présidée par le Chef du département;
|
||||||
""",
|
""",
|
||||||
"title": """Paragraphe d'introduction sur le PV""",
|
"title": """Paragraphe d'introduction sur le PV""",
|
||||||
@ -981,6 +1037,8 @@ class BasePreferences(object):
|
|||||||
<para spaceBefore="10mm" fontSize="14" leftindent="0">
|
<para spaceBefore="10mm" fontSize="14" leftindent="0">
|
||||||
Le jury de %(type_jury_abbrv)s du département %(DeptName)s
|
Le jury de %(type_jury_abbrv)s du département %(DeptName)s
|
||||||
s'est réuni le %(date_jury)s.
|
s'est réuni le %(date_jury)s.
|
||||||
|
s'est réuni le %(date_jury)s.
|
||||||
|
s'est réuni le %(date_jury)s.
|
||||||
</para>
|
</para>
|
||||||
<para fontSize="14" leftindent="0">Les décisions vous concernant sont :
|
<para fontSize="14" leftindent="0">Les décisions vous concernant sont :
|
||||||
</para>
|
</para>
|
||||||
@ -1799,8 +1857,7 @@ class BasePreferences(object):
|
|||||||
def load(self):
|
def load(self):
|
||||||
"""Load all preferences from db"""
|
"""Load all preferences from db"""
|
||||||
log(f"loading preferences for dept_id={self.dept_id}")
|
log(f"loading preferences for dept_id={self.dept_id}")
|
||||||
try:
|
|
||||||
scu.GSL.acquire()
|
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
preflist = self._editor.list(cnx, {"dept_id": self.dept_id})
|
preflist = self._editor.list(cnx, {"dept_id": self.dept_id})
|
||||||
self.prefs = {None: {}} # { formsemestre_id (or None) : { name : value } }
|
self.prefs = {None: {}} # { formsemestre_id (or None) : { name : value } }
|
||||||
@ -1811,38 +1868,11 @@ class BasePreferences(object):
|
|||||||
# Ignore les noms de préférences non utilisés dans le code:
|
# Ignore les noms de préférences non utilisés dans le code:
|
||||||
if p["name"] not in self.prefs_dict:
|
if p["name"] not in self.prefs_dict:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Convert types:
|
# Convert types:
|
||||||
if (
|
if p["name"] in self.prefs_dict:
|
||||||
p["name"] in self.prefs_dict
|
_convert_pref_type(p, self.prefs_dict[p["name"]])
|
||||||
and "type" in self.prefs_dict[p["name"]]
|
|
||||||
):
|
|
||||||
typ = self.prefs_dict[p["name"]]["type"]
|
|
||||||
if typ == "float":
|
|
||||||
# special case for float values (where NULL means 0)
|
|
||||||
if p["value"]:
|
|
||||||
p["value"] = float(p["value"])
|
|
||||||
else:
|
|
||||||
p["value"] = 0.0
|
|
||||||
else:
|
|
||||||
func = eval(typ)
|
|
||||||
p["value"] = func(p["value"])
|
|
||||||
if (
|
|
||||||
p["name"] in self.prefs_dict
|
|
||||||
and self.prefs_dict[p["name"]].get("input_type", None)
|
|
||||||
== "boolcheckbox"
|
|
||||||
):
|
|
||||||
# boolcheckbox: la valeur stockée en base est une chaine "0" ou "1"
|
|
||||||
# que l'on ressort en True|False
|
|
||||||
if p["value"]:
|
|
||||||
try:
|
|
||||||
p["value"] = bool(int(p["value"]))
|
|
||||||
except ValueError:
|
|
||||||
log(
|
|
||||||
f"""Warning: invalid value for boolean pref in db: '{p["value"]}'"""
|
|
||||||
)
|
|
||||||
p["value"] = False
|
|
||||||
else:
|
|
||||||
p["value"] = False # NULL (backward compat)
|
|
||||||
self.prefs[p["formsemestre_id"]][p["name"]] = p["value"]
|
self.prefs[p["formsemestre_id"]][p["name"]] = p["value"]
|
||||||
|
|
||||||
# add defaults for missing prefs
|
# add defaults for missing prefs
|
||||||
@ -1850,17 +1880,7 @@ class BasePreferences(object):
|
|||||||
name = pref[0]
|
name = pref[0]
|
||||||
# search preferences in configuration file
|
# search preferences in configuration file
|
||||||
if name and name[0] != "_" and name not in self.prefs[None]:
|
if name and name[0] != "_" and name not in self.prefs[None]:
|
||||||
# search in scu.CONFIG
|
value = _get_pref_default_value_from_config(name, pref[1])
|
||||||
if hasattr(scu.CONFIG, name):
|
|
||||||
value = getattr(scu.CONFIG, name)
|
|
||||||
log(
|
|
||||||
"sco_preferences: found default value in config for %s=%s"
|
|
||||||
% (name, value)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# uses hardcoded default
|
|
||||||
value = pref[1]["initvalue"]
|
|
||||||
|
|
||||||
self.default[name] = value
|
self.default[name] = value
|
||||||
self.prefs[None][name] = value
|
self.prefs[None][name] = value
|
||||||
log("creating missing preference for %s=%s" % (name, value))
|
log("creating missing preference for %s=%s" % (name, value))
|
||||||
@ -1868,19 +1888,26 @@ class BasePreferences(object):
|
|||||||
self._editor.create(
|
self._editor.create(
|
||||||
cnx, {"dept_id": self.dept_id, "name": name, "value": value}
|
cnx, {"dept_id": self.dept_id, "name": name, "value": value}
|
||||||
)
|
)
|
||||||
finally:
|
|
||||||
scu.GSL.release()
|
|
||||||
|
|
||||||
def get(self, formsemestre_id, name):
|
def get(self, formsemestre_id, name):
|
||||||
"""Returns preference value.
|
"""Returns preference value.
|
||||||
If no value defined for this semestre, returns global value.
|
If global_lookup, when no value defined for this semestre, returns global value.
|
||||||
"""
|
"""
|
||||||
if formsemestre_id in self.prefs and name in self.prefs[formsemestre_id]:
|
params = {
|
||||||
return self.prefs[formsemestre_id][name]
|
"dept_id": self.dept_id,
|
||||||
elif name in self.prefs[None]:
|
"name": name,
|
||||||
return self.prefs[None][name]
|
"formsemestre_id": formsemestre_id,
|
||||||
else:
|
}
|
||||||
|
cnx = ndb.GetDBConnexion()
|
||||||
|
plist = self._editor.list(cnx, params)
|
||||||
|
if not plist:
|
||||||
|
del params["formsemestre_id"]
|
||||||
|
plist = self._editor.list(cnx, params)
|
||||||
|
if not plist:
|
||||||
return self.default[name]
|
return self.default[name]
|
||||||
|
p = plist[0]
|
||||||
|
_convert_pref_type(p, self.prefs_dict[name])
|
||||||
|
return p["value"]
|
||||||
|
|
||||||
def __contains__(self, item):
|
def __contains__(self, item):
|
||||||
return item in self.prefs[None]
|
return item in self.prefs[None]
|
||||||
@ -1890,18 +1917,17 @@ class BasePreferences(object):
|
|||||||
|
|
||||||
def is_global(self, formsemestre_id, name):
|
def is_global(self, formsemestre_id, name):
|
||||||
"True if name if not defined for semestre"
|
"True if name if not defined for semestre"
|
||||||
if (
|
params = {
|
||||||
not (formsemestre_id in self.prefs)
|
"dept_id": self.dept_id,
|
||||||
or not name in self.prefs[formsemestre_id]
|
"name": name,
|
||||||
):
|
"formsemestre_id": formsemestre_id,
|
||||||
return True
|
}
|
||||||
else:
|
cnx = ndb.GetDBConnexion()
|
||||||
return False
|
plist = self._editor.list(cnx, params)
|
||||||
|
return len(plist) == 0
|
||||||
|
|
||||||
def save(self, formsemestre_id=None, name=None):
|
def save(self, formsemestre_id=None, name=None):
|
||||||
"""Write one or all (if name is None) values to db"""
|
"""Write one or all (if name is None) values to db"""
|
||||||
try:
|
|
||||||
scu.GSL.acquire()
|
|
||||||
modif = False
|
modif = False
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
if name is None:
|
if name is None:
|
||||||
@ -1909,7 +1935,7 @@ class BasePreferences(object):
|
|||||||
else:
|
else:
|
||||||
names = [name]
|
names = [name]
|
||||||
for name in names:
|
for name in names:
|
||||||
value = self.get(formsemestre_id, name)
|
value = self.prefs[formsemestre_id][name]
|
||||||
if self.prefs_dict[name].get("input_type", None) == "boolcheckbox":
|
if self.prefs_dict[name].get("input_type", None) == "boolcheckbox":
|
||||||
# repasse les booleens en chaines "0":"1"
|
# repasse les booleens en chaines "0":"1"
|
||||||
value = "1" if value else "0"
|
value = "1" if value else "0"
|
||||||
@ -1938,8 +1964,12 @@ class BasePreferences(object):
|
|||||||
log("create pref sem=%s %s=%s" % (formsemestre_id, name, value))
|
log("create pref sem=%s %s=%s" % (formsemestre_id, name, value))
|
||||||
else:
|
else:
|
||||||
# edit existing value
|
# edit existing value
|
||||||
if pdb[0]["value"] != str(value) and (
|
|
||||||
pdb[0]["value"] or str(value)
|
existing_value = pdb[0]["value"] # old stored value
|
||||||
|
if (
|
||||||
|
(existing_value != value)
|
||||||
|
and (existing_value != str(value))
|
||||||
|
and (existing_value or str(value))
|
||||||
):
|
):
|
||||||
self._editor.edit(
|
self._editor.edit(
|
||||||
cnx,
|
cnx,
|
||||||
@ -1956,8 +1986,6 @@ class BasePreferences(object):
|
|||||||
# les preferences peuvent affecter les PDF cachés et les notes calculées:
|
# les preferences peuvent affecter les PDF cachés et les notes calculées:
|
||||||
if modif:
|
if modif:
|
||||||
sco_cache.invalidate_formsemestre()
|
sco_cache.invalidate_formsemestre()
|
||||||
finally:
|
|
||||||
scu.GSL.release()
|
|
||||||
|
|
||||||
def set(self, formsemestre_id, name, value):
|
def set(self, formsemestre_id, name, value):
|
||||||
if not name or name[0] == "_" or name not in self.prefs_name:
|
if not name or name[0] == "_" or name not in self.prefs_name:
|
||||||
@ -1972,8 +2000,7 @@ class BasePreferences(object):
|
|||||||
def delete(self, formsemestre_id, name):
|
def delete(self, formsemestre_id, name):
|
||||||
if not formsemestre_id:
|
if not formsemestre_id:
|
||||||
raise ScoException()
|
raise ScoException()
|
||||||
try:
|
|
||||||
scu.GSL.acquire()
|
|
||||||
if formsemestre_id in self.prefs and name in self.prefs[formsemestre_id]:
|
if formsemestre_id in self.prefs and name in self.prefs[formsemestre_id]:
|
||||||
del self.prefs[formsemestre_id][name]
|
del self.prefs[formsemestre_id][name]
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
@ -1985,16 +2012,17 @@ class BasePreferences(object):
|
|||||||
assert pdb[0]["dept_id"] == self.dept_id
|
assert pdb[0]["dept_id"] == self.dept_id
|
||||||
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
|
||||||
finally:
|
|
||||||
scu.GSL.release()
|
|
||||||
|
|
||||||
def edit(self, REQUEST):
|
def edit(self, REQUEST):
|
||||||
"""HTML dialog: edit global preferences"""
|
"""HTML dialog: edit global preferences"""
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
|
|
||||||
|
self.load()
|
||||||
H = [
|
H = [
|
||||||
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)
|
||||||
|
}">modification des logos du département (pour documents pdf)</a></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="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>
|
||||||
""",
|
""",
|
||||||
|
@ -622,7 +622,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None, REQUEST=
|
|||||||
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
||||||
|
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
group_ids, formsemestre_id=formsemestre_id, REQUEST=REQUEST
|
group_ids, formsemestre_id=formsemestre_id
|
||||||
)
|
)
|
||||||
etudids = [m["etudid"] for m in groups_infos.members]
|
etudids = [m["etudid"] for m in groups_infos.members]
|
||||||
|
|
||||||
@ -800,7 +800,7 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[], REQUEST=No
|
|||||||
# tous les inscrits du semestre
|
# tous les inscrits du semestre
|
||||||
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
group_ids, formsemestre_id=formsemestre_id, REQUEST=REQUEST
|
group_ids, formsemestre_id=formsemestre_id
|
||||||
)
|
)
|
||||||
etudids = [m["etudid"] for m in groups_infos.members]
|
etudids = [m["etudid"] for m in groups_infos.members]
|
||||||
|
|
||||||
|
@ -620,7 +620,6 @@ def saisie_notes_tableur(evaluation_id, group_ids=[], REQUEST=None):
|
|||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
select_all_when_unspecified=True,
|
select_all_when_unspecified=True,
|
||||||
etat=None,
|
etat=None,
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
@ -793,7 +792,6 @@ def feuille_saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
|
|||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
select_all_when_unspecified=True,
|
select_all_when_unspecified=True,
|
||||||
etat=None,
|
etat=None,
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
groups = sco_groups.listgroups(groups_infos.group_ids)
|
groups = sco_groups.listgroups(groups_infos.group_ids)
|
||||||
gr_title_filename = sco_groups.listgroups_filename(groups)
|
gr_title_filename = sco_groups.listgroups_filename(groups)
|
||||||
@ -891,7 +889,6 @@ def saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
|
|||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
select_all_when_unspecified=True,
|
select_all_when_unspecified=True,
|
||||||
etat=None,
|
etat=None,
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if E["description"]:
|
if E["description"]:
|
||||||
|
@ -158,11 +158,10 @@ class SemSet(dict):
|
|||||||
|
|
||||||
ndb.SimpleQuery(
|
ndb.SimpleQuery(
|
||||||
"""INSERT INTO notes_semset_formsemestre
|
"""INSERT INTO notes_semset_formsemestre
|
||||||
(dept_id, id, semset_id)
|
(formsemestre_id, semset_id)
|
||||||
VALUES (%(dept_id)s, %(formsemestre_id)s, %(semset_id)s)
|
VALUES (%(formsemestre_id)s, %(semset_id)s)
|
||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
"dept_id": g.scodoc_dept_id,
|
|
||||||
"formsemestre_id": formsemestre_id,
|
"formsemestre_id": formsemestre_id,
|
||||||
"semset_id": self.semset_id,
|
"semset_id": self.semset_id,
|
||||||
},
|
},
|
||||||
|
@ -78,7 +78,7 @@ def trombino(
|
|||||||
etat = None # may be passed as ''
|
etat = None # may be passed as ''
|
||||||
# Informations sur les groupes à afficher:
|
# Informations sur les groupes à afficher:
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
group_ids, formsemestre_id=formsemestre_id, etat=etat, REQUEST=REQUEST
|
group_ids, formsemestre_id=formsemestre_id, etat=etat
|
||||||
)
|
)
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -247,7 +247,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=[], REQUEST=None, 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, REQUEST=REQUEST)
|
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
|
||||||
|
|
||||||
portal_url = sco_portal_apogee.get_portal_url()
|
portal_url = sco_portal_apogee.get_portal_url()
|
||||||
@ -485,14 +485,13 @@ def photos_generate_excel_sample(group_ids=[], REQUEST=None):
|
|||||||
"photo_filename",
|
"photo_filename",
|
||||||
],
|
],
|
||||||
extra_cols=["fichier_photo"],
|
extra_cols=["fichier_photo"],
|
||||||
REQUEST=REQUEST,
|
|
||||||
)
|
)
|
||||||
return sco_excel.send_excel_file(REQUEST, data, "ImportPhotos" + scu.XLSX_SUFFIX)
|
return sco_excel.send_excel_file(REQUEST, data, "ImportPhotos" + scu.XLSX_SUFFIX)
|
||||||
|
|
||||||
|
|
||||||
def photos_import_files_form(group_ids=[], REQUEST=None):
|
def photos_import_files_form(group_ids=[], REQUEST=None):
|
||||||
"""Formulaire pour importation photos"""
|
"""Formulaire pour importation photos"""
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids, REQUEST=REQUEST)
|
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
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
@ -541,7 +540,7 @@ def photos_import_files_form(group_ids=[], REQUEST=None):
|
|||||||
|
|
||||||
def photos_import_files(group_ids=[], xlsfile=None, zipfile=None, REQUEST=None):
|
def photos_import_files(group_ids=[], xlsfile=None, zipfile=None, REQUEST=None):
|
||||||
"""Importation des photos"""
|
"""Importation des photos"""
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids, REQUEST=REQUEST)
|
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
|
||||||
filename_title = "fichier_photo"
|
filename_title = "fichier_photo"
|
||||||
page_title = "Téléchargement des photos des étudiants"
|
page_title = "Téléchargement des photos des étudiants"
|
||||||
|
@ -61,7 +61,7 @@ def pdf_trombino_tours(
|
|||||||
"""Generation du trombinoscope en fichier PDF"""
|
"""Generation du trombinoscope en fichier PDF"""
|
||||||
# Informations sur les groupes à afficher:
|
# Informations sur les groupes à afficher:
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
group_ids, formsemestre_id=formsemestre_id, REQUEST=REQUEST
|
group_ids, formsemestre_id=formsemestre_id
|
||||||
)
|
)
|
||||||
|
|
||||||
DeptName = sco_preferences.get_preference("DeptName")
|
DeptName = sco_preferences.get_preference("DeptName")
|
||||||
@ -296,7 +296,7 @@ def pdf_feuille_releve_absences(
|
|||||||
|
|
||||||
# Informations sur les groupes à afficher:
|
# Informations sur les groupes à afficher:
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
group_ids, formsemestre_id=formsemestre_id, REQUEST=REQUEST
|
group_ids, formsemestre_id=formsemestre_id
|
||||||
)
|
)
|
||||||
|
|
||||||
DeptName = sco_preferences.get_preference("DeptName")
|
DeptName = sco_preferences.get_preference("DeptName")
|
||||||
|
@ -28,104 +28,19 @@
|
|||||||
|
|
||||||
""" Verification version logiciel vs version "stable" sur serveur
|
""" Verification version logiciel vs version "stable" sur serveur
|
||||||
N'effectue pas la mise à jour automatiquement, mais permet un affichage d'avertissement.
|
N'effectue pas la mise à jour automatiquement, mais permet un affichage d'avertissement.
|
||||||
|
|
||||||
|
Désactivé temporairement pour ScoDoc 9.
|
||||||
"""
|
"""
|
||||||
import datetime
|
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
from flask import current_app
|
||||||
from app import log
|
|
||||||
|
|
||||||
# Appel renvoyant la subversion "stable"
|
|
||||||
# La notion de "stable" est juste là pour éviter d'afficher trop frequemment
|
|
||||||
# des avertissements de mise à jour: on veut pouvoir inciter à mettre à jour lors de
|
|
||||||
# correctifs majeurs.
|
|
||||||
|
|
||||||
GET_VER_URL = "http://scodoc.iutv.univ-paris13.fr/scodoc-installmgr/last_stable_version"
|
|
||||||
|
|
||||||
|
|
||||||
def get_last_stable_version():
|
|
||||||
"""request last stable version number from server
|
|
||||||
(returns string as given by server, empty if failure)
|
|
||||||
(do not wait server answer more than 3 seconds)
|
|
||||||
"""
|
|
||||||
global _LAST_UP_TO_DATE_REQUEST
|
|
||||||
ans = scu.query_portal(
|
|
||||||
GET_VER_URL, msg="ScoDoc version server", timeout=3
|
|
||||||
) # sco_utils
|
|
||||||
if ans:
|
|
||||||
ans = ans.strip()
|
|
||||||
_LAST_UP_TO_DATE_REQUEST = datetime.datetime.now()
|
|
||||||
log(
|
|
||||||
'get_last_stable_version: updated at %s, answer="%s"'
|
|
||||||
% (_LAST_UP_TO_DATE_REQUEST, ans)
|
|
||||||
)
|
|
||||||
return ans
|
|
||||||
|
|
||||||
|
|
||||||
_LAST_UP_TO_DATE_REQUEST = None # datetime of last request to server
|
|
||||||
_UP_TO_DATE = True # cached result (limit requests to 1 per day)
|
|
||||||
_UP_TO_DATE_MSG = ""
|
|
||||||
|
|
||||||
|
|
||||||
def is_up_to_date():
|
def is_up_to_date():
|
||||||
"""True if up_to_date
|
"""True if up_to_date
|
||||||
Returns status, message
|
Returns status, message
|
||||||
"""
|
"""
|
||||||
log("Warning: is_up_to_date not implemented for ScoDoc8")
|
current_app.logger.debug("Warning: is_up_to_date not implemented for ScoDoc9")
|
||||||
return True, "unimplemented"
|
return True, "unimplemented"
|
||||||
# global _LAST_UP_TO_DATE_REQUEST, _UP_TO_DATE, _UP_TO_DATE_MSG
|
|
||||||
# if _LAST_UP_TO_DATE_REQUEST and (
|
|
||||||
# datetime.datetime.now() - _LAST_UP_TO_DATE_REQUEST
|
|
||||||
# ) < datetime.timedelta(1):
|
|
||||||
# # requete deja effectuee aujourd'hui:
|
|
||||||
# return _UP_TO_DATE, _UP_TO_DATE_MSG
|
|
||||||
|
|
||||||
# last_stable_ver = get_last_stable_version()
|
|
||||||
# cur_ver = scu.get_svn_version(scu.SCO_SRC_DIR) # in sco_utils
|
|
||||||
# cur_ver2 = cur_ver
|
|
||||||
# cur_ver_num = -1
|
|
||||||
# # Convert versions to integers:
|
|
||||||
# try:
|
|
||||||
# # cur_ver can be "1234" or "1234M' or '1234:1245M'...
|
|
||||||
# fs = cur_ver.split(":", 1)
|
|
||||||
# if len(fs) > 1:
|
|
||||||
# cur_ver2 = fs[-1]
|
|
||||||
# m = re.match(r"([0-9]*)", cur_ver2)
|
|
||||||
# if not m:
|
|
||||||
# raise ValueError(
|
|
||||||
# "invalid svn version"
|
|
||||||
# ) # should never occur, regexp always (maybe empty) match
|
|
||||||
# cur_ver_num = int(m.group(1))
|
|
||||||
# except:
|
|
||||||
# log('Warning: no numeric subversion ! (cur_ver="%s")' % cur_ver)
|
|
||||||
# return _UP_TO_DATE, _UP_TO_DATE_MSG # silently ignore misconfiguration ?
|
|
||||||
# try:
|
|
||||||
# last_stable_ver_num = int(last_stable_ver)
|
|
||||||
# except:
|
|
||||||
# log("Warning: last_stable_version returned by server is invalid !")
|
|
||||||
# return (
|
|
||||||
# _UP_TO_DATE,
|
|
||||||
# _UP_TO_DATE_MSG,
|
|
||||||
# ) # should ignore this error (maybe server is unreachable)
|
|
||||||
# #
|
|
||||||
# if cur_ver_num < last_stable_ver_num:
|
|
||||||
# _UP_TO_DATE = False
|
|
||||||
# _UP_TO_DATE_MSG = "Version %s disponible (version %s installée)" % (
|
|
||||||
# last_stable_ver,
|
|
||||||
# cur_ver_num,
|
|
||||||
# )
|
|
||||||
# log(
|
|
||||||
# "Warning: ScoDoc installation is not up-to-date, should upgrade\n%s"
|
|
||||||
# % _UP_TO_DATE_MSG
|
|
||||||
# )
|
|
||||||
# else:
|
|
||||||
# _UP_TO_DATE = True
|
|
||||||
# _UP_TO_DATE_MSG = ""
|
|
||||||
# log(
|
|
||||||
# "ScoDoc is up-to-date (cur_ver: %s, using %s=%s)"
|
|
||||||
# % (cur_ver, cur_ver2, cur_ver_num)
|
|
||||||
# )
|
|
||||||
|
|
||||||
# return _UP_TO_DATE, _UP_TO_DATE_MSG
|
|
||||||
|
|
||||||
|
|
||||||
def html_up_to_date_box():
|
def html_up_to_date_box():
|
||||||
|
@ -232,6 +232,8 @@ if not os.path.exists(SCO_TMP_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")
|
||||||
|
LOGOS_IMAGES_ALLOWED_TYPES = ("jpg", "jpeg", "png") # remind that PIL does not read pdf
|
||||||
|
|
||||||
|
|
||||||
# ----- Les outils distribués
|
# ----- Les outils distribués
|
||||||
SCO_TOOLS_DIR = os.path.join(Config.SCODOC_DIR, "tools")
|
SCO_TOOLS_DIR = os.path.join(Config.SCODOC_DIR, "tools")
|
||||||
@ -305,8 +307,6 @@ PDF_MIMETYPE = "application/pdf"
|
|||||||
XML_MIMETYPE = "text/xml"
|
XML_MIMETYPE = "text/xml"
|
||||||
JSON_MIMETYPE = "application/json"
|
JSON_MIMETYPE = "application/json"
|
||||||
|
|
||||||
LOGOS_IMAGES_ALLOWED_TYPES = ("jpg", "png") # remind that PIL does not read pdf
|
|
||||||
|
|
||||||
# Admissions des étudiants
|
# Admissions des étudiants
|
||||||
# Différents types de voies d'admission:
|
# Différents types de voies d'admission:
|
||||||
# (stocké en texte libre dans la base, mais saisie par menus pour harmoniser)
|
# (stocké en texte libre dans la base, mais saisie par menus pour harmoniser)
|
||||||
@ -672,14 +672,14 @@ def graph_from_edges(edges, graph_name="mygraph"):
|
|||||||
ICONSIZES = {} # name : (width, height) cache image sizes
|
ICONSIZES = {} # name : (width, height) cache image sizes
|
||||||
|
|
||||||
|
|
||||||
def icontag(name, file_format="png", **attrs):
|
def icontag(name, file_format="png", no_size=False, **attrs):
|
||||||
"""tag HTML pour un icone.
|
"""tag HTML pour un icone.
|
||||||
(dans les versions anterieures on utilisait Zope)
|
(dans les versions anterieures on utilisait Zope)
|
||||||
Les icones sont des fichiers PNG dans .../static/icons
|
Les icones sont des fichiers PNG dans .../static/icons
|
||||||
Si la taille (width et height) n'est pas spécifiée, lit l'image
|
Si la taille (width et height) n'est pas spécifiée, lit l'image
|
||||||
pour la mesurer (et cache le résultat).
|
pour la mesurer (et cache le résultat).
|
||||||
"""
|
"""
|
||||||
if ("width" not in attrs) or ("height" not in attrs):
|
if (not no_size) and (("width" not in attrs) or ("height" not in attrs)):
|
||||||
if name not in ICONSIZES:
|
if name not in ICONSIZES:
|
||||||
img_file = os.path.join(
|
img_file = os.path.join(
|
||||||
Config.SCODOC_DIR,
|
Config.SCODOC_DIR,
|
||||||
|
@ -96,6 +96,10 @@ tr.bandeaugtr {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar-default .navbar-nav>li.logout a {
|
||||||
|
color: rgb(255,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
/* ----- page content ------ */
|
/* ----- page content ------ */
|
||||||
|
|
||||||
div.about-logo {
|
div.about-logo {
|
||||||
@ -253,6 +257,13 @@ div.logo-insidebar {
|
|||||||
div.logo-logo {
|
div.logo-logo {
|
||||||
text-align: center ;
|
text-align: center ;
|
||||||
}
|
}
|
||||||
|
div.logo-logo img {
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
div.sidebar-bottom {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
div.etud_info_div {
|
div.etud_info_div {
|
||||||
border: 2px solid gray;
|
border: 2px solid gray;
|
||||||
@ -317,6 +328,9 @@ table.listesems th {
|
|||||||
padding-top: 0.5em;
|
padding-top: 0.5em;
|
||||||
padding-left: 0.5em;
|
padding-left: 0.5em;
|
||||||
}
|
}
|
||||||
|
table.listesems td {
|
||||||
|
vertical-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
table.listesems td.semicon {
|
table.listesems td.semicon {
|
||||||
padding-left: 1.5em;
|
padding-left: 1.5em;
|
||||||
@ -326,6 +340,11 @@ table.listesems tr.firstsem td {
|
|||||||
padding-top: 0.8em;
|
padding-top: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td.datesem {
|
||||||
|
font-size: 80%;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
h2.listesems {
|
h2.listesems {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
@ -816,11 +835,22 @@ a.discretelink:hover {
|
|||||||
|
|
||||||
div.sco_help {
|
div.sco_help {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
|
margin-bottom: 3px;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: navy;
|
color: navy;
|
||||||
background-color: rgb(200,200,220);
|
background-color: rgb(200,200,220);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.wtf-field ul.errors li {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.configuration_logo div.img-container {
|
||||||
|
width: 256px;
|
||||||
|
}
|
||||||
|
.configuration_logo div.img-container img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
p.indent {
|
p.indent {
|
||||||
padding-left: 2em;
|
padding-left: 2em;
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 29 KiB |
@ -25,15 +25,21 @@
|
|||||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||||
{% if current_user.is_administrator() %}
|
{% if current_user.is_administrator() %}
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li><a href="{{ url_for('scodoc.configuration') }}">Configuration</a></li>
|
<li><a href="{{ url_for('scodoc.configuration') }}">configuration</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
{% if current_user.is_anonymous %}
|
{% if current_user.is_anonymous %}
|
||||||
<li><a href="{{ url_for('auth.login') }}">Login</a></li>
|
<li><a href="{{ url_for('auth.login') }}">connexion</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li>{{current_user.user_name}}</li>
|
<li>{% if current_user.dept %}
|
||||||
<li><a href="{{ url_for('auth.logout') }}">Logout</a></li>
|
<a href="{{ url_for('users.user_info_page', scodoc_dept=current_user.dept, user_name=current_user.user_name )
|
||||||
|
}}">{{current_user.user_name}} ({{current_user.dept}})</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="">{{current_user.user_name}}</a>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
<li class="logout"><a href="{{ url_for('auth.logout') }}">déconnexion</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,17 +17,37 @@
|
|||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
<h1>Configuration générale</h1>
|
|
||||||
|
|
||||||
<p class="help">Les paramètres donnés ici s'appliquent à tout ScoDoc (tous les départements).</p>
|
{% if scodoc_dept %}
|
||||||
|
<h1>Logos du département {{ scodoc_dept }}</h1>
|
||||||
|
{% else %}
|
||||||
|
<h1>Configuration générale {{ scodoc_dept }}</h1>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<form class="sco-form" action="" method="post" novalidate>
|
<form class="sco-form" action="" method="post" enctype="multipart/form-data" novalidate>
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
|
|
||||||
|
{% if not scodoc_dept %}
|
||||||
|
<div class="sco_help">Les paramètres donnés ici s'appliquent à tout ScoDoc (tous les départements):</div>
|
||||||
|
|
||||||
{{ render_field(form.bonus_sport_func_name)}}
|
{{ render_field(form.bonus_sport_func_name)}}
|
||||||
{# <p>
|
{% endif %}
|
||||||
{{ form.bonus_sport_func_name.label }}<br>
|
|
||||||
{{ form.bonus_sport_func_name() }}
|
<div class="configuration_logo">
|
||||||
</p> #}
|
<h3>Logo en-tête</h3>
|
||||||
|
<p class="help">image placée en haut de certains documents documents PDF. Image actuelle:</p>
|
||||||
|
<div class="img-container"><img src="{{ url_for('scodoc.logo_header', scodoc_dept=scodoc_dept) }}"
|
||||||
|
alt="pas de logo chargé" /></div>
|
||||||
|
{{ render_field(form.logo_header) }}
|
||||||
|
<h3>Logo pied de page</h3>
|
||||||
|
<p class="help">image placée en pied de page de certains documents documents PDF. Image actuelle:</p>
|
||||||
|
<div class="img-container"><img src="{{ url_for('scodoc.logo_footer', scodoc_dept=g.scodoc_dept) }}"
|
||||||
|
alt="pas de logo chargé" /></div>
|
||||||
|
{{ render_field(form.logo_footer) }}
|
||||||
|
</div>
|
||||||
|
<!-- <div class="sco_help">Les paramètres ci-dessous peuvent être changés dans chaque département
|
||||||
|
(paramétrage).<br />On indique ici les valeurs initiales par défaut:
|
||||||
|
</div> -->
|
||||||
<div class="sco-submit">{{ form.submit() }}</div>
|
<div class="sco-submit">{{ form.submit() }}</div>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -30,7 +30,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
<form action="table_etud_in_accessible_depts" method="POST">
|
<form action="{{url_for('scodoc.table_etud_in_accessible_depts')}}" method="POST">
|
||||||
<b>Chercher étudiant:</b>
|
<b>Chercher étudiant:</b>
|
||||||
<input type="text" name="expnom" width="12" spellcheck="false" value="">
|
<input type="text" name="expnom" width="12" spellcheck="false" value="">
|
||||||
<input type="submit" value="Chercher">
|
<input type="submit" value="Chercher">
|
||||||
@ -38,8 +38,9 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<!--
|
||||||
<div style="margin-top: 1cm;">
|
<div style="margin-top: 1cm;">
|
||||||
<p><a href="/ScoDoc/static/mobile">Charger la version mobile (expérimentale)</a></p>
|
<p><a href="/ScoDoc/static/mobile">Charger la version mobile (expérimentale)</a></p>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -68,6 +68,7 @@ from app.decorators import (
|
|||||||
permission_required,
|
permission_required,
|
||||||
admin_required,
|
admin_required,
|
||||||
login_required,
|
login_required,
|
||||||
|
permission_required_compat_scodoc7,
|
||||||
)
|
)
|
||||||
|
|
||||||
from app.views import absences_bp as bp
|
from app.views import absences_bp as bp
|
||||||
@ -312,7 +313,7 @@ def SignaleAbsenceGrHebdo(
|
|||||||
moduleimpl_id = None
|
moduleimpl_id = None
|
||||||
|
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
group_ids, moduleimpl_id=moduleimpl_id, REQUEST=REQUEST
|
group_ids, moduleimpl_id=moduleimpl_id
|
||||||
)
|
)
|
||||||
if not groups_infos.members:
|
if not groups_infos.members:
|
||||||
return (
|
return (
|
||||||
@ -474,7 +475,7 @@ def SignaleAbsenceGrSemestre(
|
|||||||
REQUEST=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, REQUEST=REQUEST)
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
||||||
if not groups_infos.members:
|
if not groups_infos.members:
|
||||||
return (
|
return (
|
||||||
html_sco_header.sco_header(page_title="Saisie des absences")
|
html_sco_header.sco_header(page_title="Saisie des absences")
|
||||||
@ -847,7 +848,7 @@ def EtatAbsencesGr(
|
|||||||
datedebut = ndb.DateDMYtoISO(debut)
|
datedebut = ndb.DateDMYtoISO(debut)
|
||||||
datefin = ndb.DateDMYtoISO(fin)
|
datefin = ndb.DateDMYtoISO(fin)
|
||||||
# Informations sur les groupes à afficher:
|
# Informations sur les groupes à afficher:
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids, REQUEST=REQUEST)
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
||||||
formsemestre_id = groups_infos.formsemestre_id
|
formsemestre_id = groups_infos.formsemestre_id
|
||||||
sem = groups_infos.formsemestre
|
sem = groups_infos.formsemestre
|
||||||
|
|
||||||
@ -971,13 +972,11 @@ ou entrez une date pour visualiser les absents un jour donné :
|
|||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def EtatAbsencesDate(
|
def EtatAbsencesDate(group_ids=[], date=None): # list of groups to display
|
||||||
group_ids=[], date=None, REQUEST=None # list of groups to display
|
|
||||||
):
|
|
||||||
# ported from dtml
|
# ported from dtml
|
||||||
"""Etat des absences pour un groupe à une date donnée"""
|
"""Etat des absences pour un groupe à une date donnée"""
|
||||||
# Informations sur les groupes à afficher:
|
# Informations sur les groupes à afficher:
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids, REQUEST=REQUEST)
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
||||||
H = [html_sco_header.sco_header(page_title="Etat des absences")]
|
H = [html_sco_header.sco_header(page_title="Etat des absences")]
|
||||||
if date:
|
if date:
|
||||||
dateiso = ndb.DateDMYtoISO(date)
|
dateiso = ndb.DateDMYtoISO(date)
|
||||||
@ -1236,9 +1235,11 @@ def listeBilletsEtud(etudid=False, REQUEST=None, format="html"):
|
|||||||
return tab.make_page(REQUEST=REQUEST, format=format)
|
return tab.make_page(REQUEST=REQUEST, format=format)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/XMLgetBilletsEtud")
|
@bp.route(
|
||||||
|
"/XMLgetBilletsEtud", methods=["GET", "POST"]
|
||||||
|
) # pour compat anciens clients PHP
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def XMLgetBilletsEtud(etudid=False, REQUEST=None):
|
def XMLgetBilletsEtud(etudid=False, REQUEST=None):
|
||||||
"""Liste billets pour un etudiant"""
|
"""Liste billets pour un etudiant"""
|
||||||
@ -1250,9 +1251,9 @@ def XMLgetBilletsEtud(etudid=False, REQUEST=None):
|
|||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/listeBillets")
|
@bp.route("/listeBillets", methods=["GET", "POST"]) # pour compat anciens clients PHP
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def listeBillets(REQUEST=None):
|
def listeBillets(REQUEST=None):
|
||||||
"""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"""
|
||||||
@ -1461,9 +1462,19 @@ def ProcessBilletAbsenceForm(billet_id, REQUEST=None):
|
|||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/XMLgetAbsEtud")
|
# @bp.route("/essai_api7")
|
||||||
|
# @scodoc
|
||||||
|
# @permission_required_compat_scodoc7(Permission.ScoView)
|
||||||
|
# @scodoc7func
|
||||||
|
# def essai_api7(x="xxx"):
|
||||||
|
# "un essai"
|
||||||
|
# log("arfffffffffffffffffff")
|
||||||
|
# return "OK OK x=" + str(x)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/XMLgetAbsEtud", methods=["GET", "POST"]) # pour compat anciens clients PHP
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def XMLgetAbsEtud(beg_date="", end_date="", REQUEST=None):
|
def XMLgetAbsEtud(beg_date="", end_date="", REQUEST=None):
|
||||||
"""returns list of absences in date interval"""
|
"""returns list of absences in date interval"""
|
||||||
|
@ -50,6 +50,7 @@ from app.decorators import (
|
|||||||
scodoc,
|
scodoc,
|
||||||
scodoc7func,
|
scodoc7func,
|
||||||
permission_required,
|
permission_required,
|
||||||
|
permission_required_compat_scodoc7,
|
||||||
admin_required,
|
admin_required,
|
||||||
login_required,
|
login_required,
|
||||||
)
|
)
|
||||||
@ -252,11 +253,36 @@ sco_publish(
|
|||||||
Permission.ScoChangeFormation,
|
Permission.ScoChangeFormation,
|
||||||
methods=["GET", "POST"],
|
methods=["GET", "POST"],
|
||||||
)
|
)
|
||||||
sco_publish(
|
|
||||||
"/formsemestre_bulletinetud",
|
|
||||||
sco_bulletins.formsemestre_bulletinetud,
|
@bp.route(
|
||||||
Permission.ScoView,
|
"formsemestre_bulletinetud", methods=["GET", "POST"]
|
||||||
|
) # pour compat anciens clients PHP
|
||||||
|
@scodoc
|
||||||
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||||
|
@scodoc7func
|
||||||
|
def formsemestre_bulletinetud(
|
||||||
|
etudid=None,
|
||||||
|
formsemestre_id=None,
|
||||||
|
format="html",
|
||||||
|
version="long",
|
||||||
|
xml_with_decisions=False,
|
||||||
|
force_publishing=False,
|
||||||
|
prefer_mail_perso=False,
|
||||||
|
REQUEST=None,
|
||||||
|
):
|
||||||
|
return sco_bulletins.formsemestre_bulletinetud(
|
||||||
|
etudid=etudid,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
format=format,
|
||||||
|
version=version,
|
||||||
|
xml_with_decisions=xml_with_decisions,
|
||||||
|
force_publishing=force_publishing,
|
||||||
|
prefer_mail_perso=prefer_mail_perso,
|
||||||
|
REQUEST=REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
sco_publish(
|
sco_publish(
|
||||||
"/formsemestre_evaluations_cal",
|
"/formsemestre_evaluations_cal",
|
||||||
sco_evaluations.formsemestre_evaluations_cal,
|
sco_evaluations.formsemestre_evaluations_cal,
|
||||||
@ -573,9 +599,11 @@ sco_publish("/ue_move", sco_edit_formation.ue_move, Permission.ScoChangeFormatio
|
|||||||
# --- Semestres de formation
|
# --- Semestres de formation
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre_list")
|
@bp.route(
|
||||||
|
"/formsemestre_list", methods=["GET", "POST"]
|
||||||
|
) # pour compat anciens clients PHP
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def formsemestre_list(
|
def formsemestre_list(
|
||||||
format=None,
|
format=None,
|
||||||
@ -599,9 +627,11 @@ def formsemestre_list(
|
|||||||
return scu.sendResult(REQUEST, sems, name="formsemestre", format=format)
|
return scu.sendResult(REQUEST, sems, name="formsemestre", format=format)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/XMLgetFormsemestres")
|
@bp.route(
|
||||||
|
"/XMLgetFormsemestres", methods=["GET", "POST"]
|
||||||
|
) # pour compat anciens clients PHP
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def XMLgetFormsemestres(etape_apo=None, formsemestre_id=None, REQUEST=None):
|
def XMLgetFormsemestres(etape_apo=None, formsemestre_id=None, REQUEST=None):
|
||||||
"""List all formsemestres matching etape, XML format
|
"""List all formsemestres matching etape, XML format
|
||||||
|
@ -30,20 +30,35 @@ Module main: page d'accueil, avec liste des départements
|
|||||||
|
|
||||||
Emmanuel Viennet, 2021
|
Emmanuel Viennet, 2021
|
||||||
"""
|
"""
|
||||||
|
from app.auth.models import User
|
||||||
|
import os
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import flash, url_for, redirect, render_template
|
from flask import abort, flash, url_for, redirect, render_template, send_file
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask.app import Flask
|
from flask.app import Flask
|
||||||
from flask_login.utils import login_required
|
from flask_login.utils import login_required
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_wtf.file import FileField, FileAllowed
|
||||||
|
from werkzeug.exceptions import BadRequest, NotFound
|
||||||
from wtforms import SelectField, SubmitField
|
from wtforms import SelectField, SubmitField
|
||||||
|
from wtforms.fields import IntegerField
|
||||||
|
from wtforms.fields.simple import BooleanField, StringField, TextAreaField
|
||||||
|
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
|
||||||
|
|
||||||
# from wtforms.validators import DataRequired
|
import app
|
||||||
|
from app.models import Departement, Identite
|
||||||
from app.models import Departement, ScoDocSiteConfig
|
from app.models import FormSemestre, NotesFormsemestreInscription
|
||||||
|
from app.models import ScoDocSiteConfig
|
||||||
import sco_version
|
import sco_version
|
||||||
|
from app.scodoc import sco_logos
|
||||||
from app.scodoc import sco_find_etud
|
from app.scodoc import sco_find_etud
|
||||||
from app.decorators import admin_required
|
from app.scodoc import sco_utils as scu
|
||||||
|
from app.decorators import (
|
||||||
|
admin_required,
|
||||||
|
scodoc7func,
|
||||||
|
permission_required_compat_scodoc7,
|
||||||
|
)
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.views import scodoc_bp as bp
|
from app.views import scodoc_bp as bp
|
||||||
|
|
||||||
@ -72,13 +87,56 @@ def table_etud_in_accessible_depts():
|
|||||||
return sco_find_etud.table_etud_in_accessible_depts(expnom=request.form["expnom"])
|
return sco_find_etud.table_etud_in_accessible_depts(expnom=request.form["expnom"])
|
||||||
|
|
||||||
|
|
||||||
|
# Fonction d'API accessible sans aucun authentification
|
||||||
|
@bp.route("/ScoDoc/get_etud_dept")
|
||||||
|
def get_etud_dept():
|
||||||
|
"""Returns the dept acronym (eg "GEII") of an etud (identified by etudid,
|
||||||
|
code_nip ou code_ine in the request).
|
||||||
|
Ancienne API: ramène la chaine brute, texte sans JSON ou XML.
|
||||||
|
"""
|
||||||
|
if "etudid" in request.args:
|
||||||
|
# zero ou une réponse:
|
||||||
|
etuds = [Identite.query.get(request.args["etudid"])]
|
||||||
|
elif "code_nip" in request.args:
|
||||||
|
# il peut y avoir plusieurs réponses si l'étudiant est passé par plusieurs départements
|
||||||
|
etuds = Identite.query.filter_by(code_nip=request.args["code_nip"]).all()
|
||||||
|
elif "code_ine" in request.args:
|
||||||
|
etuds = Identite.query.filter_by(code_nip=request.args["code_ine"]).all()
|
||||||
|
else:
|
||||||
|
raise BadRequest(
|
||||||
|
"missing argument (expected one among: etudid, code_nip or code_ine)"
|
||||||
|
)
|
||||||
|
if not etuds:
|
||||||
|
raise NotFound("student not found")
|
||||||
|
elif len(etuds) == 1:
|
||||||
|
last_etud = etuds[0]
|
||||||
|
else:
|
||||||
|
# inscriptions dans plusieurs departements: cherche la plus recente
|
||||||
|
last_etud = None
|
||||||
|
last_date = None
|
||||||
|
for etud in etuds:
|
||||||
|
inscriptions = NotesFormsemestreInscription.query.filter_by(
|
||||||
|
etudid=etud.id
|
||||||
|
).all()
|
||||||
|
for ins in inscriptions:
|
||||||
|
date_fin = FormSemestre.query.get(ins.formsemestre_id).date_fin
|
||||||
|
if (last_date is None) or date_fin > last_date:
|
||||||
|
last_date = date_fin
|
||||||
|
last_etud = etud
|
||||||
|
if not last_etud:
|
||||||
|
# est présent dans plusieurs semestres mais inscrit dans aucun !
|
||||||
|
# le choix a peu d'importance...
|
||||||
|
last_etud = etuds[-1]
|
||||||
|
|
||||||
|
return Departement.query.get(last_etud.dept_id).acronym
|
||||||
|
|
||||||
|
|
||||||
# ---- CONFIGURATION
|
# ---- CONFIGURATION
|
||||||
|
|
||||||
|
|
||||||
class ScoDocConfigurationForm(FlaskForm):
|
class ScoDocConfigurationForm(FlaskForm):
|
||||||
"Panneau de configuration général"
|
"Panneau de configuration général"
|
||||||
# très préliminaire ;-)
|
|
||||||
# On veut y mettre la fonction bonus et ensuite les logos
|
|
||||||
bonus_sport_func_name = SelectField(
|
bonus_sport_func_name = SelectField(
|
||||||
label="Fonction de calcul des bonus sport&culture",
|
label="Fonction de calcul des bonus sport&culture",
|
||||||
choices=[
|
choices=[
|
||||||
@ -86,28 +144,104 @@ class ScoDocConfigurationForm(FlaskForm):
|
|||||||
for x in ScoDocSiteConfig.get_bonus_sport_func_names()
|
for x in ScoDocSiteConfig.get_bonus_sport_func_names()
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logo_header = FileField(
|
||||||
|
label="Modifier l'image:",
|
||||||
|
description="logo placé en haut des documents PDF",
|
||||||
|
validators=[
|
||||||
|
FileAllowed(
|
||||||
|
scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
||||||
|
f"n'accepte que les fichiers image <tt>{','.join([e for e in scu.LOGOS_IMAGES_ALLOWED_TYPES])}</tt>",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
logo_footer = FileField(
|
||||||
|
label="Modifier l'image:",
|
||||||
|
description="logo placé en pied des documents PDF",
|
||||||
|
validators=[
|
||||||
|
FileAllowed(
|
||||||
|
scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
||||||
|
f"n'accepte que les fichiers image <tt>{','.join([e for e in scu.LOGOS_IMAGES_ALLOWED_TYPES])}</tt>",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
submit = SubmitField("Enregistrer")
|
submit = SubmitField("Enregistrer")
|
||||||
|
|
||||||
|
|
||||||
|
# Notes pour variables config: (valeurs par défaut des paramètres de département)
|
||||||
|
# Chaines simples
|
||||||
|
# SCOLAR_FONT = "Helvetica"
|
||||||
|
# SCOLAR_FONT_SIZE = 10
|
||||||
|
# SCOLAR_FONT_SIZE_FOOT = 6
|
||||||
|
# INSTITUTION_NAME = "<b>Institut Universitaire de Technologie - Université Georges Perec</b>"
|
||||||
|
# INSTITUTION_ADDRESS = "Web <b>www.sor.bonne.top</b> - 11, rue Simon Crubelier - 75017 Paris"
|
||||||
|
# INSTITUTION_CITY = "Paris"
|
||||||
|
# Textareas:
|
||||||
|
# DEFAULT_PDF_FOOTER_TEMPLATE = "Edité par %(scodoc_name)s le %(day)s/%(month)s/%(year)s à %(hour)sh%(minute)s sur %(server_url)s"
|
||||||
|
|
||||||
|
# Booléens
|
||||||
|
# always_require_ine
|
||||||
|
|
||||||
|
# Logos:
|
||||||
|
# LOGO_FOOTER*, LOGO_HEADER*
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/ScoDoc/configuration", methods=["GET", "POST"])
|
@bp.route("/ScoDoc/configuration", methods=["GET", "POST"])
|
||||||
@admin_required
|
@admin_required
|
||||||
def configuration():
|
def configuration():
|
||||||
"Panneau de configuration général"
|
"Panneau de configuration général"
|
||||||
form = ScoDocConfigurationForm(
|
form = ScoDocConfigurationForm(
|
||||||
bonus_sport_func_name=ScoDocSiteConfig.get_bonus_sport_func_name()
|
bonus_sport_func_name=ScoDocSiteConfig.get_bonus_sport_func_name(),
|
||||||
)
|
)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
ScoDocSiteConfig.set_bonus_sport_func(form.bonus_sport_func_name.data)
|
ScoDocSiteConfig.set_bonus_sport_func(form.bonus_sport_func_name.data)
|
||||||
|
if form.logo_header.data:
|
||||||
|
sco_logos.store_image(
|
||||||
|
form.logo_header.data, os.path.join(scu.SCODOC_LOGOS_DIR, "logo_header")
|
||||||
|
)
|
||||||
|
if form.logo_footer.data:
|
||||||
|
sco_logos.store_image(
|
||||||
|
form.logo_footer.data, os.path.join(scu.SCODOC_LOGOS_DIR, "logo_footer")
|
||||||
|
)
|
||||||
|
app.clear_scodoc_cache()
|
||||||
flash(f"Configuration enregistrée")
|
flash(f"Configuration enregistrée")
|
||||||
return redirect(url_for("scodoc.index"))
|
return redirect(url_for("scodoc.index"))
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"configuration.html",
|
"configuration.html",
|
||||||
title="Configuration ScoDoc",
|
title="Configuration ScoDoc",
|
||||||
form=form,
|
form=form,
|
||||||
# bonus_sport_func_name=ScoDocSiteConfig.get_bonus_sport_func(),
|
scodoc_dept=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _return_logo(logo_type="header", scodoc_dept=""):
|
||||||
|
# stockée dans /opt/scodoc-data/config/logos donc servie manuellement ici
|
||||||
|
filename = sco_logos.get_logo_filename(logo_type, scodoc_dept)
|
||||||
|
if filename:
|
||||||
|
extension = os.path.splitext(filename)[1]
|
||||||
|
return send_file(filename, mimetype=f"image/{extension}")
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/ScoDoc/logo_header")
|
||||||
|
@bp.route("/ScoDoc/<scodoc_dept>/logo_header")
|
||||||
|
def logo_header(scodoc_dept=""):
|
||||||
|
"Image logo header"
|
||||||
|
# "/opt/scodoc-data/config/logos/logo_header")
|
||||||
|
return _return_logo(logo_type="header", scodoc_dept=scodoc_dept)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/ScoDoc/logo_footer")
|
||||||
|
@bp.route("/ScoDoc/<scodoc_dept>/logo_footer")
|
||||||
|
def logo_footer(scodoc_dept=""):
|
||||||
|
"Image logo footer"
|
||||||
|
return _return_logo(logo_type="footer", scodoc_dept=scodoc_dept)
|
||||||
|
|
||||||
|
|
||||||
# essais
|
# essais
|
||||||
# @bp.route("/testlog")
|
# @bp.route("/testlog")
|
||||||
# def testlog():
|
# def testlog():
|
||||||
|
@ -30,7 +30,7 @@ issu de ScoDoc7 / ZScolar.py
|
|||||||
|
|
||||||
Emmanuel Viennet, 2021
|
Emmanuel Viennet, 2021
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -40,15 +40,19 @@ from zipfile import ZipFile
|
|||||||
import psycopg2
|
import psycopg2
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import jsonify, url_for
|
from flask import jsonify, url_for, flash, redirect, render_template
|
||||||
from flask import current_app, g, request
|
from flask import current_app, g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_wtf.file import FileField, FileAllowed
|
||||||
|
from wtforms import SubmitField
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
from app.decorators import (
|
from app.decorators import (
|
||||||
scodoc,
|
scodoc,
|
||||||
scodoc7func,
|
scodoc7func,
|
||||||
permission_required,
|
permission_required,
|
||||||
|
permission_required_compat_scodoc7,
|
||||||
admin_required,
|
admin_required,
|
||||||
login_required,
|
login_required,
|
||||||
)
|
)
|
||||||
@ -71,8 +75,8 @@ from app.scodoc.sco_exceptions import (
|
|||||||
)
|
)
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
||||||
import sco_version
|
import sco_version
|
||||||
|
import app
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
|
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import html_sidebar
|
from app.scodoc import html_sidebar
|
||||||
from app.scodoc import imageresize
|
from app.scodoc import imageresize
|
||||||
@ -94,6 +98,7 @@ from app.scodoc import sco_formsemestre_inscriptions
|
|||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_groups_edit
|
from app.scodoc import sco_groups_edit
|
||||||
from app.scodoc import sco_groups_view
|
from app.scodoc import sco_groups_view
|
||||||
|
from app.scodoc import sco_logos
|
||||||
from app.scodoc import sco_news
|
from app.scodoc import sco_news
|
||||||
from app.scodoc import sco_page_etud
|
from app.scodoc import sco_page_etud
|
||||||
from app.scodoc import sco_parcours_dut
|
from app.scodoc import sco_parcours_dut
|
||||||
@ -201,6 +206,66 @@ def doc_preferences(REQUEST):
|
|||||||
return sco_preferences.doc_preferences()
|
return sco_preferences.doc_preferences()
|
||||||
|
|
||||||
|
|
||||||
|
class DeptLogosConfigurationForm(FlaskForm):
|
||||||
|
"Panneau de configuration logos dept"
|
||||||
|
|
||||||
|
logo_header = FileField(
|
||||||
|
label="Modifier l'image:",
|
||||||
|
description="logo placé en haut des documents PDF",
|
||||||
|
validators=[
|
||||||
|
FileAllowed(
|
||||||
|
scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
||||||
|
f"n'accepte que les fichiers image <tt>{','.join([e for e in scu.LOGOS_IMAGES_ALLOWED_TYPES])}</tt>",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
logo_footer = FileField(
|
||||||
|
label="Modifier l'image:",
|
||||||
|
description="logo placé en pied des documents PDF",
|
||||||
|
validators=[
|
||||||
|
FileAllowed(
|
||||||
|
scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
||||||
|
f"n'accepte que les fichiers image <tt>{','.join([e for e in scu.LOGOS_IMAGES_ALLOWED_TYPES])}</tt>",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
submit = SubmitField("Enregistrer")
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/config_logos", methods=["GET", "POST"])
|
||||||
|
@permission_required(Permission.ScoChangePreferences)
|
||||||
|
def config_logos(scodoc_dept):
|
||||||
|
"Panneau de configuration général"
|
||||||
|
form = DeptLogosConfigurationForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
if form.logo_header.data:
|
||||||
|
sco_logos.store_image(
|
||||||
|
form.logo_header.data,
|
||||||
|
os.path.join(
|
||||||
|
scu.SCODOC_LOGOS_DIR, "logos_" + scodoc_dept, "logo_header"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if form.logo_footer.data:
|
||||||
|
sco_logos.store_image(
|
||||||
|
form.logo_footer.data,
|
||||||
|
os.path.join(
|
||||||
|
scu.SCODOC_LOGOS_DIR, "logos_" + scodoc_dept, "logo_footer"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
app.clear_scodoc_cache()
|
||||||
|
flash(f"Logos enregistrés")
|
||||||
|
return redirect(url_for("scolar.index_html", scodoc_dept=scodoc_dept))
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"configuration.html",
|
||||||
|
title="Configuration Logos du département",
|
||||||
|
form=form,
|
||||||
|
scodoc_dept=scodoc_dept,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# ETUDIANTS
|
# ETUDIANTS
|
||||||
@ -283,7 +348,10 @@ sco_publish(
|
|||||||
)
|
)
|
||||||
|
|
||||||
sco_publish(
|
sco_publish(
|
||||||
"/trombino_copy_photos", sco_trombino.trombino_copy_photos, Permission.ScoView
|
"/trombino_copy_photos",
|
||||||
|
sco_trombino.trombino_copy_photos,
|
||||||
|
Permission.ScoView,
|
||||||
|
methods=["GET", "POST"],
|
||||||
)
|
)
|
||||||
|
|
||||||
sco_publish("/groups_view", sco_groups_view.groups_view, Permission.ScoView)
|
sco_publish("/groups_view", sco_groups_view.groups_view, Permission.ScoView)
|
||||||
@ -332,10 +400,18 @@ def search_etud_by_name():
|
|||||||
|
|
||||||
|
|
||||||
# XMLgetEtudInfos était le nom dans l'ancienne API ScoDoc 6
|
# XMLgetEtudInfos était le nom dans l'ancienne API ScoDoc 6
|
||||||
@bp.route("/etud_info")
|
@bp.route("/etud_info", methods=["GET", "POST"]) # pour compat anciens clients PHP)
|
||||||
@bp.route("/XMLgetEtudInfos")
|
@bp.route(
|
||||||
|
"/XMLgetEtudInfos", methods=["GET", "POST"]
|
||||||
|
) # pour compat anciens clients PHP)
|
||||||
|
@bp.route(
|
||||||
|
"/Absences/XMLgetEtudInfos", methods=["GET", "POST"]
|
||||||
|
) # pour compat anciens clients PHP
|
||||||
|
@bp.route(
|
||||||
|
"/Notes/XMLgetEtudInfos", methods=["GET", "POST"]
|
||||||
|
) # pour compat anciens clients PHP
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def etud_info(etudid=None, format="xml", REQUEST=None):
|
def etud_info(etudid=None, format="xml", REQUEST=None):
|
||||||
"Donne les informations sur un etudiant"
|
"Donne les informations sur un etudiant"
|
||||||
|
@ -121,7 +121,9 @@ def create_user_form(REQUEST, user_name=None, edit=0):
|
|||||||
is_super_admin = True
|
is_super_admin = True
|
||||||
|
|
||||||
# Les rôles standards créés à l'initialisation de ScoDoc:
|
# Les rôles standards créés à l'initialisation de ScoDoc:
|
||||||
standard_roles = [Role.get_named_role(r) for r in ("Ens", "Secr", "Admin")]
|
standard_roles = [
|
||||||
|
Role.get_named_role(r) for r in ("Ens", "Secr", "Admin", "RespPe")
|
||||||
|
]
|
||||||
# Rôles pouvant etre attribués aux utilisateurs via ce dialogue:
|
# Rôles pouvant etre attribués aux utilisateurs via ce dialogue:
|
||||||
# si SuperAdmin, tous les rôles standards dans tous les départements
|
# si SuperAdmin, tous les rôles standards dans tous les départements
|
||||||
# sinon, les départements dans lesquels l'utilisateur a le droit
|
# sinon, les départements dans lesquels l'utilisateur a le droit
|
||||||
|
@ -30,9 +30,9 @@ class Config:
|
|||||||
SCODOC_DIR = os.environ.get("SCODOC_DIR", "/opt/scodoc")
|
SCODOC_DIR = os.environ.get("SCODOC_DIR", "/opt/scodoc")
|
||||||
SCODOC_VAR_DIR = os.environ.get("SCODOC_VAR_DIR", "/opt/scodoc-data")
|
SCODOC_VAR_DIR = os.environ.get("SCODOC_VAR_DIR", "/opt/scodoc-data")
|
||||||
SCODOC_LOG_FILE = os.path.join(SCODOC_VAR_DIR, "log", "scodoc.log")
|
SCODOC_LOG_FILE = os.path.join(SCODOC_VAR_DIR, "log", "scodoc.log")
|
||||||
# For legacy ScoDoc7 installs: postgresql user
|
#
|
||||||
SCODOC7_SQL_USER = os.environ.get("SCODOC7_SQL_USER", "www-data")
|
MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # Flask uploads
|
||||||
DEFAULT_SQL_PORT = os.environ.get("DEFAULT_SQL_PORT", "5432")
|
|
||||||
# STATIC_URL_PATH = "/ScoDoc/static"
|
# STATIC_URL_PATH = "/ScoDoc/static"
|
||||||
# static_folder = "stat"
|
# static_folder = "stat"
|
||||||
# SERVER_NAME = os.environ.get("SERVER_NAME")
|
# SERVER_NAME = os.environ.get("SERVER_NAME")
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
"""ScoDoc 9.0.13: essai cascade
|
||||||
|
|
||||||
|
Revision ID: a217bf588f4c
|
||||||
|
Revises: f73251d1d825
|
||||||
|
Create Date: 2021-09-10 21:44:34.947317
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'a217bf588f4c'
|
||||||
|
down_revision = 'f73251d1d825'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('notes_semset_formsemestre', 'semset_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=False)
|
||||||
|
op.drop_constraint('notes_semset_formsemestre_semset_id_fkey', 'notes_semset_formsemestre', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'notes_semset_formsemestre', 'notes_semset', ['semset_id'], ['id'], ondelete='CASCADE')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint(None, 'notes_semset_formsemestre', type_='foreignkey')
|
||||||
|
op.create_foreign_key('notes_semset_formsemestre_semset_id_fkey', 'notes_semset_formsemestre', 'notes_semset', ['semset_id'], ['id'])
|
||||||
|
op.alter_column('notes_semset_formsemestre', 'semset_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=True)
|
||||||
|
# ### end Alembic commands ###
|
@ -36,7 +36,7 @@ class ScoError(Exception):
|
|||||||
|
|
||||||
def GET(s, path, errmsg=None):
|
def GET(s, path, errmsg=None):
|
||||||
"""Get and returns as JSON"""
|
"""Get and returns as JSON"""
|
||||||
r = s.get(BASEURL + "/" + path)
|
r = s.get(BASEURL + "/" + path, verify=CHECK_CERTIFICATE)
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
raise ScoError(errmsg or "erreur !")
|
raise ScoError(errmsg or "erreur !")
|
||||||
return r.json() # decode la reponse JSON
|
return r.json() # decode la reponse JSON
|
||||||
@ -44,7 +44,7 @@ def GET(s, path, errmsg=None):
|
|||||||
|
|
||||||
def POST(s, path, data, errmsg=None):
|
def POST(s, path, data, errmsg=None):
|
||||||
"""Post"""
|
"""Post"""
|
||||||
r = s.post(BASEURL + "/" + path, data=data)
|
r = s.post(BASEURL + "/" + path, data=data, verify=CHECK_CERTIFICATE)
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
raise ScoError(errmsg or "erreur !")
|
raise ScoError(errmsg or "erreur !")
|
||||||
return r.text
|
return r.text
|
||||||
@ -52,6 +52,10 @@ def POST(s, path, data, errmsg=None):
|
|||||||
|
|
||||||
# --- Ouverture session (login)
|
# --- Ouverture session (login)
|
||||||
s = requests.Session()
|
s = requests.Session()
|
||||||
|
s.post(
|
||||||
|
"https://deb11.viennet.net/api/auth/login",
|
||||||
|
data={"user_name": USER, "password": PASSWORD},
|
||||||
|
)
|
||||||
r = s.get(BASEURL, auth=(USER, PASSWORD), verify=CHECK_CERTIFICATE)
|
r = s.get(BASEURL, auth=(USER, PASSWORD), verify=CHECK_CERTIFICATE)
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
raise ScoError("erreur de connection: vérifier adresse et identifiants")
|
raise ScoError("erreur de connection: vérifier adresse et identifiants")
|
||||||
|
@ -18,6 +18,7 @@ Flask==2.0.1
|
|||||||
Flask-Babel==2.0.0
|
Flask-Babel==2.0.0
|
||||||
Flask-Bootstrap==3.3.7.1
|
Flask-Bootstrap==3.3.7.1
|
||||||
Flask-Caching==1.10.1
|
Flask-Caching==1.10.1
|
||||||
|
Flask-HTTPAuth==4.4.0
|
||||||
Flask-Login==0.5.0
|
Flask-Login==0.5.0
|
||||||
Flask-Mail==0.9.1
|
Flask-Mail==0.9.1
|
||||||
Flask-Migrate==3.1.0
|
Flask-Migrate==3.1.0
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.0.8"
|
SCOVERSION = "9.0.13"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
18
scodoc.py
18
scodoc.py
@ -188,24 +188,6 @@ def create_dept(dept): # create-dept
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@app.cli.command()
|
|
||||||
@click.argument("filename")
|
|
||||||
@with_appcontext
|
|
||||||
def test_interactive(filename=None):
|
|
||||||
"Run interactive test"
|
|
||||||
import flask_login
|
|
||||||
from app import decorators
|
|
||||||
|
|
||||||
click.echo("Executing {}".format(filename))
|
|
||||||
with app.test_request_context(""):
|
|
||||||
u = User.query.first()
|
|
||||||
flask_login.login_user(u)
|
|
||||||
REQUEST = decorators.ZRequest()
|
|
||||||
exec(open(filename).read())
|
|
||||||
|
|
||||||
click.echo("Done.")
|
|
||||||
|
|
||||||
|
|
||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
def import_scodoc7_users(): # import-scodoc7-users
|
def import_scodoc7_users(): # import-scodoc7-users
|
||||||
|
@ -40,11 +40,11 @@ from app.scodoc.sco_exceptions import ScoValueError
|
|||||||
random.seed(12345) # tests reproductibles
|
random.seed(12345) # tests reproductibles
|
||||||
|
|
||||||
|
|
||||||
DEMO_DIR = Config.SCODOC_DIR + "/tools/demo/"
|
NOMS_DIR = Config.SCODOC_DIR + "/tools/fakeportal/nomsprenoms"
|
||||||
NOMS = [x.strip() for x in open(DEMO_DIR + "/noms.txt").readlines()]
|
NOMS = [x.strip() for x in open(NOMS_DIR + "/noms.txt").readlines()]
|
||||||
PRENOMS_H = [x.strip() for x in open(DEMO_DIR + "/prenoms-h.txt").readlines()]
|
PRENOMS_H = [x.strip() for x in open(NOMS_DIR + "/prenoms-h.txt").readlines()]
|
||||||
PRENOMS_F = [x.strip() for x in open(DEMO_DIR + "/prenoms-f.txt").readlines()]
|
PRENOMS_F = [x.strip() for x in open(NOMS_DIR + "/prenoms-f.txt").readlines()]
|
||||||
PRENOMS_X = [x.strip() for x in open(DEMO_DIR + "/prenoms-x.txt").readlines()]
|
PRENOMS_X = [x.strip() for x in open(NOMS_DIR + "/prenoms-x.txt").readlines()]
|
||||||
|
|
||||||
|
|
||||||
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
|
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
|
||||||
|
@ -73,6 +73,8 @@ def test_preferences(test_client):
|
|||||||
assert len(prefs2) == len(prefs)
|
assert len(prefs2) == len(prefs)
|
||||||
prefs2.set(None, "abs_notification_mail_tmpl", "toto")
|
prefs2.set(None, "abs_notification_mail_tmpl", "toto")
|
||||||
assert prefs2.get(None, "abs_notification_mail_tmpl") == "toto"
|
assert prefs2.get(None, "abs_notification_mail_tmpl") == "toto"
|
||||||
|
# Vérifie que les prefs sont bien sur un seul département:
|
||||||
|
app.set_sco_dept(current_dept.acronym)
|
||||||
assert prefs.get(None, "abs_notification_mail_tmpl") != "toto"
|
assert prefs.get(None, "abs_notification_mail_tmpl") != "toto"
|
||||||
orm_val = (
|
orm_val = (
|
||||||
ScoPreference.query.filter_by(dept_id=d.id, name="abs_notification_mail_tmpl")
|
ScoPreference.query.filter_by(dept_id=d.id, name="abs_notification_mail_tmpl")
|
||||||
@ -82,6 +84,7 @@ def test_preferences(test_client):
|
|||||||
assert orm_val == "toto"
|
assert orm_val == "toto"
|
||||||
# --- Preferences d'un semestre
|
# --- Preferences d'un semestre
|
||||||
# rejoue ce test pour avoir un semestre créé
|
# rejoue ce test pour avoir un semestre créé
|
||||||
|
app.set_sco_dept("D2")
|
||||||
test_sco_basic.run_sco_basic()
|
test_sco_basic.run_sco_basic()
|
||||||
sem = sco_formsemestre.do_formsemestre_list()[0]
|
sem = sco_formsemestre.do_formsemestre_list()[0]
|
||||||
formsemestre_id = sem["formsemestre_id"]
|
formsemestre_id = sem["formsemestre_id"]
|
||||||
|
@ -108,8 +108,7 @@ change_scodoc_file_ownership
|
|||||||
# ------------ CREATION BASE DE DONNEES
|
# ------------ CREATION BASE DE DONNEES
|
||||||
echo
|
echo
|
||||||
echo "Voulez-vous créer la base SQL SCODOC ?"
|
echo "Voulez-vous créer la base SQL SCODOC ?"
|
||||||
echo "répondre oui sauf si vous avez déjà une base existante"
|
echo "(répondre oui sauf si vous savez vraiment ce que vous faites)"
|
||||||
echo "que vous souhaitez conserver (mais pour les migrations, répondre oui)."
|
|
||||||
echo -n 'Créer la base de données SCODOC ? (y/n) [y] '
|
echo -n 'Créer la base de données SCODOC ? (y/n) [y] '
|
||||||
read -r ans
|
read -r ans
|
||||||
if [ "$(norm_ans "$ans")" != 'N' ]
|
if [ "$(norm_ans "$ans")" != 'N' ]
|
||||||
@ -121,9 +120,10 @@ then
|
|||||||
echo
|
echo
|
||||||
echo "Création des tables et du compte admin"
|
echo "Création des tables et du compte admin"
|
||||||
echo
|
echo
|
||||||
su -c "(cd /opt/scodoc; source venv/bin/activate; flask db upgrade; flask sco-db-init; flask user-password admin)" "$SCODOC_USER" || die "Erreur: sco-db-init"
|
msg="Saisir le mot de passe de l\'administrateur \(admin\):"
|
||||||
|
su -c "(cd /opt/scodoc; source venv/bin/activate; flask db upgrade; flask sco-db-init; echo; echo $msg; flask user-password admin)" "$SCODOC_USER" || die "Erreur: sco-db-init"
|
||||||
echo
|
echo
|
||||||
echo "base initialisée et admin créé."
|
echo "Base initialisée et admin créé."
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -134,6 +134,7 @@ systemctl start scodoc9
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Service configuré et démarré."
|
echo "Service configuré et démarré."
|
||||||
|
echo "Vous pouvez vous connecter en web et vous identifier comme \"admin\"."
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
|
||||||
|
2
tools/debian/postinst
Normal file → Executable file
2
tools/debian/postinst
Normal file → Executable file
@ -29,7 +29,7 @@ do
|
|||||||
/usr/sbin/locale-gen --keep-existing
|
/usr/sbin/locale-gen --keep-existing
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
echo "debian postinst: scodoc9 is $(systemctl is-active scodoc9)"
|
echo "debian postinst: scodoc9 systemd service is $(systemctl is-active scodoc9)"
|
||||||
# On a besoin d'un postgresql lancé pour la mise à jour
|
# On a besoin d'un postgresql lancé pour la mise à jour
|
||||||
systemctl restart postgresql
|
systemctl restart postgresql
|
||||||
|
|
||||||
|
19
tools/debian/postrm
Executable file
19
tools/debian/postrm
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Déinstallation de scodoc
|
||||||
|
# Ne touche pas aux données (/opt/scodoc-data)
|
||||||
|
# N'enlève complètement /opt/scodoc qui si --purge
|
||||||
|
|
||||||
|
systemctl stop scodoc9
|
||||||
|
systemctl disable scodoc9
|
||||||
|
|
||||||
|
if [ "$#" == 1 ] && [ "$1" == "purge" ]
|
||||||
|
then
|
||||||
|
/bin/rm -rf /opt/scodoc
|
||||||
|
/bin/rm -f scodoc9.service
|
||||||
|
/bin/rm -f /etc/systemd/system/scodoc-updater.service
|
||||||
|
/bin/rm -f /etc/systemd/system/scodoc-updater.timer
|
||||||
|
/bin/rm -f /etc/nginx/sites-enabled/scodoc9.nginx
|
||||||
|
fi
|
||||||
|
|
||||||
|
systemctl reload nginx
|
0
tools/debian/preinst
Normal file → Executable file
0
tools/debian/preinst
Normal file → Executable file
@ -20,7 +20,7 @@ User=scodoc
|
|||||||
Group=scodoc
|
Group=scodoc
|
||||||
WorkingDirectory=/opt/scodoc
|
WorkingDirectory=/opt/scodoc
|
||||||
#Environment=FLASK_ENV=production
|
#Environment=FLASK_ENV=production
|
||||||
ExecStart=/opt/scodoc/venv/bin/gunicorn -b localhost:8000 -w 4 scodoc:app
|
ExecStart=/opt/scodoc/venv/bin/gunicorn -b localhost:8000 -w 4 --timeout 600 scodoc:app
|
||||||
Restart=always
|
Restart=always
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
@ -100,7 +100,7 @@ class MyHttpRequestHandler(http.server.SimpleHTTPRequestHandler):
|
|||||||
|
|
||||||
if "etapes" in self.path.lower():
|
if "etapes" in self.path.lower():
|
||||||
self.path = str(Path(script_dir / "etapes.xml").relative_to(Path.cwd()))
|
self.path = str(Path(script_dir / "etapes.xml").relative_to(Path.cwd()))
|
||||||
elif "scodocEtudiant" in self.path:
|
elif "scodocEtudiant" in self.path: # API v2
|
||||||
# 2 forms: nip=xxx or etape=eee&annee=aaa
|
# 2 forms: nip=xxx or etape=eee&annee=aaa
|
||||||
if "nip" in query_components:
|
if "nip" in query_components:
|
||||||
nip = query_components["nip"][0]
|
nip = query_components["nip"][0]
|
||||||
|
@ -59,7 +59,7 @@ def import_scodoc7_user_db(scodoc7_db="dbname=SCOUSERS"):
|
|||||||
roles7 = []
|
roles7 = []
|
||||||
for role_dept in roles7:
|
for role_dept in roles7:
|
||||||
# Migre les rôles RespPeX, EnsX, AdminX, SecrX et ignore les autres
|
# Migre les rôles RespPeX, EnsX, AdminX, SecrX et ignore les autres
|
||||||
m = re.match(r"^(-?Ens|-?Secr|-?ResPe|-?Admin)(.*)$", role_dept)
|
m = re.match(r"^(-?Ens|-?Secr|-?RespPe|-?Admin)(.*)$", role_dept)
|
||||||
if not m:
|
if not m:
|
||||||
msg = f"User {user_name}: role inconnu '{role_dept}' (ignoré)"
|
msg = f"User {user_name}: role inconnu '{role_dept}' (ignoré)"
|
||||||
current_app.logger.warning(msg)
|
current_app.logger.warning(msg)
|
||||||
@ -75,7 +75,7 @@ def import_scodoc7_user_db(scodoc7_db="dbname=SCOUSERS"):
|
|||||||
dept = m.group(2)
|
dept = m.group(2)
|
||||||
role = Role.query.filter_by(name=role_name).first()
|
role = Role.query.filter_by(name=role_name).first()
|
||||||
if not role:
|
if not role:
|
||||||
msg = f"User {user_name}: ignoring role '{role_dept}'"
|
msg = f"Role '{role_name}' introuvable. User {user_name}: ignoring role '{role_dept}'"
|
||||||
current_app.logger.warning(msg)
|
current_app.logger.warning(msg)
|
||||||
messages.append(msg)
|
messages.append(msg)
|
||||||
else:
|
else:
|
||||||
|
@ -23,10 +23,6 @@ CONFIG = CFG()
|
|||||||
|
|
||||||
CONFIG.always_require_ine = 0 # set to 1 if you want to require INE
|
CONFIG.always_require_ine = 0 # set to 1 if you want to require INE
|
||||||
|
|
||||||
# The base URL, use only if you are behind a proxy
|
|
||||||
# eg "https://scodoc.example.net/ScoDoc"
|
|
||||||
CONFIG.ABSOLUTE_URL = ""
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# ------------- Documents PDF -------------
|
# ------------- Documents PDF -------------
|
||||||
#
|
#
|
||||||
@ -78,6 +74,7 @@ CONFIG.DEFAULT_PDF_FOOTER_TEMPLATE = "Edité par %(scodoc_name)s le %(day)s/%(mo
|
|||||||
#
|
#
|
||||||
# - règle "LMD": capitalisation uniquement des UE avec moy. > 10
|
# - règle "LMD": capitalisation uniquement des UE avec moy. > 10
|
||||||
|
|
||||||
|
# XXX à revoir pour le BUT: variable à intégrer aux parcours
|
||||||
CONFIG.CAPITALIZE_ALL_UES = (
|
CONFIG.CAPITALIZE_ALL_UES = (
|
||||||
True # si vrai, capitalise toutes les UE des semestres validés (règle "LMD").
|
True # si vrai, capitalise toutes les UE des semestres validés (règle "LMD").
|
||||||
)
|
)
|
||||||
@ -86,7 +83,7 @@ CONFIG.CAPITALIZE_ALL_UES = (
|
|||||||
#
|
#
|
||||||
# -----------------------------------------------------
|
# -----------------------------------------------------
|
||||||
#
|
#
|
||||||
# -------------- Personnalisation des pages
|
# -------------- Personnalisation des pages (DEPRECATED)
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------
|
# -----------------------------------------------------
|
||||||
# Nom (chemin complet) d'un fichier .html à inclure juste après le <body>
|
# Nom (chemin complet) d'un fichier .html à inclure juste après le <body>
|
||||||
|
Loading…
Reference in New Issue
Block a user