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 users_bp
|
||||
from app.views import absences_bp
|
||||
from app.api import bp as api_bp
|
||||
|
||||
# https://scodoc.fr/ScoDoc
|
||||
app.register_blueprint(scodoc_bp)
|
||||
@ -130,6 +131,7 @@ def create_app(config_class=DevConfig):
|
||||
app.register_blueprint(
|
||||
absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences"
|
||||
)
|
||||
app.register_blueprint(api_bp, url_prefix="/ScoDoc/api")
|
||||
scodoc_exc_formatter = RequestFormatter(
|
||||
"[%(asctime)s] %(remote_addr)s requested %(url)s\n"
|
||||
"%(levelname)s in %(module)s: %(message)s"
|
||||
@ -190,9 +192,7 @@ def create_app(config_class=DevConfig):
|
||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
|
||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
|
||||
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
|
||||
|
||||
|
||||
|
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():
|
||||
user = User.query.filter_by(user_name=form.user_name.data).first()
|
||||
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"))
|
||||
return redirect(url_for("auth.login"))
|
||||
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")
|
||||
if not next_page or url_parse(next_page).netloc != "":
|
||||
next_page = url_for("scodoc.index")
|
||||
|
@ -16,8 +16,10 @@ from flask import request
|
||||
from flask_login import current_user
|
||||
from flask_login import login_required
|
||||
from flask import current_app
|
||||
import flask_login
|
||||
|
||||
import app
|
||||
from app.auth.models import User
|
||||
|
||||
|
||||
class ZUser(object):
|
||||
@ -141,6 +143,48 @@ def permission_required(permission):
|
||||
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):
|
||||
from app.auth.models import Permission
|
||||
|
||||
|
@ -34,3 +34,13 @@ class Departement(db.Model):
|
||||
|
||||
def __repr__(self):
|
||||
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)
|
||||
|
||||
|
||||
# Association:
|
||||
# Association: many to many
|
||||
notes_semset_formsemestre = db.Table(
|
||||
"notes_semset_formsemestre",
|
||||
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"),
|
||||
)
|
||||
|
@ -33,6 +33,17 @@ class ScoDocSiteConfig(db.Model):
|
||||
value = db.Column(db.Text())
|
||||
|
||||
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):
|
||||
self.name = name
|
||||
@ -41,6 +52,13 @@ class ScoDocSiteConfig(db.Model):
|
||||
def __repr__(self):
|
||||
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
|
||||
def set_bonus_sport_func(cls, func_name):
|
||||
"""Record bonus_sport config.
|
||||
|
@ -83,22 +83,19 @@ def sidebar():
|
||||
from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_etud
|
||||
|
||||
params = {
|
||||
"ScoURL": scu.ScoURL(),
|
||||
"SCO_USER_MANUAL": scu.SCO_USER_MANUAL,
|
||||
}
|
||||
params = {}
|
||||
|
||||
H = ['<div class="sidebar">', sidebar_common()]
|
||||
|
||||
H.append(
|
||||
"""<div class="box-chercheetud">Chercher étudiant:<br/>
|
||||
<form method="get" id="form-chercheetud" action="%(ScoURL)s/search_etud_in_dept">
|
||||
<div><input type="text" size="12" id="in-expnom" name="expnom" spellcheck="false"></input></div>
|
||||
</form></div>
|
||||
<div class="etud-insidebar">
|
||||
"""
|
||||
% params
|
||||
)
|
||||
H = [
|
||||
f"""<div class="sidebar">
|
||||
{ sidebar_common() }
|
||||
<div class="box-chercheetud">Chercher étudiant:<br/>
|
||||
<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>
|
||||
</form></div>
|
||||
<div class="etud-insidebar">
|
||||
"""
|
||||
]
|
||||
# ---- Il y-a-t-il un etudiant selectionné ?
|
||||
etudid = None
|
||||
if request.method == "GET":
|
||||
@ -121,59 +118,50 @@ def sidebar():
|
||||
% params
|
||||
)
|
||||
if etud["cursem"]:
|
||||
params["nbabs"], params["nbabsjust"] = sco_abs.get_abs_count(
|
||||
etudid, etud["cursem"]
|
||||
)
|
||||
params["nbabsnj"] = params["nbabs"] - params["nbabsjust"]
|
||||
params["date_debut"] = etud["cursem"]["date_debut"]
|
||||
params["date_fin"] = etud["cursem"]["date_fin"]
|
||||
cur_sem = etud["cursem"]
|
||||
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, cur_sem)
|
||||
nbabsnj = nbabs - nbabsjust
|
||||
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>"""
|
||||
% params
|
||||
f"""<span title="absences du { cur_sem["date_debut"] } au { cur_sem["date_fin"] }">(1/2 j.)
|
||||
<br/>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
|
||||
)
|
||||
|
||||
H.append("<ul>")
|
||||
if current_user.has_permission(Permission.ScoAbsChange):
|
||||
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"):
|
||||
H.append(
|
||||
"""<li> <a href="%(ScoURL)s/Absences/listeBilletsEtud?etudid=%(etudid)s">Billets</a></li>"""
|
||||
% params
|
||||
f"""<li><a href="{ url_for('absences.listeBilletsEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Billets</a></li>"""
|
||||
)
|
||||
H.append(
|
||||
f"""
|
||||
<li><a href="{ url_for('absences.CalAbs', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Calendrier</a></li>
|
||||
<li><a href="{ url_for('absences.ListeAbsEtud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Liste</a></li>
|
||||
</ul>
|
||||
"""
|
||||
<li> <a href="%(ScoURL)s/Absences/CalAbs?etudid=%(etudid)s">Calendrier</a></li>
|
||||
<li> <a href="%(ScoURL)s/Absences/ListeAbsEtud?etudid=%(etudid)s">Liste</a></li>
|
||||
</ul>
|
||||
"""
|
||||
% params
|
||||
)
|
||||
else:
|
||||
pass # H.append("(pas d'étudiant en cours)")
|
||||
# ---------
|
||||
H.append("</div>") # /etud-insidebar
|
||||
# Logo
|
||||
scologo_img = scu.icontag("scologo_img")
|
||||
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/>
|
||||
<a href="{ scu.SCO_USER_MANUAL }" target="_blank" class="sidebar">Aide</a>
|
||||
</div></div>
|
||||
<div class="logo-logo"><a href= { url_for( 'scolar.about', scodoc_dept=g.scodoc_dept ) }
|
||||
">{ scu.icontag("scologo_img", no_size=True) }</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of sidebar -->
|
||||
"""
|
||||
)
|
||||
H.append(
|
||||
"""<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> <!-- end of sidebar -->
|
||||
"""
|
||||
% params
|
||||
) # '
|
||||
#
|
||||
return "".join(H)
|
||||
|
||||
|
||||
|
@ -287,7 +287,7 @@ class EditableTable(object):
|
||||
input_formators={},
|
||||
aux_tables=[],
|
||||
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 ] }
|
||||
filter_nulls=True, # dont allow to set fields to null
|
||||
filter_dept=False, # ajoute selection sur g.scodoc_dept_id
|
||||
@ -321,8 +321,10 @@ class EditableTable(object):
|
||||
del vals["id"]
|
||||
if self.filter_dept:
|
||||
vals["dept_id"] = g.scodoc_dept_id
|
||||
if self.html_quote:
|
||||
quote_dict(vals) # quote all HTML markup
|
||||
if (
|
||||
self.html_quote
|
||||
): # quote all HTML markup (une bien mauvaise idée venue des ages obscurs)
|
||||
quote_dict(vals)
|
||||
# format value
|
||||
for title in vals:
|
||||
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
|
||||
avec calcul du rang
|
||||
@ -181,7 +181,9 @@ class TableTag(object):
|
||||
lesMoyennesTriees = sorted(
|
||||
listMoyEtCoeff,
|
||||
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
|
||||
self.rangs[tag] = notes_table.comp_ranks(lesMoyennesTriees) # les rangs
|
||||
|
||||
|
@ -313,7 +313,7 @@ def do_formsemestre_archive(
|
||||
# tous les inscrits du semestre
|
||||
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
||||
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
|
||||
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
|
||||
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
group_ids, formsemestre_id=formsemestre_id, REQUEST=REQUEST
|
||||
group_ids, formsemestre_id=formsemestre_id
|
||||
)
|
||||
|
||||
H = [
|
||||
|
@ -183,7 +183,7 @@ def _sem_table(sems):
|
||||
"""Affiche liste des semestres, utilisée pour semestres en cours"""
|
||||
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="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>
|
||||
<span class="respsem">(%(responsable_name)s)</span>
|
||||
</td>
|
||||
@ -196,7 +196,7 @@ def _sem_table(sems):
|
||||
H = ['<table class="listesems">']
|
||||
for modalite in modalites:
|
||||
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"]]:
|
||||
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):
|
||||
"""Conversion d'une date Excel en date
|
||||
Peut lever une ValueError
|
||||
"""
|
||||
return openpyxl.utils.datetime.from_ISO8601(xldate)
|
||||
|
||||
|
||||
|
@ -120,10 +120,11 @@ def search_etud_in_dept(expnom=""):
|
||||
if etudid is not None:
|
||||
etuds = sco_etud.get_etud_info(filled=True, etudid=expnom)
|
||||
if (etudid is None) or len(etuds) != 1:
|
||||
if scu.is_valid_code_nip(expnom):
|
||||
etuds = search_etuds_infos(code_nip=expnom)
|
||||
expnom_str = str(expnom)
|
||||
if scu.is_valid_code_nip(expnom_str):
|
||||
etuds = search_etuds_infos(code_nip=expnom_str)
|
||||
else:
|
||||
etuds = search_etuds_infos(expnom=expnom)
|
||||
etuds = search_etuds_infos(expnom=expnom_str)
|
||||
else:
|
||||
etuds = [] # si expnom est trop court, n'affiche rien
|
||||
|
||||
@ -151,7 +152,7 @@ def search_etud_in_dept(expnom=""):
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Recherche d'un étudiant",
|
||||
no_side_bar=True,
|
||||
no_side_bar=False,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
)
|
||||
@ -250,10 +251,12 @@ def search_etud_by_name(term: str) -> list:
|
||||
r = ndb.SimpleDictFetch(
|
||||
"""SELECT nom, prenom, code_nip
|
||||
FROM identite
|
||||
WHERE code_nip
|
||||
LIKE %(beginning)s ORDER BY nom
|
||||
WHERE
|
||||
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 = [
|
||||
{
|
||||
@ -267,10 +270,12 @@ def search_etud_by_name(term: str) -> list:
|
||||
r = ndb.SimpleDictFetch(
|
||||
"""SELECT id AS etudid, nom, prenom
|
||||
FROM identite
|
||||
WHERE nom LIKE %(beginning)s
|
||||
WHERE
|
||||
dept_id = %(dept_id)s
|
||||
AND nom LIKE %(beginning)s
|
||||
ORDER BY nom
|
||||
""",
|
||||
{"beginning": term + "%"},
|
||||
{"beginning": term + "%", "dept_id": g.scodoc_dept_id},
|
||||
)
|
||||
|
||||
data = [
|
||||
|
@ -34,12 +34,12 @@
|
||||
import collections
|
||||
import datetime
|
||||
import operator
|
||||
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
|
||||
import urllib
|
||||
from urllib.parse import parse_qs
|
||||
import time
|
||||
|
||||
|
||||
from flask import url_for, g
|
||||
from flask import url_for, g, request
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import html_sco_header
|
||||
@ -86,7 +86,6 @@ def groups_view(
|
||||
group_ids,
|
||||
formsemestre_id=formsemestre_id,
|
||||
etat=etat,
|
||||
REQUEST=REQUEST,
|
||||
select_all_when_unspecified=True,
|
||||
)
|
||||
# Formats spéciaux: download direct
|
||||
@ -301,7 +300,6 @@ class DisplayedGroupsInfos(object):
|
||||
etat=None,
|
||||
select_all_when_unspecified=False,
|
||||
moduleimpl_id=None, # used to find formsemestre when unspecified
|
||||
REQUEST=None,
|
||||
):
|
||||
if isinstance(group_ids, int):
|
||||
if group_ids:
|
||||
@ -334,7 +332,7 @@ class DisplayedGroupsInfos(object):
|
||||
for group_id in group_ids:
|
||||
gq.append("group_ids=" + str(group_id))
|
||||
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.groups = []
|
||||
groups_titles = []
|
||||
@ -918,7 +916,7 @@ def form_choix_saisie_semaine(groups_infos, REQUEST=None):
|
||||
del query_args["head_message"]
|
||||
destination = "%s?%s" % (
|
||||
REQUEST.URL,
|
||||
six.moves.urllib.parse.urlencode(query_args, True),
|
||||
urllib.parse.urlencode(query_args, True),
|
||||
)
|
||||
destination = destination.replace(
|
||||
"%", "%%"
|
||||
|
@ -157,7 +157,6 @@ def sco_import_generate_excel_sample(
|
||||
exclude_cols=[],
|
||||
extra_cols=[],
|
||||
group_ids=[],
|
||||
REQUEST=None,
|
||||
):
|
||||
"""Generates an excel document based on format fmt
|
||||
(format is the result of sco_import_format())
|
||||
@ -188,7 +187,7 @@ def sco_import_generate_excel_sample(
|
||||
titles += extra_cols
|
||||
titlesStyles += [style] * len(extra_cols)
|
||||
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
|
||||
log(
|
||||
"sco_import_generate_excel_sample: group_ids=%s %d members"
|
||||
@ -378,8 +377,12 @@ def scolars_import_excel_file(
|
||||
# Excel date conversion:
|
||||
if titleslist[i].lower() == "date_naissance":
|
||||
if val:
|
||||
# if re.match(r"^[0-9]*\.?[0-9]*$", str(val)):
|
||||
try:
|
||||
val = sco_excel.xldate_as_datetime(val)
|
||||
except ValueError:
|
||||
raise ScoValueError(
|
||||
f"date invalide ({val}) sur ligne {linenum}, colonne {titleslist[i]}"
|
||||
)
|
||||
# INE
|
||||
if (
|
||||
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):
|
||||
"create a moduleimpl_inscription"
|
||||
cnx = ndb.GetDBConnexion()
|
||||
log("do_moduleimpl_inscription_create: " + str(args))
|
||||
r = _moduleimpl_inscriptionEditor.create(cnx, args)
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=formsemestre_id
|
||||
|
@ -33,6 +33,7 @@
|
||||
|
||||
En ScoDoc 9, ce n'est pas nécessaire car on est multiptocessus / monothread.
|
||||
"""
|
||||
import html
|
||||
import io
|
||||
import os
|
||||
import queue
|
||||
@ -85,7 +86,11 @@ def SU(s):
|
||||
# car les "combining accents" ne sont pas traités par ReportLab mais peuvent
|
||||
# nous être envoyés par certains navigateurs ou imports
|
||||
# (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):
|
||||
|
@ -111,7 +111,7 @@ get_base_preferences(formsemestre_id)
|
||||
|
||||
"""
|
||||
import flask
|
||||
from flask import g
|
||||
from flask import g, url_for
|
||||
|
||||
from app.models import Departement
|
||||
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)
|
||||
|
||||
|
||||
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 = (
|
||||
# sur page "Paramètres"
|
||||
("general", {}),
|
||||
@ -472,9 +518,15 @@ class BasePreferences(object):
|
||||
--- Ceci est un message de notification automatique issu de ScoDoc ---
|
||||
|
||||
L'étudiant %(nomprenom)s
|
||||
L'étudiant %(nomprenom)s
|
||||
L'étudiant %(nomprenom)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
|
||||
et %(nbabsnonjust)s absences NON justifiées.
|
||||
|
||||
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;
|
||||
</para>
|
||||
<para><bullet>-</bullet>
|
||||
<para><bullet>-</bullet>
|
||||
<para><bullet>-</bullet>
|
||||
vu l'arrêté n° %(Decnum)s du Président de l'%(UnivName)s;
|
||||
</para>
|
||||
<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;
|
||||
""",
|
||||
"title": """Paragraphe d'introduction sur le PV""",
|
||||
@ -981,6 +1037,8 @@ class BasePreferences(object):
|
||||
<para spaceBefore="10mm" fontSize="14" leftindent="0">
|
||||
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.
|
||||
</para>
|
||||
<para fontSize="14" leftindent="0">Les décisions vous concernant sont :
|
||||
</para>
|
||||
@ -1799,8 +1857,7 @@ class BasePreferences(object):
|
||||
def load(self):
|
||||
"""Load all preferences from db"""
|
||||
log(f"loading preferences for dept_id={self.dept_id}")
|
||||
try:
|
||||
scu.GSL.acquire()
|
||||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
preflist = self._editor.list(cnx, {"dept_id": self.dept_id})
|
||||
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:
|
||||
if p["name"] not in self.prefs_dict:
|
||||
continue
|
||||
|
||||
# Convert types:
|
||||
if (
|
||||
p["name"] in self.prefs_dict
|
||||
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)
|
||||
if p["name"] in self.prefs_dict:
|
||||
_convert_pref_type(p, self.prefs_dict[p["name"]])
|
||||
|
||||
self.prefs[p["formsemestre_id"]][p["name"]] = p["value"]
|
||||
|
||||
# add defaults for missing prefs
|
||||
@ -1850,17 +1880,7 @@ class BasePreferences(object):
|
||||
name = pref[0]
|
||||
# search preferences in configuration file
|
||||
if name and name[0] != "_" and name not in self.prefs[None]:
|
||||
# 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[1]["initvalue"]
|
||||
|
||||
value = _get_pref_default_value_from_config(name, pref[1])
|
||||
self.default[name] = value
|
||||
self.prefs[None][name] = value
|
||||
log("creating missing preference for %s=%s" % (name, value))
|
||||
@ -1868,19 +1888,26 @@ class BasePreferences(object):
|
||||
self._editor.create(
|
||||
cnx, {"dept_id": self.dept_id, "name": name, "value": value}
|
||||
)
|
||||
finally:
|
||||
scu.GSL.release()
|
||||
|
||||
def get(self, formsemestre_id, name):
|
||||
"""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]:
|
||||
return self.prefs[formsemestre_id][name]
|
||||
elif name in self.prefs[None]:
|
||||
return self.prefs[None][name]
|
||||
else:
|
||||
params = {
|
||||
"dept_id": self.dept_id,
|
||||
"name": name,
|
||||
"formsemestre_id": formsemestre_id,
|
||||
}
|
||||
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]
|
||||
p = plist[0]
|
||||
_convert_pref_type(p, self.prefs_dict[name])
|
||||
return p["value"]
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self.prefs[None]
|
||||
@ -1890,18 +1917,17 @@ class BasePreferences(object):
|
||||
|
||||
def is_global(self, formsemestre_id, name):
|
||||
"True if name if not defined for semestre"
|
||||
if (
|
||||
not (formsemestre_id in self.prefs)
|
||||
or not name in self.prefs[formsemestre_id]
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
params = {
|
||||
"dept_id": self.dept_id,
|
||||
"name": name,
|
||||
"formsemestre_id": formsemestre_id,
|
||||
}
|
||||
cnx = ndb.GetDBConnexion()
|
||||
plist = self._editor.list(cnx, params)
|
||||
return len(plist) == 0
|
||||
|
||||
def save(self, formsemestre_id=None, name=None):
|
||||
"""Write one or all (if name is None) values to db"""
|
||||
try:
|
||||
scu.GSL.acquire()
|
||||
modif = False
|
||||
cnx = ndb.GetDBConnexion()
|
||||
if name is None:
|
||||
@ -1909,7 +1935,7 @@ class BasePreferences(object):
|
||||
else:
|
||||
names = [name]
|
||||
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":
|
||||
# repasse les booleens en chaines "0":"1"
|
||||
value = "1" if value else "0"
|
||||
@ -1938,8 +1964,12 @@ class BasePreferences(object):
|
||||
log("create pref sem=%s %s=%s" % (formsemestre_id, name, value))
|
||||
else:
|
||||
# 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(
|
||||
cnx,
|
||||
@ -1956,8 +1986,6 @@ class BasePreferences(object):
|
||||
# les preferences peuvent affecter les PDF cachés et les notes calculées:
|
||||
if modif:
|
||||
sco_cache.invalidate_formsemestre()
|
||||
finally:
|
||||
scu.GSL.release()
|
||||
|
||||
def set(self, formsemestre_id, name, value):
|
||||
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):
|
||||
if not formsemestre_id:
|
||||
raise ScoException()
|
||||
try:
|
||||
scu.GSL.acquire()
|
||||
|
||||
if formsemestre_id in self.prefs and name in self.prefs[formsemestre_id]:
|
||||
del self.prefs[formsemestre_id][name]
|
||||
cnx = ndb.GetDBConnexion()
|
||||
@ -1985,16 +2012,17 @@ class BasePreferences(object):
|
||||
assert pdb[0]["dept_id"] == self.dept_id
|
||||
self._editor.delete(cnx, pdb[0]["pref_id"])
|
||||
sco_cache.invalidate_formsemestre() # > modif preferences
|
||||
finally:
|
||||
scu.GSL.release()
|
||||
|
||||
def edit(self, REQUEST):
|
||||
"""HTML dialog: edit global preferences"""
|
||||
from app.scodoc import html_sco_header
|
||||
|
||||
self.load()
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Préférences"),
|
||||
"<h2>Préférences globales pour %s</h2>" % scu.ScoURL(),
|
||||
f"""<p><a href="{url_for("scolar.config_logos", scodoc_dept=g.scodoc_dept)
|
||||
}">modification des logos du département (pour documents pdf)</a></p>""",
|
||||
"""<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>
|
||||
""",
|
||||
|
@ -622,7 +622,7 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None, REQUEST=
|
||||
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
||||
|
||||
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]
|
||||
|
||||
@ -800,7 +800,7 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[], REQUEST=No
|
||||
# tous les inscrits du semestre
|
||||
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
||||
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]
|
||||
|
||||
|
@ -620,7 +620,6 @@ def saisie_notes_tableur(evaluation_id, group_ids=[], REQUEST=None):
|
||||
formsemestre_id=formsemestre_id,
|
||||
select_all_when_unspecified=True,
|
||||
etat=None,
|
||||
REQUEST=REQUEST,
|
||||
)
|
||||
|
||||
H = [
|
||||
@ -793,7 +792,6 @@ def feuille_saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
|
||||
formsemestre_id=formsemestre_id,
|
||||
select_all_when_unspecified=True,
|
||||
etat=None,
|
||||
REQUEST=REQUEST,
|
||||
)
|
||||
groups = sco_groups.listgroups(groups_infos.group_ids)
|
||||
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,
|
||||
select_all_when_unspecified=True,
|
||||
etat=None,
|
||||
REQUEST=REQUEST,
|
||||
)
|
||||
|
||||
if E["description"]:
|
||||
|
@ -158,11 +158,10 @@ class SemSet(dict):
|
||||
|
||||
ndb.SimpleQuery(
|
||||
"""INSERT INTO notes_semset_formsemestre
|
||||
(dept_id, id, semset_id)
|
||||
VALUES (%(dept_id)s, %(formsemestre_id)s, %(semset_id)s)
|
||||
(formsemestre_id, semset_id)
|
||||
VALUES (%(formsemestre_id)s, %(semset_id)s)
|
||||
""",
|
||||
{
|
||||
"dept_id": g.scodoc_dept_id,
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"semset_id": self.semset_id,
|
||||
},
|
||||
|
@ -78,7 +78,7 @@ def trombino(
|
||||
etat = None # may be passed as ''
|
||||
# Informations sur les groupes à afficher:
|
||||
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
|
||||
def trombino_copy_photos(group_ids=[], REQUEST=None, dialog_confirmed=False):
|
||||
"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
|
||||
|
||||
portal_url = sco_portal_apogee.get_portal_url()
|
||||
@ -485,14 +485,13 @@ def photos_generate_excel_sample(group_ids=[], REQUEST=None):
|
||||
"photo_filename",
|
||||
],
|
||||
extra_cols=["fichier_photo"],
|
||||
REQUEST=REQUEST,
|
||||
)
|
||||
return sco_excel.send_excel_file(REQUEST, data, "ImportPhotos" + scu.XLSX_SUFFIX)
|
||||
|
||||
|
||||
def photos_import_files_form(group_ids=[], REQUEST=None):
|
||||
"""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
|
||||
|
||||
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):
|
||||
"""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
|
||||
filename_title = "fichier_photo"
|
||||
page_title = "Téléchargement des photos des étudiants"
|
||||
|
@ -61,7 +61,7 @@ def pdf_trombino_tours(
|
||||
"""Generation du trombinoscope en fichier PDF"""
|
||||
# Informations sur les groupes à afficher:
|
||||
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")
|
||||
@ -296,7 +296,7 @@ def pdf_feuille_releve_absences(
|
||||
|
||||
# Informations sur les groupes à afficher:
|
||||
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")
|
||||
|
@ -28,104 +28,19 @@
|
||||
|
||||
""" Verification version logiciel vs version "stable" sur serveur
|
||||
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 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 = ""
|
||||
from flask import current_app
|
||||
|
||||
|
||||
def is_up_to_date():
|
||||
"""True if up_to_date
|
||||
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"
|
||||
# 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():
|
||||
|
@ -232,6 +232,8 @@ if not os.path.exists(SCO_TMP_DIR):
|
||||
os.mkdir(SCO_TMP_DIR, 0o755)
|
||||
# ----- Les logos: /opt/scodoc-data/config/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
|
||||
SCO_TOOLS_DIR = os.path.join(Config.SCODOC_DIR, "tools")
|
||||
@ -305,8 +307,6 @@ PDF_MIMETYPE = "application/pdf"
|
||||
XML_MIMETYPE = "text/xml"
|
||||
JSON_MIMETYPE = "application/json"
|
||||
|
||||
LOGOS_IMAGES_ALLOWED_TYPES = ("jpg", "png") # remind that PIL does not read pdf
|
||||
|
||||
# Admissions des étudiants
|
||||
# Différents types de voies d'admission:
|
||||
# (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
|
||||
|
||||
|
||||
def icontag(name, file_format="png", **attrs):
|
||||
def icontag(name, file_format="png", no_size=False, **attrs):
|
||||
"""tag HTML pour un icone.
|
||||
(dans les versions anterieures on utilisait Zope)
|
||||
Les icones sont des fichiers PNG dans .../static/icons
|
||||
Si la taille (width et height) n'est pas spécifiée, lit l'image
|
||||
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:
|
||||
img_file = os.path.join(
|
||||
Config.SCODOC_DIR,
|
||||
|
@ -96,6 +96,10 @@ tr.bandeaugtr {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-nav>li.logout a {
|
||||
color: rgb(255,0,0);
|
||||
}
|
||||
|
||||
/* ----- page content ------ */
|
||||
|
||||
div.about-logo {
|
||||
@ -253,6 +257,13 @@ div.logo-insidebar {
|
||||
div.logo-logo {
|
||||
text-align: center ;
|
||||
}
|
||||
div.logo-logo img {
|
||||
margin-top: 20px;
|
||||
width: 100px;
|
||||
}
|
||||
div.sidebar-bottom {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
div.etud_info_div {
|
||||
border: 2px solid gray;
|
||||
@ -317,6 +328,9 @@ table.listesems th {
|
||||
padding-top: 0.5em;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
table.listesems td {
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
table.listesems td.semicon {
|
||||
padding-left: 1.5em;
|
||||
@ -326,6 +340,11 @@ table.listesems tr.firstsem td {
|
||||
padding-top: 0.8em;
|
||||
}
|
||||
|
||||
td.datesem {
|
||||
font-size: 80%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
h2.listesems {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 0px;
|
||||
@ -816,11 +835,22 @@ a.discretelink:hover {
|
||||
|
||||
div.sco_help {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 3px;
|
||||
font-style: italic;
|
||||
color: navy;
|
||||
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 {
|
||||
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">
|
||||
{% if current_user.is_administrator() %}
|
||||
<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>
|
||||
{% endif %}
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{% 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 %}
|
||||
<li>{{current_user.user_name}}</li>
|
||||
<li><a href="{{ url_for('auth.logout') }}">Logout</a></li>
|
||||
<li>{% if current_user.dept %}
|
||||
<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 %}
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% macro render_field(field) %}
|
||||
<div>
|
||||
<div>
|
||||
<span class="wtf-field">{{ field.label }} :</span>
|
||||
<span class="wtf-field">{{ field()|safe }}
|
||||
{% if field.errors %}
|
||||
@ -13,21 +13,41 @@
|
||||
</ul>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% 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() }}
|
||||
|
||||
{% 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)}}
|
||||
{# <p>
|
||||
{{ form.bonus_sport_func_name.label }}<br>
|
||||
{{ form.bonus_sport_func_name() }}
|
||||
</p> #}
|
||||
{% endif %}
|
||||
|
||||
<div class="configuration_logo">
|
||||
<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>
|
||||
</form>
|
||||
{% endblock %}
|
@ -30,7 +30,7 @@
|
||||
</p>
|
||||
|
||||
{% 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>
|
||||
<input type="text" name="expnom" width="12" spellcheck="false" value="">
|
||||
<input type="submit" value="Chercher">
|
||||
@ -38,8 +38,9 @@
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<!--
|
||||
<div style="margin-top: 1cm;">
|
||||
<p><a href="/ScoDoc/static/mobile">Charger la version mobile (expérimentale)</a></p>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
{% endblock %}
|
@ -68,6 +68,7 @@ from app.decorators import (
|
||||
permission_required,
|
||||
admin_required,
|
||||
login_required,
|
||||
permission_required_compat_scodoc7,
|
||||
)
|
||||
|
||||
from app.views import absences_bp as bp
|
||||
@ -312,7 +313,7 @@ def SignaleAbsenceGrHebdo(
|
||||
moduleimpl_id = None
|
||||
|
||||
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:
|
||||
return (
|
||||
@ -474,7 +475,7 @@ def SignaleAbsenceGrSemestre(
|
||||
REQUEST=None,
|
||||
):
|
||||
"""Saisie des absences sur une journée sur un semestre (ou intervalle de dates) entier"""
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids, REQUEST=REQUEST)
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
||||
if not groups_infos.members:
|
||||
return (
|
||||
html_sco_header.sco_header(page_title="Saisie des absences")
|
||||
@ -847,7 +848,7 @@ def EtatAbsencesGr(
|
||||
datedebut = ndb.DateDMYtoISO(debut)
|
||||
datefin = ndb.DateDMYtoISO(fin)
|
||||
# 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
|
||||
sem = groups_infos.formsemestre
|
||||
|
||||
@ -971,13 +972,11 @@ ou entrez une date pour visualiser les absents un jour donné :
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def EtatAbsencesDate(
|
||||
group_ids=[], date=None, REQUEST=None # list of groups to display
|
||||
):
|
||||
def EtatAbsencesDate(group_ids=[], date=None): # list of groups to display
|
||||
# ported from dtml
|
||||
"""Etat des absences pour un groupe à une date donnée"""
|
||||
# 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")]
|
||||
if 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)
|
||||
|
||||
|
||||
@bp.route("/XMLgetBilletsEtud")
|
||||
@bp.route(
|
||||
"/XMLgetBilletsEtud", methods=["GET", "POST"]
|
||||
) # pour compat anciens clients PHP
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def XMLgetBilletsEtud(etudid=False, REQUEST=None):
|
||||
"""Liste billets pour un etudiant"""
|
||||
@ -1250,9 +1251,9 @@ def XMLgetBilletsEtud(etudid=False, REQUEST=None):
|
||||
return r
|
||||
|
||||
|
||||
@bp.route("/listeBillets")
|
||||
@bp.route("/listeBillets", methods=["GET", "POST"]) # pour compat anciens clients PHP
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def listeBillets(REQUEST=None):
|
||||
"""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()
|
||||
|
||||
|
||||
@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
|
||||
@permission_required(Permission.ScoView)
|
||||
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def XMLgetAbsEtud(beg_date="", end_date="", REQUEST=None):
|
||||
"""returns list of absences in date interval"""
|
||||
|
@ -50,6 +50,7 @@ from app.decorators import (
|
||||
scodoc,
|
||||
scodoc7func,
|
||||
permission_required,
|
||||
permission_required_compat_scodoc7,
|
||||
admin_required,
|
||||
login_required,
|
||||
)
|
||||
@ -252,11 +253,36 @@ sco_publish(
|
||||
Permission.ScoChangeFormation,
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
sco_publish(
|
||||
"/formsemestre_bulletinetud",
|
||||
sco_bulletins.formsemestre_bulletinetud,
|
||||
Permission.ScoView,
|
||||
)
|
||||
|
||||
|
||||
@bp.route(
|
||||
"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(
|
||||
"/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
|
||||
|
||||
|
||||
@bp.route("/formsemestre_list")
|
||||
@bp.route(
|
||||
"/formsemestre_list", methods=["GET", "POST"]
|
||||
) # pour compat anciens clients PHP
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def formsemestre_list(
|
||||
format=None,
|
||||
@ -599,9 +627,11 @@ def formsemestre_list(
|
||||
return scu.sendResult(REQUEST, sems, name="formsemestre", format=format)
|
||||
|
||||
|
||||
@bp.route("/XMLgetFormsemestres")
|
||||
@bp.route(
|
||||
"/XMLgetFormsemestres", methods=["GET", "POST"]
|
||||
) # pour compat anciens clients PHP
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def XMLgetFormsemestres(etape_apo=None, formsemestre_id=None, REQUEST=None):
|
||||
"""List all formsemestres matching etape, XML format
|
||||
|
@ -30,20 +30,35 @@ Module main: page d'accueil, avec liste des départements
|
||||
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
from app.auth.models import User
|
||||
import os
|
||||
|
||||
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.app import Flask
|
||||
from flask_login.utils import login_required
|
||||
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.fields import IntegerField
|
||||
from wtforms.fields.simple import BooleanField, StringField, TextAreaField
|
||||
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
|
||||
|
||||
# from wtforms.validators import DataRequired
|
||||
|
||||
from app.models import Departement, ScoDocSiteConfig
|
||||
import app
|
||||
from app.models import Departement, Identite
|
||||
from app.models import FormSemestre, NotesFormsemestreInscription
|
||||
from app.models import ScoDocSiteConfig
|
||||
import sco_version
|
||||
from app.scodoc import sco_logos
|
||||
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.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"])
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
class ScoDocConfigurationForm(FlaskForm):
|
||||
"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(
|
||||
label="Fonction de calcul des bonus sport&culture",
|
||||
choices=[
|
||||
@ -86,28 +144,104 @@ class ScoDocConfigurationForm(FlaskForm):
|
||||
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")
|
||||
|
||||
|
||||
# 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"])
|
||||
@admin_required
|
||||
def configuration():
|
||||
"Panneau de configuration général"
|
||||
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():
|
||||
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")
|
||||
return redirect(url_for("scodoc.index"))
|
||||
|
||||
return render_template(
|
||||
"configuration.html",
|
||||
title="Configuration ScoDoc",
|
||||
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
|
||||
# @bp.route("/testlog")
|
||||
# def testlog():
|
||||
|
@ -30,7 +30,7 @@ issu de ScoDoc7 / ZScolar.py
|
||||
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
@ -40,15 +40,19 @@ from zipfile import ZipFile
|
||||
import psycopg2
|
||||
|
||||
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_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 app.decorators import (
|
||||
scodoc,
|
||||
scodoc7func,
|
||||
permission_required,
|
||||
permission_required_compat_scodoc7,
|
||||
admin_required,
|
||||
login_required,
|
||||
)
|
||||
@ -71,8 +75,8 @@ from app.scodoc.sco_exceptions import (
|
||||
)
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
||||
import sco_version
|
||||
import app
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import html_sidebar
|
||||
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_edit
|
||||
from app.scodoc import sco_groups_view
|
||||
from app.scodoc import sco_logos
|
||||
from app.scodoc import sco_news
|
||||
from app.scodoc import sco_page_etud
|
||||
from app.scodoc import sco_parcours_dut
|
||||
@ -201,6 +206,66 @@ def doc_preferences(REQUEST):
|
||||
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
|
||||
@ -283,7 +348,10 @@ 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)
|
||||
@ -332,10 +400,18 @@ def search_etud_by_name():
|
||||
|
||||
|
||||
# XMLgetEtudInfos était le nom dans l'ancienne API ScoDoc 6
|
||||
@bp.route("/etud_info")
|
||||
@bp.route("/XMLgetEtudInfos")
|
||||
@bp.route("/etud_info", methods=["GET", "POST"]) # pour compat anciens clients PHP)
|
||||
@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
|
||||
@permission_required(Permission.ScoView)
|
||||
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||
@scodoc7func
|
||||
def etud_info(etudid=None, format="xml", REQUEST=None):
|
||||
"Donne les informations sur un etudiant"
|
||||
|
@ -121,7 +121,9 @@ def create_user_form(REQUEST, user_name=None, edit=0):
|
||||
is_super_admin = True
|
||||
|
||||
# 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:
|
||||
# si SuperAdmin, tous les rôles standards dans tous les départements
|
||||
# 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_VAR_DIR = os.environ.get("SCODOC_VAR_DIR", "/opt/scodoc-data")
|
||||
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")
|
||||
DEFAULT_SQL_PORT = os.environ.get("DEFAULT_SQL_PORT", "5432")
|
||||
#
|
||||
MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # Flask uploads
|
||||
|
||||
# STATIC_URL_PATH = "/ScoDoc/static"
|
||||
# static_folder = "stat"
|
||||
# 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):
|
||||
"""Get and returns as JSON"""
|
||||
r = s.get(BASEURL + "/" + path)
|
||||
r = s.get(BASEURL + "/" + path, verify=CHECK_CERTIFICATE)
|
||||
if r.status_code != 200:
|
||||
raise ScoError(errmsg or "erreur !")
|
||||
return r.json() # decode la reponse JSON
|
||||
@ -44,7 +44,7 @@ def GET(s, path, errmsg=None):
|
||||
|
||||
def POST(s, path, data, errmsg=None):
|
||||
"""Post"""
|
||||
r = s.post(BASEURL + "/" + path, data=data)
|
||||
r = s.post(BASEURL + "/" + path, data=data, verify=CHECK_CERTIFICATE)
|
||||
if r.status_code != 200:
|
||||
raise ScoError(errmsg or "erreur !")
|
||||
return r.text
|
||||
@ -52,6 +52,10 @@ def POST(s, path, data, errmsg=None):
|
||||
|
||||
# --- Ouverture session (login)
|
||||
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)
|
||||
if r.status_code != 200:
|
||||
raise ScoError("erreur de connection: vérifier adresse et identifiants")
|
||||
|
@ -18,6 +18,7 @@ Flask==2.0.1
|
||||
Flask-Babel==2.0.0
|
||||
Flask-Bootstrap==3.3.7.1
|
||||
Flask-Caching==1.10.1
|
||||
Flask-HTTPAuth==4.4.0
|
||||
Flask-Login==0.5.0
|
||||
Flask-Mail==0.9.1
|
||||
Flask-Migrate==3.1.0
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.0.8"
|
||||
SCOVERSION = "9.0.13"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
18
scodoc.py
18
scodoc.py
@ -188,24 +188,6 @@ def create_dept(dept): # create-dept
|
||||
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()
|
||||
@with_appcontext
|
||||
def import_scodoc7_users(): # import-scodoc7-users
|
||||
|
@ -40,11 +40,11 @@ from app.scodoc.sco_exceptions import ScoValueError
|
||||
random.seed(12345) # tests reproductibles
|
||||
|
||||
|
||||
DEMO_DIR = Config.SCODOC_DIR + "/tools/demo/"
|
||||
NOMS = [x.strip() for x in open(DEMO_DIR + "/noms.txt").readlines()]
|
||||
PRENOMS_H = [x.strip() for x in open(DEMO_DIR + "/prenoms-h.txt").readlines()]
|
||||
PRENOMS_F = [x.strip() for x in open(DEMO_DIR + "/prenoms-f.txt").readlines()]
|
||||
PRENOMS_X = [x.strip() for x in open(DEMO_DIR + "/prenoms-x.txt").readlines()]
|
||||
NOMS_DIR = Config.SCODOC_DIR + "/tools/fakeportal/nomsprenoms"
|
||||
NOMS = [x.strip() for x in open(NOMS_DIR + "/noms.txt").readlines()]
|
||||
PRENOMS_H = [x.strip() for x in open(NOMS_DIR + "/prenoms-h.txt").readlines()]
|
||||
PRENOMS_F = [x.strip() for x in open(NOMS_DIR + "/prenoms-f.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):
|
||||
|
@ -73,6 +73,8 @@ def test_preferences(test_client):
|
||||
assert len(prefs2) == len(prefs)
|
||||
prefs2.set(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"
|
||||
orm_val = (
|
||||
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"
|
||||
# --- Preferences d'un semestre
|
||||
# rejoue ce test pour avoir un semestre créé
|
||||
app.set_sco_dept("D2")
|
||||
test_sco_basic.run_sco_basic()
|
||||
sem = sco_formsemestre.do_formsemestre_list()[0]
|
||||
formsemestre_id = sem["formsemestre_id"]
|
||||
|
@ -108,8 +108,7 @@ change_scodoc_file_ownership
|
||||
# ------------ CREATION BASE DE DONNEES
|
||||
echo
|
||||
echo "Voulez-vous créer la base SQL SCODOC ?"
|
||||
echo "répondre oui sauf si vous avez déjà une base existante"
|
||||
echo "que vous souhaitez conserver (mais pour les migrations, répondre oui)."
|
||||
echo "(répondre oui sauf si vous savez vraiment ce que vous faites)"
|
||||
echo -n 'Créer la base de données SCODOC ? (y/n) [y] '
|
||||
read -r ans
|
||||
if [ "$(norm_ans "$ans")" != 'N' ]
|
||||
@ -121,9 +120,10 @@ then
|
||||
echo
|
||||
echo "Création des tables et du compte admin"
|
||||
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 "base initialisée et admin créé."
|
||||
echo "Base initialisée et admin créé."
|
||||
echo
|
||||
fi
|
||||
|
||||
@ -134,6 +134,7 @@ systemctl start scodoc9
|
||||
|
||||
echo
|
||||
echo "Service configuré et démarré."
|
||||
echo "Vous pouvez vous connecter en web et vous identifier comme \"admin\"."
|
||||
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
|
||||
fi
|
||||
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
|
||||
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
|
||||
WorkingDirectory=/opt/scodoc
|
||||
#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
|
||||
|
||||
[Install]
|
||||
|
@ -100,7 +100,7 @@ class MyHttpRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
|
||||
if "etapes" in self.path.lower():
|
||||
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
|
||||
if "nip" in query_components:
|
||||
nip = query_components["nip"][0]
|
||||
|
@ -59,7 +59,7 @@ def import_scodoc7_user_db(scodoc7_db="dbname=SCOUSERS"):
|
||||
roles7 = []
|
||||
for role_dept in roles7:
|
||||
# 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:
|
||||
msg = f"User {user_name}: role inconnu '{role_dept}' (ignoré)"
|
||||
current_app.logger.warning(msg)
|
||||
@ -75,7 +75,7 @@ def import_scodoc7_user_db(scodoc7_db="dbname=SCOUSERS"):
|
||||
dept = m.group(2)
|
||||
role = Role.query.filter_by(name=role_name).first()
|
||||
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)
|
||||
messages.append(msg)
|
||||
else:
|
||||
|
@ -23,10 +23,6 @@ CONFIG = CFG()
|
||||
|
||||
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 -------------
|
||||
#
|
||||
@ -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
|
||||
|
||||
# XXX à revoir pour le BUT: variable à intégrer aux parcours
|
||||
CONFIG.CAPITALIZE_ALL_UES = (
|
||||
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>
|
||||
|
Loading…
Reference in New Issue
Block a user