forked from ScoDoc/DocScoDoc
Merge remote-tracking branch 'origin/master' into temp
# Conflicts: # app/scodoc/sco_placement.py
This commit is contained in:
commit
42ef9f795f
@ -148,9 +148,15 @@ Mémo pour développeurs: séquence re-création d'une base:
|
|||||||
flask import-scodoc7-users
|
flask import-scodoc7-users
|
||||||
flask import-scodoc7-dept STID SCOSTID
|
flask import-scodoc7-dept STID SCOSTID
|
||||||
|
|
||||||
|
Si la base utilisée pour les dev n'est plus en phase avec les scripts de
|
||||||
|
migration, utiliser les commandes `flask db history`et `flask db stamp`pour se
|
||||||
|
positionner à la bonne étape.
|
||||||
|
|
||||||
# Paquet debian 11
|
# Paquet debian 11
|
||||||
|
|
||||||
Les scripts associés au paquet Debian (.deb) sont dans `tools/debian`.
|
Les scripts associés au paquet Debian (.deb) sont dans `tools/debian`. Le plus
|
||||||
|
important est `postinst`qui se charge de configurer le système (install ou
|
||||||
|
upgrade de scodoc9).
|
||||||
|
|
||||||
La préparation d'une release se fait à l'aide du script
|
La préparation d'une release se fait à l'aide du script
|
||||||
`tools/build_release.sh`.
|
`tools/build_release.sh`.
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
@ -12,19 +13,19 @@ from logging.handlers import SMTPHandler, WatchedFileHandler
|
|||||||
|
|
||||||
from flask import current_app, g, request
|
from flask import current_app, g, request
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask import abort, has_request_context
|
from flask import abort, has_request_context, jsonify
|
||||||
from flask import render_template
|
from flask import render_template
|
||||||
from flask.logging import default_handler
|
from flask.logging import default_handler
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
from flask_login import LoginManager
|
from flask_login import LoginManager, current_user
|
||||||
from flask_mail import Mail
|
from flask_mail import Mail
|
||||||
from flask_bootstrap import Bootstrap
|
from flask_bootstrap import Bootstrap
|
||||||
from flask_moment import Moment
|
from flask_moment import Moment
|
||||||
from flask_caching import Cache
|
from flask_caching import Cache
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError, APIInvalidParams
|
||||||
from config import DevConfig
|
from config import DevConfig
|
||||||
import sco_version
|
import sco_version
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ db = SQLAlchemy()
|
|||||||
migrate = Migrate(compare_type=True)
|
migrate = Migrate(compare_type=True)
|
||||||
login = LoginManager()
|
login = LoginManager()
|
||||||
login.login_view = "auth.login"
|
login.login_view = "auth.login"
|
||||||
login.login_message = "Please log in to access this page."
|
login.login_message = "Identifiez-vous pour accéder à cette page."
|
||||||
mail = Mail()
|
mail = Mail()
|
||||||
bootstrap = Bootstrap()
|
bootstrap = Bootstrap()
|
||||||
moment = Moment()
|
moment = Moment()
|
||||||
@ -56,6 +57,12 @@ def internal_server_error(e):
|
|||||||
return render_template("error_500.html", SCOVERSION=sco_version.SCOVERSION), 500
|
return render_template("error_500.html", SCOVERSION=sco_version.SCOVERSION), 500
|
||||||
|
|
||||||
|
|
||||||
|
def handle_invalid_usage(error):
|
||||||
|
response = jsonify(error.to_dict())
|
||||||
|
response.status_code = error.status_code
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def render_raw_html(template_filename: str, **args) -> str:
|
def render_raw_html(template_filename: str, **args) -> str:
|
||||||
"""Load and render an HTML file _without_ using Flask
|
"""Load and render an HTML file _without_ using Flask
|
||||||
Necessary for 503 error mesage, when DB is down and Flask may be broken.
|
Necessary for 503 error mesage, when DB is down and Flask may be broken.
|
||||||
@ -76,7 +83,7 @@ def postgresql_server_error(e):
|
|||||||
return render_raw_html("error_503.html", SCOVERSION=sco_version.SCOVERSION), 503
|
return render_raw_html("error_503.html", SCOVERSION=sco_version.SCOVERSION), 503
|
||||||
|
|
||||||
|
|
||||||
class RequestFormatter(logging.Formatter):
|
class LogRequestFormatter(logging.Formatter):
|
||||||
"""Ajoute URL et remote_addr for logging"""
|
"""Ajoute URL et remote_addr for logging"""
|
||||||
|
|
||||||
def format(self, record):
|
def format(self, record):
|
||||||
@ -86,12 +93,64 @@ class RequestFormatter(logging.Formatter):
|
|||||||
else:
|
else:
|
||||||
record.url = None
|
record.url = None
|
||||||
record.remote_addr = None
|
record.remote_addr = None
|
||||||
|
record.sco_user = current_user
|
||||||
|
|
||||||
return super().format(record)
|
return super().format(record)
|
||||||
|
|
||||||
|
|
||||||
|
class LogExceptionFormatter(logging.Formatter):
|
||||||
|
"""Formatteur pour les exceptions: ajoute détails"""
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
if has_request_context():
|
||||||
|
record.url = request.url
|
||||||
|
record.remote_addr = request.environ.get(
|
||||||
|
"HTTP_X_FORWARDED_FOR", request.remote_addr
|
||||||
|
)
|
||||||
|
record.http_referrer = request.referrer
|
||||||
|
record.http_method = request.method
|
||||||
|
if request.method == "GET":
|
||||||
|
record.http_params = str(request.args)
|
||||||
|
else:
|
||||||
|
record.http_params = "(post data not loggued)"
|
||||||
|
else:
|
||||||
|
record.url = None
|
||||||
|
record.remote_addr = None
|
||||||
|
record.http_referrer = None
|
||||||
|
record.http_method = None
|
||||||
|
record.http_params = None
|
||||||
|
record.sco_user = current_user
|
||||||
|
|
||||||
|
return super().format(record)
|
||||||
|
|
||||||
|
|
||||||
|
class ScoSMTPHandler(SMTPHandler):
|
||||||
|
def getSubject(self, record: logging.LogRecord) -> str:
|
||||||
|
stack_summary = traceback.extract_tb(record.exc_info[2])
|
||||||
|
frame_summary = stack_summary[-1]
|
||||||
|
subject = f"ScoExc({sco_version.SCOVERSION}): {record.exc_info[0].__name__} in {frame_summary.name} {frame_summary.filename}"
|
||||||
|
|
||||||
|
return subject
|
||||||
|
|
||||||
|
|
||||||
|
class ReverseProxied(object):
|
||||||
|
"""Adaptateur wsgi qui nous permet d'avoir toutes les URL calculées en https
|
||||||
|
sauf quand on est en dev.
|
||||||
|
La variable HTTP_X_FORWARDED_PROTO est positionnée par notre config nginx"""
|
||||||
|
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
scheme = environ.get("HTTP_X_FORWARDED_PROTO")
|
||||||
|
if scheme:
|
||||||
|
environ["wsgi.url_scheme"] = scheme # ou forcer à https ici ?
|
||||||
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
def create_app(config_class=DevConfig):
|
def create_app(config_class=DevConfig):
|
||||||
app = Flask(__name__, static_url_path="/ScoDoc/static", static_folder="static")
|
app = Flask(__name__, static_url_path="/ScoDoc/static", static_folder="static")
|
||||||
|
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||||
app.logger.setLevel(logging.DEBUG)
|
app.logger.setLevel(logging.DEBUG)
|
||||||
app.config.from_object(config_class)
|
app.config.from_object(config_class)
|
||||||
|
|
||||||
@ -107,6 +166,7 @@ def create_app(config_class=DevConfig):
|
|||||||
app.register_error_handler(ScoValueError, handle_sco_value_error)
|
app.register_error_handler(ScoValueError, handle_sco_value_error)
|
||||||
app.register_error_handler(500, internal_server_error)
|
app.register_error_handler(500, internal_server_error)
|
||||||
app.register_error_handler(503, postgresql_server_error)
|
app.register_error_handler(503, postgresql_server_error)
|
||||||
|
app.register_error_handler(APIInvalidParams, handle_invalid_usage)
|
||||||
|
|
||||||
from app.auth import bp as auth_bp
|
from app.auth import bp as auth_bp
|
||||||
|
|
||||||
@ -132,9 +192,16 @@ def create_app(config_class=DevConfig):
|
|||||||
absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences"
|
absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences"
|
||||||
)
|
)
|
||||||
app.register_blueprint(api_bp, url_prefix="/ScoDoc/api")
|
app.register_blueprint(api_bp, url_prefix="/ScoDoc/api")
|
||||||
scodoc_exc_formatter = RequestFormatter(
|
scodoc_log_formatter = LogRequestFormatter(
|
||||||
"[%(asctime)s] %(remote_addr)s requested %(url)s\n"
|
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
|
||||||
"%(levelname)s in %(module)s: %(message)s"
|
"%(levelname)s: %(message)s"
|
||||||
|
)
|
||||||
|
scodoc_exc_formatter = LogExceptionFormatter(
|
||||||
|
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
|
||||||
|
"%(levelname)s: %(message)s\n"
|
||||||
|
"Referrer: %(http_referrer)s\n"
|
||||||
|
"Method: %(http_method)s\n"
|
||||||
|
"Params: %(http_params)s\n"
|
||||||
)
|
)
|
||||||
if not app.testing:
|
if not app.testing:
|
||||||
if not app.debug:
|
if not app.debug:
|
||||||
@ -150,11 +217,11 @@ def create_app(config_class=DevConfig):
|
|||||||
if app.config["MAIL_USE_TLS"]:
|
if app.config["MAIL_USE_TLS"]:
|
||||||
secure = ()
|
secure = ()
|
||||||
host_name = socket.gethostname()
|
host_name = socket.gethostname()
|
||||||
mail_handler = SMTPHandler(
|
mail_handler = ScoSMTPHandler(
|
||||||
mailhost=(app.config["MAIL_SERVER"], app.config["MAIL_PORT"]),
|
mailhost=(app.config["MAIL_SERVER"], app.config["MAIL_PORT"]),
|
||||||
fromaddr="no-reply@" + app.config["MAIL_SERVER"],
|
fromaddr="no-reply@" + app.config["MAIL_SERVER"],
|
||||||
toaddrs=["exception@scodoc.org"],
|
toaddrs=["exception@scodoc.org"],
|
||||||
subject="ScoDoc Exception from " + host_name,
|
subject="ScoDoc Exception", # unused see ScoSMTPHandler
|
||||||
credentials=auth,
|
credentials=auth,
|
||||||
secure=secure,
|
secure=secure,
|
||||||
)
|
)
|
||||||
@ -163,7 +230,7 @@ def create_app(config_class=DevConfig):
|
|||||||
app.logger.addHandler(mail_handler)
|
app.logger.addHandler(mail_handler)
|
||||||
else:
|
else:
|
||||||
# Pour logs en DEV uniquement:
|
# Pour logs en DEV uniquement:
|
||||||
default_handler.setFormatter(scodoc_exc_formatter)
|
default_handler.setFormatter(scodoc_log_formatter)
|
||||||
|
|
||||||
# Config logs pour DEV et PRODUCTION
|
# Config logs pour DEV et PRODUCTION
|
||||||
# Configuration des logs (actifs aussi en mode development)
|
# Configuration des logs (actifs aussi en mode development)
|
||||||
@ -172,9 +239,17 @@ def create_app(config_class=DevConfig):
|
|||||||
file_handler = WatchedFileHandler(
|
file_handler = WatchedFileHandler(
|
||||||
app.config["SCODOC_LOG_FILE"], encoding="utf-8"
|
app.config["SCODOC_LOG_FILE"], encoding="utf-8"
|
||||||
)
|
)
|
||||||
file_handler.setFormatter(scodoc_exc_formatter)
|
file_handler.setFormatter(scodoc_log_formatter)
|
||||||
file_handler.setLevel(logging.INFO)
|
file_handler.setLevel(logging.INFO)
|
||||||
app.logger.addHandler(file_handler)
|
app.logger.addHandler(file_handler)
|
||||||
|
# Log pour les erreurs (exceptions) uniquement:
|
||||||
|
# usually /opt/scodoc-data/log/scodoc_exc.log
|
||||||
|
file_handler = WatchedFileHandler(
|
||||||
|
app.config["SCODOC_ERR_FILE"], encoding="utf-8"
|
||||||
|
)
|
||||||
|
file_handler.setFormatter(scodoc_exc_formatter)
|
||||||
|
file_handler.setLevel(logging.ERROR)
|
||||||
|
app.logger.addHandler(file_handler)
|
||||||
|
|
||||||
# app.logger.setLevel(logging.INFO)
|
# app.logger.setLevel(logging.INFO)
|
||||||
app.logger.info(f"{sco_version.SCONAME} {sco_version.SCOVERSION} startup")
|
app.logger.info(f"{sco_version.SCONAME} {sco_version.SCOVERSION} startup")
|
||||||
|
@ -16,20 +16,20 @@ _l = _
|
|||||||
|
|
||||||
|
|
||||||
class LoginForm(FlaskForm):
|
class LoginForm(FlaskForm):
|
||||||
user_name = StringField(_l("Username"), validators=[DataRequired()])
|
user_name = StringField(_l("Nom d'utilisateur"), validators=[DataRequired()])
|
||||||
password = PasswordField(_l("Password"), validators=[DataRequired()])
|
password = PasswordField(_l("Mot de passe"), validators=[DataRequired()])
|
||||||
remember_me = BooleanField(_l("Remember Me"))
|
remember_me = BooleanField(_l("mémoriser la connexion"))
|
||||||
submit = SubmitField(_l("Sign In"))
|
submit = SubmitField(_l("Suivant"))
|
||||||
|
|
||||||
|
|
||||||
class UserCreationForm(FlaskForm):
|
class UserCreationForm(FlaskForm):
|
||||||
user_name = StringField(_l("Username"), validators=[DataRequired()])
|
user_name = StringField(_l("Nom d'utilisateur"), validators=[DataRequired()])
|
||||||
email = StringField(_l("Email"), validators=[DataRequired(), Email()])
|
email = StringField(_l("Email"), validators=[DataRequired(), Email()])
|
||||||
password = PasswordField(_l("Password"), validators=[DataRequired()])
|
password = PasswordField(_l("Mot de passe"), validators=[DataRequired()])
|
||||||
password2 = PasswordField(
|
password2 = PasswordField(
|
||||||
_l("Repeat Password"), validators=[DataRequired(), EqualTo("password")]
|
_l("Répéter"), validators=[DataRequired(), EqualTo("password")]
|
||||||
)
|
)
|
||||||
submit = SubmitField(_l("Register"))
|
submit = SubmitField(_l("Inscrire"))
|
||||||
|
|
||||||
def validate_user_name(self, user_name):
|
def validate_user_name(self, user_name):
|
||||||
user = User.query.filter_by(user_name=user_name.data).first()
|
user = User.query.filter_by(user_name=user_name.data).first()
|
||||||
@ -48,9 +48,9 @@ class ResetPasswordRequestForm(FlaskForm):
|
|||||||
|
|
||||||
|
|
||||||
class ResetPasswordForm(FlaskForm):
|
class ResetPasswordForm(FlaskForm):
|
||||||
password = PasswordField(_l("Password"), validators=[DataRequired()])
|
password = PasswordField(_l("Mot de passe"), validators=[DataRequired()])
|
||||||
password2 = PasswordField(
|
password2 = PasswordField(
|
||||||
_l("Repeat Password"), validators=[DataRequired(), EqualTo("password")]
|
_l("Répéter"), validators=[DataRequired(), EqualTo("password")]
|
||||||
)
|
)
|
||||||
submit = SubmitField(_l("Request Password Reset"))
|
submit = SubmitField(_l("Request Password Reset"))
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ def login():
|
|||||||
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)
|
current_app.logger.info("login: invalid (%s)", form.user_name.data)
|
||||||
flash(_("Invalid user name or password"))
|
flash(_("Nom ou mot de passe invalide"))
|
||||||
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)
|
current_app.logger.info("login: success (%s)", form.user_name.data)
|
||||||
@ -95,7 +95,7 @@ def reset_password_request():
|
|||||||
current_app.logger.info(
|
current_app.logger.info(
|
||||||
"reset_password_request: for unkown user '{}'".format(form.email.data)
|
"reset_password_request: for unkown user '{}'".format(form.email.data)
|
||||||
)
|
)
|
||||||
flash(_("Check your email for the instructions to reset your password"))
|
flash(_("Voir les instructions envoyées par mail"))
|
||||||
return redirect(url_for("auth.login"))
|
return redirect(url_for("auth.login"))
|
||||||
return render_template(
|
return render_template(
|
||||||
"auth/reset_password_request.html", title=_("Reset Password"), form=form
|
"auth/reset_password_request.html", title=_("Reset Password"), form=form
|
||||||
|
@ -43,12 +43,14 @@ class ZRequest(object):
|
|||||||
"Emulating Zope 2 REQUEST"
|
"Emulating Zope 2 REQUEST"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if current_app.config["DEBUG"]:
|
# if current_app.config["DEBUG"]:
|
||||||
self.URL = request.base_url
|
|
||||||
self.BASE0 = request.url_root
|
# le ReverseProxied se charge maintenant de mettre le bon protocole http ou https
|
||||||
else:
|
self.URL = request.base_url
|
||||||
self.URL = request.base_url.replace("http://", "https://")
|
self.BASE0 = request.url_root
|
||||||
self.BASE0 = request.url_root.replace("http://", "https://")
|
# else:
|
||||||
|
# self.URL = request.base_url.replace("http://", "https://")
|
||||||
|
# self.BASE0 = request.url_root.replace("http://", "https://")
|
||||||
self.URL0 = self.URL
|
self.URL0 = self.URL
|
||||||
# query_string is bytes:
|
# query_string is bytes:
|
||||||
self.QUERY_STRING = request.query_string.decode("utf-8")
|
self.QUERY_STRING = request.query_string.decode("utf-8")
|
||||||
|
@ -41,6 +41,7 @@ class Identite(db.Model):
|
|||||||
code_nip = db.Column(db.Text())
|
code_nip = db.Column(db.Text())
|
||||||
code_ine = db.Column(db.Text())
|
code_ine = db.Column(db.Text())
|
||||||
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
||||||
|
# ne pas utiliser après migrate_scodoc7_dept_archive
|
||||||
scodoc7_id = db.Column(db.Text(), nullable=True)
|
scodoc7_id = db.Column(db.Text(), nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ class FormSemestre(db.Model):
|
|||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
formsemestre_id = db.synonym("id")
|
formsemestre_id = db.synonym("id")
|
||||||
# dept_id est aussi dans la formation, ajpouté ici pour
|
# dept_id est aussi dans la formation, ajouté ici pour
|
||||||
# simplifier et accélérer les selects dans notesdb
|
# simplifier et accélérer les selects dans notesdb
|
||||||
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
|
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
|
||||||
formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
|
formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
|
||||||
@ -41,6 +41,10 @@ class FormSemestre(db.Model):
|
|||||||
bul_hide_xml = db.Column(
|
bul_hide_xml = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
)
|
)
|
||||||
|
# Bloque le calcul des moyennes (générale et d'UE)
|
||||||
|
block_moyennes = db.Column(
|
||||||
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
|
)
|
||||||
# semestres decales (pour gestion jurys):
|
# semestres decales (pour gestion jurys):
|
||||||
gestion_semestrielle = db.Column(
|
gestion_semestrielle = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
@ -70,6 +74,7 @@ class FormSemestre(db.Model):
|
|||||||
"NotesFormsemestreEtape", cascade="all,delete", backref="notes_formsemestre"
|
"NotesFormsemestreEtape", cascade="all,delete", backref="notes_formsemestre"
|
||||||
)
|
)
|
||||||
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
||||||
|
# ne pas utiliser après migrate_scodoc7_dept_archive
|
||||||
scodoc7_id = db.Column(db.Text(), nullable=True)
|
scodoc7_id = db.Column(db.Text(), nullable=True)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
v 1.3 (python3)
|
v 1.3 (python3)
|
||||||
"""
|
"""
|
||||||
|
import html
|
||||||
|
|
||||||
|
|
||||||
def TrivialFormulator(
|
def TrivialFormulator(
|
||||||
@ -722,7 +723,9 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
|||||||
if str(descr["allowed_values"][i]) == str(self.values[field]):
|
if str(descr["allowed_values"][i]) == str(self.values[field]):
|
||||||
R.append('<span class="tf-ro-value">%s</span>' % labels[i])
|
R.append('<span class="tf-ro-value">%s</span>' % labels[i])
|
||||||
elif input_type == "textarea":
|
elif input_type == "textarea":
|
||||||
R.append('<div class="tf-ro-textarea">%s</div>' % self.values[field])
|
R.append(
|
||||||
|
'<div class="tf-ro-textarea">%s</div>' % html.escape(self.values[field])
|
||||||
|
)
|
||||||
elif input_type == "separator" or input_type == "hidden":
|
elif input_type == "separator" or input_type == "hidden":
|
||||||
pass
|
pass
|
||||||
elif input_type == "file":
|
elif input_type == "file":
|
||||||
|
@ -87,10 +87,6 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
_TOP_LEVEL_CSS = """
|
|
||||||
<style type="text/css">
|
|
||||||
</style>"""
|
|
||||||
|
|
||||||
_HTML_BEGIN = """<?xml version="1.0" encoding="%(encoding)s"?>
|
_HTML_BEGIN = """<?xml version="1.0" encoding="%(encoding)s"?>
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
@ -105,31 +101,30 @@ _HTML_BEGIN = """<?xml version="1.0" encoding="%(encoding)s"?>
|
|||||||
|
|
||||||
<link href="/ScoDoc/static/css/scodoc.css" rel="stylesheet" type="text/css" />
|
<link href="/ScoDoc/static/css/scodoc.css" rel="stylesheet" type="text/css" />
|
||||||
<link href="/ScoDoc/static/css/menu.css" rel="stylesheet" type="text/css" />
|
<link href="/ScoDoc/static/css/menu.css" rel="stylesheet" type="text/css" />
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/menu.js"></script>
|
<script src="/ScoDoc/static/libjs/menu.js"></script>
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/sorttable.js"></script>
|
<script src="/ScoDoc/static/libjs/sorttable.js"></script>
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/bubble.js"></script>
|
<script src="/ScoDoc/static/libjs/bubble.js"></script>
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
window.onload=function(){enableTooltips("gtrcontent")};
|
window.onload=function(){enableTooltips("gtrcontent")};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery.js"></script>
|
<script src="/ScoDoc/static/jQuery/jquery.js"></script>
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
<script src="/ScoDoc/static/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery.field.min.js"></script>
|
<script src="/ScoDoc/static/libjs/jquery.field.min.js"></script>
|
||||||
|
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
<script src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||||
|
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
<script src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||||
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.css" />
|
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.css" />
|
||||||
|
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/scodoc.js"></script>
|
<script src="/ScoDoc/static/js/scodoc.js"></script>
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/etud_info.js"></script>
|
<script src="/ScoDoc/static/js/etud_info.js"></script>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def scodoc_top_html_header(page_title="ScoDoc: bienvenue"):
|
def scodoc_top_html_header(page_title="ScoDoc: bienvenue"):
|
||||||
H = [
|
H = [
|
||||||
_HTML_BEGIN % {"page_title": page_title, "encoding": scu.SCO_ENCODING},
|
_HTML_BEGIN % {"page_title": page_title, "encoding": scu.SCO_ENCODING},
|
||||||
_TOP_LEVEL_CSS,
|
|
||||||
"""</head><body class="gtrcontent" id="gtrcontent">""",
|
"""</head><body class="gtrcontent" id="gtrcontent">""",
|
||||||
scu.CUSTOM_HTML_HEADER_CNX,
|
scu.CUSTOM_HTML_HEADER_CNX,
|
||||||
]
|
]
|
||||||
@ -185,13 +180,10 @@ def sco_header(
|
|||||||
init_jquery = True
|
init_jquery = True
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
"""<?xml version="1.0" encoding="%(encoding)s"?>
|
"""<!DOCTYPE html><html lang="fr">
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
<title>%(page_title)s</title>
|
<title>%(page_title)s</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=%(encoding)s" />
|
|
||||||
<meta http-equiv="Content-Style-Type" content="text/css" />
|
|
||||||
<meta name="LANG" content="fr" />
|
<meta name="LANG" content="fr" />
|
||||||
<meta name="DESCRIPTION" content="ScoDoc" />
|
<meta name="DESCRIPTION" content="ScoDoc" />
|
||||||
|
|
||||||
@ -206,9 +198,7 @@ def sco_header(
|
|||||||
)
|
)
|
||||||
if init_google_maps:
|
if init_google_maps:
|
||||||
# It may be necessary to add an API key:
|
# It may be necessary to add an API key:
|
||||||
H.append(
|
H.append('<script src="https://maps.google.com/maps/api/js"></script>')
|
||||||
'<script type="text/javascript" src="https://maps.google.com/maps/api/js"></script>'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Feuilles de style additionnelles:
|
# Feuilles de style additionnelles:
|
||||||
for cssstyle in cssstyles:
|
for cssstyle in cssstyles:
|
||||||
@ -223,9 +213,9 @@ def sco_header(
|
|||||||
<link href="/ScoDoc/static/css/menu.css" rel="stylesheet" type="text/css" />
|
<link href="/ScoDoc/static/css/menu.css" rel="stylesheet" type="text/css" />
|
||||||
<link href="/ScoDoc/static/css/gt_table.css" rel="stylesheet" type="text/css" />
|
<link href="/ScoDoc/static/css/gt_table.css" rel="stylesheet" type="text/css" />
|
||||||
|
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/menu.js"></script>
|
<script src="/ScoDoc/static/libjs/menu.js"></script>
|
||||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/bubble.js"></script>
|
<script src="/ScoDoc/static/libjs/bubble.js"></script>
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
window.onload=function(){enableTooltips("gtrcontent")};
|
window.onload=function(){enableTooltips("gtrcontent")};
|
||||||
|
|
||||||
var SCO_URL="%(ScoURL)s";
|
var SCO_URL="%(ScoURL)s";
|
||||||
@ -236,16 +226,14 @@ def sco_header(
|
|||||||
# jQuery
|
# jQuery
|
||||||
if init_jquery:
|
if init_jquery:
|
||||||
H.append(
|
H.append(
|
||||||
"""<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery.js"></script>
|
"""<script src="/ScoDoc/static/jQuery/jquery.js"></script>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
H.append(
|
H.append('<script src="/ScoDoc/static/libjs/jquery.field.min.js"></script>')
|
||||||
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery.field.min.js"></script>'
|
|
||||||
)
|
|
||||||
# qTip
|
# qTip
|
||||||
if init_qtip:
|
if init_qtip:
|
||||||
H.append(
|
H.append(
|
||||||
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>'
|
'<script src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>'
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
'<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.css" />'
|
'<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.css" />'
|
||||||
@ -253,32 +241,25 @@ def sco_header(
|
|||||||
|
|
||||||
if init_jquery_ui:
|
if init_jquery_ui:
|
||||||
H.append(
|
H.append(
|
||||||
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>'
|
'<script src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>'
|
||||||
)
|
|
||||||
# H.append('<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery-ui/js/jquery-ui-i18n.js"></script>')
|
|
||||||
H.append(
|
|
||||||
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/scodoc.js"></script>'
|
|
||||||
)
|
)
|
||||||
|
# H.append('<script src="/ScoDoc/static/libjs/jquery-ui/js/jquery-ui-i18n.js"></script>')
|
||||||
|
H.append('<script src="/ScoDoc/static/js/scodoc.js"></script>')
|
||||||
if init_google_maps:
|
if init_google_maps:
|
||||||
H.append(
|
H.append(
|
||||||
'<script type="text/javascript" src="/ScoDoc/static/libjs/jquery.ui.map.full.min.js"></script>'
|
'<script src="/ScoDoc/static/libjs/jquery.ui.map.full.min.js"></script>'
|
||||||
)
|
)
|
||||||
if init_datatables:
|
if init_datatables:
|
||||||
H.append(
|
H.append(
|
||||||
'<link rel="stylesheet" type="text/css" href="/ScoDoc/static/DataTables/datatables.min.css"/>'
|
'<link rel="stylesheet" type="text/css" href="/ScoDoc/static/DataTables/datatables.min.css"/>'
|
||||||
)
|
)
|
||||||
H.append(
|
H.append('<script src="/ScoDoc/static/DataTables/datatables.min.js"></script>')
|
||||||
'<script type="text/javascript" src="/ScoDoc/static/DataTables/datatables.min.js"></script>'
|
|
||||||
)
|
|
||||||
# JS additionels
|
# JS additionels
|
||||||
for js in javascripts:
|
for js in javascripts:
|
||||||
H.append(
|
H.append("""<script src="/ScoDoc/static/%s"></script>\n""" % js)
|
||||||
"""<script language="javascript" type="text/javascript" src="/ScoDoc/static/%s"></script>\n"""
|
|
||||||
% js
|
|
||||||
)
|
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
"""<style type="text/css">
|
"""<style>
|
||||||
.gtrcontent {
|
.gtrcontent {
|
||||||
margin-left: %(margin_left)s;
|
margin-left: %(margin_left)s;
|
||||||
height: 100%%;
|
height: 100%%;
|
||||||
@ -290,7 +271,7 @@ def sco_header(
|
|||||||
)
|
)
|
||||||
# Scripts de la page:
|
# Scripts de la page:
|
||||||
if scripts:
|
if scripts:
|
||||||
H.append("""<script language="javascript" type="text/javascript">""")
|
H.append("""<script>""")
|
||||||
for script in scripts:
|
for script in scripts:
|
||||||
H.append(script)
|
H.append(script)
|
||||||
H.append("""</script>""")
|
H.append("""</script>""")
|
||||||
|
@ -28,9 +28,8 @@
|
|||||||
"""
|
"""
|
||||||
Génération de la "sidebar" (marge gauche des pages HTML)
|
Génération de la "sidebar" (marge gauche des pages HTML)
|
||||||
"""
|
"""
|
||||||
from flask import url_for
|
from flask import render_template, url_for
|
||||||
from flask import g
|
from flask import g, request
|
||||||
from flask import request
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
@ -152,11 +151,12 @@ def sidebar():
|
|||||||
# Logo
|
# Logo
|
||||||
H.append(
|
H.append(
|
||||||
f"""<div class="logo-insidebar">
|
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/>
|
<div class="sidebar-bottom"><a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }" class="sidebar">À propos</a><br/>
|
||||||
<a href="{ scu.SCO_USER_MANUAL }" target="_blank" class="sidebar">Aide</a>
|
<a href="{ scu.SCO_USER_MANUAL }" target="_blank" class="sidebar">Aide</a>
|
||||||
</div></div>
|
</div></div>
|
||||||
<div class="logo-logo"><a href= { url_for( 'scolar.about', scodoc_dept=g.scodoc_dept ) }
|
<div class="logo-logo">
|
||||||
">{ scu.icontag("scologo_img", no_size=True) }</a>
|
<a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }">
|
||||||
|
{ scu.icontag("scologo_img", no_size=True) }</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- end of sidebar -->
|
<!-- end of sidebar -->
|
||||||
@ -167,19 +167,7 @@ def sidebar():
|
|||||||
|
|
||||||
def sidebar_dept():
|
def sidebar_dept():
|
||||||
"""Partie supérieure de la marge de gauche"""
|
"""Partie supérieure de la marge de gauche"""
|
||||||
H = [
|
return render_template(
|
||||||
f"""<h2 class="insidebar">Dépt. {sco_preferences.get_preference("DeptName")}</h2>
|
"sidebar_dept.html",
|
||||||
<a href="{url_for("scodoc.index")}" class="sidebar">Accueil</a> <br/> """
|
prefs=sco_preferences.SemPreferences(),
|
||||||
]
|
)
|
||||||
dept_intranet_url = sco_preferences.get_preference("DeptIntranetURL")
|
|
||||||
if dept_intranet_url:
|
|
||||||
H.append(
|
|
||||||
f"""<a href="{dept_intranet_url}" class="sidebar">{
|
|
||||||
sco_preferences.get_preference("DeptIntranetTitle")}</a> <br/>"""
|
|
||||||
)
|
|
||||||
# Entreprises pas encore supporté en ScoDoc8
|
|
||||||
# H.append(
|
|
||||||
# """<br/><a href="%(ScoURL)s/Entreprises" class="sidebar">Entreprises</a> <br/>"""
|
|
||||||
# % infos
|
|
||||||
# )
|
|
||||||
return "\n".join(H)
|
|
||||||
|
@ -186,6 +186,8 @@ class NotesTable(object):
|
|||||||
self.use_ue_coefs = sco_preferences.get_preference(
|
self.use_ue_coefs = sco_preferences.get_preference(
|
||||||
"use_ue_coefs", formsemestre_id
|
"use_ue_coefs", formsemestre_id
|
||||||
)
|
)
|
||||||
|
# si vrai, bloque calcul des moy gen. et d'UE.:
|
||||||
|
self.block_moyennes = self.sem["block_moyennes"]
|
||||||
# Infos sur les etudiants
|
# Infos sur les etudiants
|
||||||
self.inscrlist = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
self.inscrlist = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||||
args={"formsemestre_id": formsemestre_id}
|
args={"formsemestre_id": formsemestre_id}
|
||||||
@ -738,6 +740,7 @@ class NotesTable(object):
|
|||||||
block_computation = (
|
block_computation = (
|
||||||
self.inscrdict[etudid]["etat"] == "D"
|
self.inscrdict[etudid]["etat"] == "D"
|
||||||
or self.inscrdict[etudid]["etat"] == DEF
|
or self.inscrdict[etudid]["etat"] == DEF
|
||||||
|
or self.block_moyennes
|
||||||
)
|
)
|
||||||
|
|
||||||
moy_ues = {}
|
moy_ues = {}
|
||||||
|
@ -324,15 +324,14 @@ def list_abs_in_range(etudid, debut, fin, matin=None, moduleimpl_id=None, cursor
|
|||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""
|
"""SELECT DISTINCT A.JOUR, A.MATIN
|
||||||
SELECT DISTINCT A.JOUR, A.MATIN
|
FROM ABSENCES A
|
||||||
FROM ABSENCES A
|
WHERE A.ETUDID = %(etudid)s
|
||||||
WHERE A.ETUDID = %(etudid)s
|
AND A.ESTABS"""
|
||||||
AND A.ESTABS"""
|
|
||||||
+ ismatin
|
+ ismatin
|
||||||
+ modul
|
+ modul
|
||||||
+ """
|
+ """
|
||||||
AND A.JOUR BETWEEN %(debut)s AND %(fin)s
|
AND A.JOUR BETWEEN %(debut)s AND %(fin)s
|
||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
"etudid": etudid,
|
"etudid": etudid,
|
||||||
@ -639,7 +638,10 @@ def add_absence(
|
|||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"insert into absences (etudid,jour,estabs,estjust,matin,description, moduleimpl_id) values (%(etudid)s, %(jour)s, TRUE, %(estjust)s, %(matin)s, %(description)s, %(moduleimpl_id)s )",
|
"""
|
||||||
|
INSERT into absences (etudid,jour,estabs,estjust,matin,description, moduleimpl_id)
|
||||||
|
VALUES (%(etudid)s, %(jour)s, true, %(estjust)s, %(matin)s, %(description)s, %(moduleimpl_id)s )
|
||||||
|
""",
|
||||||
vars(),
|
vars(),
|
||||||
)
|
)
|
||||||
logdb(
|
logdb(
|
||||||
|
@ -218,7 +218,10 @@ def user_nbdays_since_last_notif(email_addr, etudid):
|
|||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""select * from absences_notifications where email = %(email_addr)s and etudid=%(etudid)s order by notification_date desc""",
|
"""SELECT * FROM absences_notifications
|
||||||
|
WHERE email = %(email_addr)s and etudid=%(etudid)s
|
||||||
|
ORDER BY notification_date DESC
|
||||||
|
""",
|
||||||
{"email_addr": email_addr, "etudid": etudid},
|
{"email_addr": email_addr, "etudid": etudid},
|
||||||
)
|
)
|
||||||
res = cursor.dictfetchone()
|
res = cursor.dictfetchone()
|
||||||
|
@ -628,14 +628,18 @@ def AnnuleAbsencesDatesNoJust(etudid, dates, moduleimpl_id=None):
|
|||||||
# supr les absences non justifiees
|
# supr les absences non justifiees
|
||||||
for date in dates:
|
for date in dates:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"delete from absences where etudid=%(etudid)s and (not estjust) and jour=%(date)s and moduleimpl_id=%(moduleimpl_id)s",
|
"""DELETE FROM absences
|
||||||
|
WHERE etudid=%(etudid)s and (not estjust) and jour=%(date)s and moduleimpl_id=%(moduleimpl_id)s
|
||||||
|
""",
|
||||||
vars(),
|
vars(),
|
||||||
)
|
)
|
||||||
sco_abs.invalidate_abs_etud_date(etudid, date)
|
sco_abs.invalidate_abs_etud_date(etudid, date)
|
||||||
# s'assure que les justificatifs ne sont pas "absents"
|
# s'assure que les justificatifs ne sont pas "absents"
|
||||||
for date in dates:
|
for date in dates:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"update absences set estabs=FALSE where etudid=%(etudid)s and jour=%(date)s and moduleimpl_id=%(moduleimpl_id)s",
|
"""UPDATE absences SET estabs=FALSE
|
||||||
|
WHERE etudid=%(etudid)s AND jour=%(date)s AND moduleimpl_id=%(moduleimpl_id)s
|
||||||
|
""",
|
||||||
vars(),
|
vars(),
|
||||||
)
|
)
|
||||||
if dates:
|
if dates:
|
||||||
@ -840,9 +844,9 @@ def ListeAbsEtud(
|
|||||||
# Formats non HTML et demande d'une seule table:
|
# Formats non HTML et demande d'une seule table:
|
||||||
if format != "html" and format != "text":
|
if format != "html" and format != "text":
|
||||||
if absjust_only == 1:
|
if absjust_only == 1:
|
||||||
return tab_absjust.make_page(format=format, REQUEST=REQUEST)
|
return tab_absjust.make_page(format=format)
|
||||||
else:
|
else:
|
||||||
return tab_absnonjust.make_page(format=format, REQUEST=REQUEST)
|
return tab_absnonjust.make_page(format=format)
|
||||||
|
|
||||||
if format == "html":
|
if format == "html":
|
||||||
# Mise en forme HTML:
|
# Mise en forme HTML:
|
||||||
|
@ -34,11 +34,11 @@
|
|||||||
|
|
||||||
Les PV de jurys et documents associés sont stockées dans un sous-repertoire de la forme
|
Les PV de jurys et documents associés sont stockées dans un sous-repertoire de la forme
|
||||||
<archivedir>/<dept>/<formsemestre_id>/<YYYY-MM-DD-HH-MM-SS>
|
<archivedir>/<dept>/<formsemestre_id>/<YYYY-MM-DD-HH-MM-SS>
|
||||||
(formsemestre_id est ici FormSemestre.scodoc7_id ou à défaut FormSemestre.id)
|
(formsemestre_id est ici FormSemestre.id)
|
||||||
|
|
||||||
Les documents liés à l'étudiant sont dans
|
Les documents liés à l'étudiant sont dans
|
||||||
<archivedir>/docetuds/<dept>/<etudid>/<YYYY-MM-DD-HH-MM-SS>
|
<archivedir>/docetuds/<dept>/<etudid>/<YYYY-MM-DD-HH-MM-SS>
|
||||||
(etudid est ici soit Identite.scodoc7id, soit à défaut Identite.id)
|
(etudid est ici Identite.id)
|
||||||
|
|
||||||
Les maquettes Apogée pour l'export des notes sont dans
|
Les maquettes Apogée pour l'export des notes sont dans
|
||||||
<archivedir>/apo_csv/<dept>/<annee_scolaire>-<sem_id>/<YYYY-MM-DD-HH-MM-SS>/<code_etape>.csv
|
<archivedir>/apo_csv/<dept>/<annee_scolaire>-<sem_id>/<YYYY-MM-DD-HH-MM-SS>/<code_etape>.csv
|
||||||
@ -47,12 +47,13 @@
|
|||||||
qui est une description (humaine, format libre) de l'archive.
|
qui est une description (humaine, format libre) de l'archive.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import glob
|
||||||
|
import mimetypes
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import glob
|
import time
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import g
|
from flask import g
|
||||||
@ -244,31 +245,15 @@ class BaseArchiver(object):
|
|||||||
log("reading archive file %s" % fname)
|
log("reading archive file %s" % fname)
|
||||||
return open(fname, "rb").read()
|
return open(fname, "rb").read()
|
||||||
|
|
||||||
def get_archived_file(self, REQUEST, oid, archive_name, filename):
|
def get_archived_file(self, oid, archive_name, filename):
|
||||||
"""Recupere donnees du fichier indiqué et envoie au client"""
|
"""Recupere donnees du fichier indiqué et envoie au client"""
|
||||||
# XXX très incomplet: devrait inférer et assigner un type MIME
|
|
||||||
archive_id = self.get_id_from_name(oid, archive_name)
|
archive_id = self.get_id_from_name(oid, archive_name)
|
||||||
data = self.get(archive_id, filename)
|
data = self.get(archive_id, filename)
|
||||||
ext = os.path.splitext(filename.lower())[1]
|
mime = mimetypes.guess_type(filename)[0]
|
||||||
if ext == ".html" or ext == ".htm":
|
if mime is None:
|
||||||
return data
|
mime = "application/octet-stream"
|
||||||
elif ext == ".xml":
|
|
||||||
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
|
return scu.send_file(data, filename, mime=mime)
|
||||||
return data
|
|
||||||
elif ext == ".xls":
|
|
||||||
return sco_excel.send_excel_file(
|
|
||||||
REQUEST, data, filename, mime=scu.XLS_MIMETYPE
|
|
||||||
)
|
|
||||||
elif ext == ".xlsx":
|
|
||||||
return sco_excel.send_excel_file(
|
|
||||||
REQUEST, data, filename, mime=scu.XLSX_MIMETYPE
|
|
||||||
)
|
|
||||||
elif ext == ".csv":
|
|
||||||
return scu.sendCSVFile(REQUEST, data, filename)
|
|
||||||
elif ext == ".pdf":
|
|
||||||
return scu.sendPDFFile(REQUEST, data, filename)
|
|
||||||
REQUEST.RESPONSE.setHeader("content-type", "application/octet-stream")
|
|
||||||
return data # should set mimetype for known files like images
|
|
||||||
|
|
||||||
|
|
||||||
class SemsArchiver(BaseArchiver):
|
class SemsArchiver(BaseArchiver):
|
||||||
@ -305,7 +290,7 @@ def do_formsemestre_archive(
|
|||||||
from app.scodoc.sco_recapcomplet import make_formsemestre_recapcomplet
|
from app.scodoc.sco_recapcomplet import make_formsemestre_recapcomplet
|
||||||
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
|
sem_archive_id = formsemestre_id
|
||||||
archive_id = PVArchive.create_obj_archive(sem_archive_id, description)
|
archive_id = PVArchive.create_obj_archive(sem_archive_id, description)
|
||||||
date = PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M")
|
date = PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M")
|
||||||
|
|
||||||
@ -519,7 +504,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
|||||||
def formsemestre_list_archives(REQUEST, formsemestre_id):
|
def formsemestre_list_archives(REQUEST, formsemestre_id):
|
||||||
"""Page listing archives"""
|
"""Page listing archives"""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
|
sem_archive_id = formsemestre_id
|
||||||
L = []
|
L = []
|
||||||
for archive_id in PVArchive.list_obj_archives(sem_archive_id):
|
for archive_id in PVArchive.list_obj_archives(sem_archive_id):
|
||||||
a = {
|
a = {
|
||||||
@ -559,11 +544,11 @@ def formsemestre_list_archives(REQUEST, formsemestre_id):
|
|||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_get_archived_file(REQUEST, formsemestre_id, archive_name, filename):
|
def formsemestre_get_archived_file(formsemestre_id, archive_name, filename):
|
||||||
"""Send file to client."""
|
"""Send file to client."""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
|
sem_archive_id = formsemestre_id
|
||||||
return PVArchive.get_archived_file(REQUEST, sem_archive_id, archive_name, filename)
|
return PVArchive.get_archived_file(sem_archive_id, archive_name, filename)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_delete_archive(
|
def formsemestre_delete_archive(
|
||||||
@ -575,7 +560,7 @@ def formsemestre_delete_archive(
|
|||||||
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
|
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
|
||||||
)
|
)
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
|
sem_archive_id = formsemestre_id
|
||||||
archive_id = PVArchive.get_id_from_name(sem_archive_id, archive_name)
|
archive_id = PVArchive.get_id_from_name(sem_archive_id, archive_name)
|
||||||
|
|
||||||
dest_url = "formsemestre_list_archives?formsemestre_id=%s" % (formsemestre_id)
|
dest_url = "formsemestre_list_archives?formsemestre_id=%s" % (formsemestre_id)
|
||||||
|
@ -65,7 +65,7 @@ def etud_list_archives_html(REQUEST, etudid):
|
|||||||
if not etuds:
|
if not etuds:
|
||||||
raise ScoValueError("étudiant inexistant")
|
raise ScoValueError("étudiant inexistant")
|
||||||
etud = etuds[0]
|
etud = etuds[0]
|
||||||
etud_archive_id = etud["scodoc7_id"] or etudid
|
etud_archive_id = etudid
|
||||||
L = []
|
L = []
|
||||||
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
||||||
a = {
|
a = {
|
||||||
@ -118,7 +118,7 @@ def add_archives_info_to_etud_list(etuds):
|
|||||||
"""
|
"""
|
||||||
for etud in etuds:
|
for etud in etuds:
|
||||||
l = []
|
l = []
|
||||||
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
|
etud_archive_id = etud["etudid"]
|
||||||
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
||||||
l.append(
|
l.append(
|
||||||
"%s (%s)"
|
"%s (%s)"
|
||||||
@ -181,7 +181,7 @@ def etud_upload_file_form(REQUEST, etudid):
|
|||||||
data = tf[2]["datafile"].read()
|
data = tf[2]["datafile"].read()
|
||||||
descr = tf[2]["description"]
|
descr = tf[2]["description"]
|
||||||
filename = tf[2]["datafile"].filename
|
filename = tf[2]["datafile"].filename
|
||||||
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
|
etud_archive_id = etud["etudid"]
|
||||||
_store_etud_file_to_new_archive(
|
_store_etud_file_to_new_archive(
|
||||||
etud_archive_id, data, filename, description=descr
|
etud_archive_id, data, filename, description=descr
|
||||||
)
|
)
|
||||||
@ -210,7 +210,7 @@ def etud_delete_archive(REQUEST, etudid, archive_name, dialog_confirmed=False):
|
|||||||
if not etuds:
|
if not etuds:
|
||||||
raise ScoValueError("étudiant inexistant")
|
raise ScoValueError("étudiant inexistant")
|
||||||
etud = etuds[0]
|
etud = etuds[0]
|
||||||
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
|
etud_archive_id = etud["etudid"]
|
||||||
archive_id = EtudsArchive.get_id_from_name(etud_archive_id, archive_name)
|
archive_id = EtudsArchive.get_id_from_name(etud_archive_id, archive_name)
|
||||||
if not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
return scu.confirm_dialog(
|
return scu.confirm_dialog(
|
||||||
@ -242,16 +242,14 @@ def etud_delete_archive(REQUEST, etudid, archive_name, dialog_confirmed=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def etud_get_archived_file(REQUEST, etudid, archive_name, filename):
|
def etud_get_archived_file(etudid, archive_name, filename):
|
||||||
"""Send file to client."""
|
"""Send file to client."""
|
||||||
etuds = sco_etud.get_etud_info(filled=True)
|
etuds = sco_etud.get_etud_info(etudid=etudid, filled=True)
|
||||||
if not etuds:
|
if not etuds:
|
||||||
raise ScoValueError("étudiant inexistant")
|
raise ScoValueError("étudiant inexistant")
|
||||||
etud = etuds[0]
|
etud = etuds[0]
|
||||||
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
|
etud_archive_id = etud["etudid"]
|
||||||
return EtudsArchive.get_archived_file(
|
return EtudsArchive.get_archived_file(etud_archive_id, archive_name, filename)
|
||||||
REQUEST, etud_archive_id, archive_name, filename
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants)
|
# --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants)
|
||||||
|
@ -35,9 +35,8 @@ from email.mime.multipart import MIMEMultipart
|
|||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.mime.base import MIMEBase
|
from email.mime.base import MIMEBase
|
||||||
from email.header import Header
|
from email.header import Header
|
||||||
|
|
||||||
from reportlab.lib.colors import Color
|
from reportlab.lib.colors import Color
|
||||||
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
|
import urllib
|
||||||
|
|
||||||
from flask import g
|
from flask import g
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
@ -444,7 +443,10 @@ def _ue_mod_bulletin(etudid, formsemestre_id, ue_id, modimpls, nt, version):
|
|||||||
if mod["mod_moy_txt"][:2] == "NA":
|
if mod["mod_moy_txt"][:2] == "NA":
|
||||||
mod["mod_moy_txt"] = "-"
|
mod["mod_moy_txt"] = "-"
|
||||||
if is_malus:
|
if is_malus:
|
||||||
if mod_moy > 0:
|
if isinstance(mod_moy, str):
|
||||||
|
mod["mod_moy_txt"] = "-"
|
||||||
|
mod["mod_coef_txt"] = "-"
|
||||||
|
elif mod_moy > 0:
|
||||||
mod["mod_moy_txt"] = scu.fmt_note(mod_moy)
|
mod["mod_moy_txt"] = scu.fmt_note(mod_moy)
|
||||||
mod["mod_coef_txt"] = "Malus"
|
mod["mod_coef_txt"] = "Malus"
|
||||||
elif mod_moy < 0:
|
elif mod_moy < 0:
|
||||||
@ -1061,7 +1063,7 @@ def _formsemestre_bulletinetud_header_html(
|
|||||||
# Menu
|
# Menu
|
||||||
endpoint = "notes.formsemestre_bulletinetud"
|
endpoint = "notes.formsemestre_bulletinetud"
|
||||||
url = REQUEST.URL0
|
url = REQUEST.URL0
|
||||||
qurl = six.moves.urllib.parse.quote_plus(url + "?" + REQUEST.QUERY_STRING)
|
qurl = urllib.parse.quote_plus(url + "?" + REQUEST.QUERY_STRING)
|
||||||
|
|
||||||
menuBul = [
|
menuBul = [
|
||||||
{
|
{
|
||||||
|
@ -100,7 +100,7 @@ class ScoDocCache:
|
|||||||
log("Error: cache set failed !")
|
log("Error: cache set failed !")
|
||||||
except:
|
except:
|
||||||
log("XXX CACHE Warning: error in set !!!")
|
log("XXX CACHE Warning: error in set !!!")
|
||||||
|
status = None
|
||||||
return status
|
return status
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -197,4 +197,4 @@ def formsemestre_estim_cost(
|
|||||||
coef_tp,
|
coef_tp,
|
||||||
)
|
)
|
||||||
|
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
@ -70,7 +70,6 @@ def report_debouche_date(start_year=None, format="html", REQUEST=None):
|
|||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
javascripts=["js/etud_info.js"],
|
javascripts=["js/etud_info.js"],
|
||||||
format=format,
|
format=format,
|
||||||
REQUEST=REQUEST,
|
|
||||||
with_html_headers=True,
|
with_html_headers=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1039,7 +1039,7 @@ def formation_table_recap(formation_id, format="html", REQUEST=None):
|
|||||||
pdf_title=title,
|
pdf_title=title,
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
|
|
||||||
def ue_list_semestre_ids(ue):
|
def ue_list_semestre_ids(ue):
|
||||||
|
@ -578,7 +578,7 @@ def _view_etuds_page(
|
|||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
H.append(tab.html())
|
H.append(tab.html())
|
||||||
|
|
||||||
@ -678,7 +678,8 @@ def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
|
|||||||
sem_id = semset["sem_id"]
|
sem_id = semset["sem_id"]
|
||||||
csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id)
|
csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id)
|
||||||
if format == "raw":
|
if format == "raw":
|
||||||
return scu.sendCSVFile(REQUEST, csv_data, etape_apo + ".txt")
|
scu.send_file(csv_data, etape_apo, suffix=".txt", mime=scu.CSV_MIMETYPE)
|
||||||
|
|
||||||
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
|
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
|
||||||
|
|
||||||
(
|
(
|
||||||
@ -753,7 +754,7 @@ def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
H += [
|
H += [
|
||||||
tab.html(),
|
tab.html(),
|
||||||
|
@ -31,27 +31,21 @@
|
|||||||
# Ancien module "scolars"
|
# Ancien module "scolars"
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from flask import url_for, g, request
|
|
||||||
|
|
||||||
from email.mime.multipart import MIMEMultipart
|
|
||||||
from email.mime.text import MIMEText
|
|
||||||
from email.header import Header
|
|
||||||
from email.mime.base import MIMEBase
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
|
from flask import url_for, g, request
|
||||||
|
from flask_mail import Message
|
||||||
|
|
||||||
|
from app import email
|
||||||
|
from app import log
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_utils import SCO_ENCODING
|
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
||||||
|
|
||||||
from app import log
|
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
|
||||||
from app.scodoc import safehtml
|
from app.scodoc import safehtml
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
from flask_mail import Message
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
from app import mail
|
|
||||||
|
|
||||||
MONTH_NAMES_ABBREV = [
|
MONTH_NAMES_ABBREV = [
|
||||||
"Jan ",
|
"Jan ",
|
||||||
@ -256,7 +250,6 @@ _identiteEditor = ndb.EditableTable(
|
|||||||
"photo_filename",
|
"photo_filename",
|
||||||
"code_ine",
|
"code_ine",
|
||||||
"code_nip",
|
"code_nip",
|
||||||
"scodoc7_id",
|
|
||||||
),
|
),
|
||||||
filter_dept=True,
|
filter_dept=True,
|
||||||
sortkey="nom",
|
sortkey="nom",
|
||||||
@ -307,7 +300,7 @@ def check_nom_prenom(cnx, nom="", prenom="", etudid=None):
|
|||||||
# Don't allow some special cars (eg used in sql regexps)
|
# Don't allow some special cars (eg used in sql regexps)
|
||||||
if scu.FORBIDDEN_CHARS_EXP.search(nom) or scu.FORBIDDEN_CHARS_EXP.search(prenom):
|
if scu.FORBIDDEN_CHARS_EXP.search(nom) or scu.FORBIDDEN_CHARS_EXP.search(prenom):
|
||||||
return False, 0
|
return False, 0
|
||||||
# Now count homonyms:
|
# Now count homonyms (dans tous les départements):
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
req = """SELECT id
|
req = """SELECT id
|
||||||
FROM identite
|
FROM identite
|
||||||
@ -440,7 +433,7 @@ def notify_etud_change(email_addr, etud, before, after, subject):
|
|||||||
"Civilité: " + etud["civilite_str"],
|
"Civilité: " + etud["civilite_str"],
|
||||||
"Nom: " + etud["nom"],
|
"Nom: " + etud["nom"],
|
||||||
"Prénom: " + etud["prenom"],
|
"Prénom: " + etud["prenom"],
|
||||||
"Etudid: " + etud["etudid"],
|
"Etudid: " + str(etud["etudid"]),
|
||||||
"\n",
|
"\n",
|
||||||
"Changements effectués:",
|
"Changements effectués:",
|
||||||
]
|
]
|
||||||
@ -456,7 +449,7 @@ def notify_etud_change(email_addr, etud, before, after, subject):
|
|||||||
log("notify_etud_change: sending notification to %s" % email_addr)
|
log("notify_etud_change: sending notification to %s" % email_addr)
|
||||||
log("notify_etud_change: subject: %s" % subject)
|
log("notify_etud_change: subject: %s" % subject)
|
||||||
log(txt)
|
log(txt)
|
||||||
mail.send_email(
|
email.send_email(
|
||||||
subject, sco_preferences.get_preference("email_from_addr"), [email_addr], txt
|
subject, sco_preferences.get_preference("email_from_addr"), [email_addr], txt
|
||||||
)
|
)
|
||||||
return txt
|
return txt
|
||||||
@ -896,7 +889,7 @@ def list_scolog(etudid):
|
|||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"select * from scolog where etudid=%(etudid)s ORDER BY DATE DESC",
|
"SELECT * FROM scolog WHERE etudid=%(etudid)s ORDER BY DATE DESC",
|
||||||
{"etudid": etudid},
|
{"etudid": etudid},
|
||||||
)
|
)
|
||||||
return cursor.dictfetchall()
|
return cursor.dictfetchall()
|
||||||
|
@ -305,6 +305,8 @@ class ScoExcelSheet:
|
|||||||
style -- style par défaut (dictionnaire cf. excel_make_style) de la feuille si non spécifié
|
style -- style par défaut (dictionnaire cf. excel_make_style) de la feuille si non spécifié
|
||||||
"""
|
"""
|
||||||
cell = WriteOnlyCell(self.ws, value or "")
|
cell = WriteOnlyCell(self.ws, value or "")
|
||||||
|
if not (isinstance(value, int) or isinstance(value, float)):
|
||||||
|
cell.data_type = "s"
|
||||||
# if style is not None and "fill" in style:
|
# if style is not None and "fill" in style:
|
||||||
# toto()
|
# toto()
|
||||||
if style is None:
|
if style is None:
|
||||||
|
@ -96,3 +96,20 @@ class ScoGenError(ScoException):
|
|||||||
|
|
||||||
class ScoInvalidDateError(ScoValueError):
|
class ScoInvalidDateError(ScoValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Pour les API JSON
|
||||||
|
class APIInvalidParams(Exception):
|
||||||
|
status_code = 400
|
||||||
|
|
||||||
|
def __init__(self, message, status_code=None, payload=None):
|
||||||
|
Exception.__init__(self)
|
||||||
|
self.message = message
|
||||||
|
if status_code is not None:
|
||||||
|
self.status_code = status_code
|
||||||
|
self.payload = payload
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
rv = dict(self.payload or ())
|
||||||
|
rv["message"] = self.message
|
||||||
|
return rv
|
||||||
|
@ -246,9 +246,7 @@ def scodoc_table_results(
|
|||||||
"&types_parcours=".join([str(x) for x in types_parcours]),
|
"&types_parcours=".join([str(x) for x in types_parcours]),
|
||||||
)
|
)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return tab.make_page(
|
return tab.make_page(format=format, with_html_headers=False)
|
||||||
format=format, with_html_headers=False, REQUEST=REQUEST
|
|
||||||
)
|
|
||||||
tab_html = tab.html()
|
tab_html = tab.html()
|
||||||
nb_rows = tab.get_nb_rows()
|
nb_rows = tab.get_nb_rows()
|
||||||
else:
|
else:
|
||||||
|
@ -225,7 +225,7 @@ def search_etuds_infos(expnom=None, code_nip=None):
|
|||||||
else:
|
else:
|
||||||
code_nip = code_nip or expnom
|
code_nip = code_nip or expnom
|
||||||
if code_nip:
|
if code_nip:
|
||||||
etuds = sco_etud.etudident_list(cnx, args={"code_nip": code_nip})
|
etuds = sco_etud.etudident_list(cnx, args={"code_nip": str(code_nip)})
|
||||||
else:
|
else:
|
||||||
etuds = []
|
etuds = []
|
||||||
sco_etud.fill_etuds_info(etuds)
|
sco_etud.fill_etuds_info(etuds)
|
||||||
@ -404,6 +404,4 @@ def search_inscr_etud_by_nip(code_nip, REQUEST=None, format="json"):
|
|||||||
)
|
)
|
||||||
tab = GenTable(columns_ids=columns_ids, rows=T)
|
tab = GenTable(columns_ids=columns_ids, rows=T)
|
||||||
|
|
||||||
return tab.make_page(
|
return tab.make_page(format=format, with_html_headers=False, publish=True)
|
||||||
format=format, with_html_headers=False, REQUEST=REQUEST, publish=True
|
|
||||||
)
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
"""Operations de base sur les formsemestres
|
"""Operations de base sur les formsemestres
|
||||||
"""
|
"""
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
import time
|
import time
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
@ -60,6 +61,7 @@ _formsemestreEditor = ndb.EditableTable(
|
|||||||
"gestion_semestrielle",
|
"gestion_semestrielle",
|
||||||
"etat",
|
"etat",
|
||||||
"bul_hide_xml",
|
"bul_hide_xml",
|
||||||
|
"block_moyennes",
|
||||||
"bul_bgcolor",
|
"bul_bgcolor",
|
||||||
"modalite",
|
"modalite",
|
||||||
"resp_can_edit",
|
"resp_can_edit",
|
||||||
@ -67,7 +69,6 @@ _formsemestreEditor = ndb.EditableTable(
|
|||||||
"ens_can_edit_eval",
|
"ens_can_edit_eval",
|
||||||
"elt_sem_apo",
|
"elt_sem_apo",
|
||||||
"elt_annee_apo",
|
"elt_annee_apo",
|
||||||
"scodoc7_id",
|
|
||||||
),
|
),
|
||||||
filter_dept=True,
|
filter_dept=True,
|
||||||
sortkey="date_debut",
|
sortkey="date_debut",
|
||||||
@ -81,6 +82,7 @@ _formsemestreEditor = ndb.EditableTable(
|
|||||||
"etat": bool,
|
"etat": bool,
|
||||||
"gestion_compensation": bool,
|
"gestion_compensation": bool,
|
||||||
"bul_hide_xml": bool,
|
"bul_hide_xml": bool,
|
||||||
|
"block_moyennes": bool,
|
||||||
"gestion_semestrielle": bool,
|
"gestion_semestrielle": bool,
|
||||||
"gestion_compensation": bool,
|
"gestion_compensation": bool,
|
||||||
"gestion_semestrielle": bool,
|
"gestion_semestrielle": bool,
|
||||||
@ -93,6 +95,10 @@ _formsemestreEditor = ndb.EditableTable(
|
|||||||
|
|
||||||
def get_formsemestre(formsemestre_id):
|
def get_formsemestre(formsemestre_id):
|
||||||
"list ONE formsemestre"
|
"list ONE formsemestre"
|
||||||
|
if not isinstance(formsemestre_id, int):
|
||||||
|
raise ScoValueError(
|
||||||
|
"""Semestre invalide, reprenez l'opération au départ ou si le problème persiste signalez l'erreur sur scodoc-devel@listes.univ-paris13.fr"""
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
sem = do_formsemestre_list(args={"formsemestre_id": formsemestre_id})[0]
|
sem = do_formsemestre_list(args={"formsemestre_id": formsemestre_id})[0]
|
||||||
return sem
|
return sem
|
||||||
@ -578,7 +584,7 @@ def view_formsemestre_by_etape(etape_apo=None, format="html", REQUEST=None):
|
|||||||
</form>""",
|
</form>""",
|
||||||
)
|
)
|
||||||
tab.base_url = "%s?etape_apo=%s" % (REQUEST.URL0, etape_apo or "")
|
tab.base_url = "%s?etape_apo=%s" % (REQUEST.URL0, etape_apo or "")
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
|
|
||||||
def sem_has_etape(sem, code_etape):
|
def sem_has_etape(sem, code_etape):
|
||||||
|
@ -501,6 +501,14 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
|||||||
"labels": [""],
|
"labels": [""],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"block_moyennes",
|
||||||
|
{
|
||||||
|
"input_type": "boolcheckbox",
|
||||||
|
"title": "Bloquer moyennes",
|
||||||
|
"explanation": "empêcher le calcul des moyennes d'UE et générale.",
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"sep",
|
"sep",
|
||||||
{
|
{
|
||||||
@ -693,7 +701,6 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
|||||||
tf[2]["bul_hide_xml"] = False
|
tf[2]["bul_hide_xml"] = False
|
||||||
else:
|
else:
|
||||||
tf[2]["bul_hide_xml"] = True
|
tf[2]["bul_hide_xml"] = True
|
||||||
|
|
||||||
# remap les identifiants de responsables:
|
# remap les identifiants de responsables:
|
||||||
tf[2]["responsable_id"] = User.get_user_id_from_nomplogin(
|
tf[2]["responsable_id"] = User.get_user_id_from_nomplogin(
|
||||||
tf[2]["responsable_id"]
|
tf[2]["responsable_id"]
|
||||||
|
@ -64,7 +64,7 @@ from app.scodoc.sco_formsemestre_custommenu import formsemestre_custommenu_html
|
|||||||
|
|
||||||
|
|
||||||
def _build_menu_stats(formsemestre_id):
|
def _build_menu_stats(formsemestre_id):
|
||||||
"Définition du menu 'Statistiques' "
|
"Définition du menu 'Statistiques'"
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"title": "Statistiques...",
|
"title": "Statistiques...",
|
||||||
@ -495,6 +495,7 @@ def formsemestre_page_title():
|
|||||||
if not formsemestre_id:
|
if not formsemestre_id:
|
||||||
return ""
|
return ""
|
||||||
try:
|
try:
|
||||||
|
formsemestre_id = int(formsemestre_id)
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id).copy()
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id).copy()
|
||||||
except:
|
except:
|
||||||
log("can't find formsemestre_id %s" % formsemestre_id)
|
log("can't find formsemestre_id %s" % formsemestre_id)
|
||||||
@ -727,7 +728,7 @@ def formsemestre_description(
|
|||||||
tab.html_before_table += "checked"
|
tab.html_before_table += "checked"
|
||||||
tab.html_before_table += ">indiquer les évaluations</input></form>"
|
tab.html_before_table += ">indiquer les évaluations</input></form>"
|
||||||
|
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
|
|
||||||
# genere liste html pour accès aux groupes de ce semestre
|
# genere liste html pour accès aux groupes de ce semestre
|
||||||
@ -913,10 +914,10 @@ def formsemestre_status_head(formsemestre_id=None, REQUEST=None, page_title=None
|
|||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
REQUEST, page_title, sem, with_page_header=False, with_h2=False
|
REQUEST, page_title, sem, with_page_header=False, with_h2=False
|
||||||
),
|
),
|
||||||
"""<table>
|
f"""<table>
|
||||||
<tr><td class="fichetitre2">Formation: </td><td>
|
<tr><td class="fichetitre2">Formation: </td><td>
|
||||||
<a href="Notes/ue_list?formation_id=%(formation_id)s" class="discretelink" title="Formation %(acronyme)s, v%(version)s">%(titre)s</a>"""
|
<a href="{url_for('notes.ue_list', scodoc_dept=g.scodoc_dept, formation_id=F['formation_id'])}"
|
||||||
% F,
|
class="discretelink" title="Formation {F['acronyme']}, v{F['version']}">{F['titre']}</a>""",
|
||||||
]
|
]
|
||||||
if sem["semestre_id"] >= 0:
|
if sem["semestre_id"] >= 0:
|
||||||
H.append(", %s %s" % (parcours.SESSION_NAME, sem["semestre_id"]))
|
H.append(", %s %s" % (parcours.SESSION_NAME, sem["semestre_id"]))
|
||||||
@ -947,10 +948,13 @@ Il y a des notes en attente ! Le classement des étudiants n'a qu'une valeur ind
|
|||||||
</td></tr>"""
|
</td></tr>"""
|
||||||
)
|
)
|
||||||
H.append("</table>")
|
H.append("</table>")
|
||||||
|
sem_warning = ""
|
||||||
if sem["bul_hide_xml"]:
|
if sem["bul_hide_xml"]:
|
||||||
H.append(
|
sem_warning += "Bulletins non publiés sur le portail. "
|
||||||
'<p class="fontorange"><em>Bulletins non publiés sur le portail</em></p>'
|
if sem["block_moyennes"]:
|
||||||
)
|
sem_warning += "Calcul des moyennes bloqué !"
|
||||||
|
if sem_warning:
|
||||||
|
H.append('<p class="fontorange"><em>' + sem_warning + "</em></p>")
|
||||||
if sem["semestre_id"] >= 0 and not sco_formsemestre.sem_une_annee(sem):
|
if sem["semestre_id"] >= 0 and not sco_formsemestre.sem_une_annee(sem):
|
||||||
H.append(
|
H.append(
|
||||||
'<p class="fontorange"><em>Attention: ce semestre couvre plusieurs années scolaires !</em></p>'
|
'<p class="fontorange"><em>Attention: ce semestre couvre plusieurs années scolaires !</em></p>'
|
||||||
|
@ -1499,7 +1499,7 @@ def _sortgroups(groups):
|
|||||||
# Tri: place 'all' en tête, puis groupe par partition / nom de groupe
|
# Tri: place 'all' en tête, puis groupe par partition / nom de groupe
|
||||||
R = [g for g in groups if g["partition_name"] is None]
|
R = [g for g in groups if g["partition_name"] is None]
|
||||||
o = [g for g in groups if g["partition_name"] != None]
|
o = [g for g in groups if g["partition_name"] != None]
|
||||||
o.sort(key=lambda x: (x["numero"], x["group_name"]))
|
o.sort(key=lambda x: (x["numero"] or 0, x["group_name"]))
|
||||||
|
|
||||||
return R + o
|
return R + o
|
||||||
|
|
||||||
|
@ -477,6 +477,9 @@ def groups_table(
|
|||||||
[(p["partition_id"], p["partition_name"]) for p in groups_infos.partitions]
|
[(p["partition_id"], p["partition_name"]) for p in groups_infos.partitions]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
partitions_name = {
|
||||||
|
p["partition_id"]: p["partition_name"] for p in groups_infos.partitions
|
||||||
|
}
|
||||||
|
|
||||||
if format != "html": # ne mentionne l'état que en Excel (style en html)
|
if format != "html": # ne mentionne l'état que en Excel (style en html)
|
||||||
columns_ids.append("etat")
|
columns_ids.append("etat")
|
||||||
@ -500,11 +503,7 @@ def groups_table(
|
|||||||
if with_annotations:
|
if with_annotations:
|
||||||
sco_etud.add_annotations_to_etud_list(groups_infos.members)
|
sco_etud.add_annotations_to_etud_list(groups_infos.members)
|
||||||
columns_ids += ["annotations_str"]
|
columns_ids += ["annotations_str"]
|
||||||
|
moodle_sem_name = groups_infos.formsemestre["session_id"]
|
||||||
if groups_infos.formsemestre["semestre_id"] >= 0:
|
|
||||||
moodle_sem_name = "S%d" % groups_infos.formsemestre["semestre_id"]
|
|
||||||
else:
|
|
||||||
moodle_sem_name = "A" # pas de semestre spécifié, que faire ?
|
|
||||||
moodle_groupenames = set()
|
moodle_groupenames = set()
|
||||||
# ajoute liens
|
# ajoute liens
|
||||||
for etud in groups_infos.members:
|
for etud in groups_infos.members:
|
||||||
@ -529,23 +528,27 @@ def groups_table(
|
|||||||
# et groupes:
|
# et groupes:
|
||||||
for partition_id in etud["partitions"]:
|
for partition_id in etud["partitions"]:
|
||||||
etud[partition_id] = etud["partitions"][partition_id]["group_name"]
|
etud[partition_id] = etud["partitions"][partition_id]["group_name"]
|
||||||
# Ajoute colonne pour moodle: semestre_groupe, de la forme S1-NomgroupeXXX
|
# Ajoute colonne pour moodle: semestre_groupe, de la forme RT-DUT-FI-S3-2021-PARTITION-GROUPE
|
||||||
moodle_groupename = []
|
moodle_groupename = []
|
||||||
if groups_infos.selected_partitions:
|
if groups_infos.selected_partitions:
|
||||||
# il y a des groupes selectionnes, utilise leurs partitions
|
# il y a des groupes selectionnes, utilise leurs partitions
|
||||||
for partition_id in groups_infos.selected_partitions:
|
for partition_id in groups_infos.selected_partitions:
|
||||||
if partition_id in etud["partitions"]:
|
if partition_id in etud["partitions"]:
|
||||||
moodle_groupename.append(
|
moodle_groupename.append(
|
||||||
etud["partitions"][partition_id]["group_name"]
|
partitions_name[partition_id]
|
||||||
|
+ "-"
|
||||||
|
+ etud["partitions"][partition_id]["group_name"]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# pas de groupes sélectionnés: prend le premier s'il y en a un
|
# pas de groupes sélectionnés: prend le premier s'il y en a un
|
||||||
|
moodle_groupename = ["tous"]
|
||||||
if etud["partitions"]:
|
if etud["partitions"]:
|
||||||
for p in etud["partitions"].items(): # partitions is an OrderedDict
|
for p in etud["partitions"].items(): # partitions is an OrderedDict
|
||||||
|
moodle_groupename = [
|
||||||
|
partitions_name[p[0]] + "-" + p[1]["group_name"]
|
||||||
|
]
|
||||||
break
|
break
|
||||||
moodle_groupename = [p[1]["group_name"]]
|
|
||||||
else:
|
|
||||||
moodle_groupename = ["tous"]
|
|
||||||
moodle_groupenames |= set(moodle_groupename)
|
moodle_groupenames |= set(moodle_groupename)
|
||||||
etud["semestre_groupe"] = moodle_sem_name + "-" + "+".join(moodle_groupename)
|
etud["semestre_groupe"] = moodle_sem_name + "-" + "+".join(moodle_groupename)
|
||||||
|
|
||||||
@ -706,7 +709,7 @@ def groups_table(
|
|||||||
):
|
):
|
||||||
if format == "moodlecsv":
|
if format == "moodlecsv":
|
||||||
format = "csv"
|
format = "csv"
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
elif format == "xlsappel":
|
elif format == "xlsappel":
|
||||||
xls = sco_excel.excel_feuille_listeappel(
|
xls = sco_excel.excel_feuille_listeappel(
|
||||||
@ -935,7 +938,7 @@ def form_choix_saisie_semaine(groups_infos, REQUEST=None):
|
|||||||
return "\n".join(FA)
|
return "\n".join(FA)
|
||||||
|
|
||||||
|
|
||||||
def export_groups_as_moodle_csv(formsemestre_id=None, REQUEST=None):
|
def export_groups_as_moodle_csv(formsemestre_id=None):
|
||||||
"""Export all students/groups, in a CSV format suitable for Moodle
|
"""Export all students/groups, in a CSV format suitable for Moodle
|
||||||
Each (student,group) will be listed on a separate line
|
Each (student,group) will be listed on a separate line
|
||||||
jo@univ.fr,S3-A
|
jo@univ.fr,S3-A
|
||||||
@ -977,4 +980,4 @@ def export_groups_as_moodle_csv(formsemestre_id=None, REQUEST=None):
|
|||||||
text_with_titles=prefs["moodle_csv_with_headerline"],
|
text_with_titles=prefs["moodle_csv_with_headerline"],
|
||||||
preferences=prefs,
|
preferences=prefs,
|
||||||
)
|
)
|
||||||
return tab.make_page(format="csv", REQUEST=REQUEST)
|
return tab.make_page(format="csv")
|
||||||
|
@ -139,18 +139,22 @@ def list_etuds_from_sem(src, dst):
|
|||||||
|
|
||||||
def list_inscrits_date(sem):
|
def list_inscrits_date(sem):
|
||||||
"""Liste les etudiants inscrits dans n'importe quel semestre
|
"""Liste les etudiants inscrits dans n'importe quel semestre
|
||||||
|
du même département
|
||||||
SAUF sem à la date de début de sem.
|
SAUF sem à la date de début de sem.
|
||||||
"""
|
"""
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
|
sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""SELECT I.etudid
|
"""SELECT ins.etudid
|
||||||
FROM notes_formsemestre_inscription I, notes_formsemestre S
|
FROM
|
||||||
WHERE I.formsemestre_id = S.id
|
notes_formsemestre_inscription ins,
|
||||||
|
notes_formsemestre S
|
||||||
|
WHERE ins.formsemestre_id = S.id
|
||||||
AND S.id != %(formsemestre_id)s
|
AND S.id != %(formsemestre_id)s
|
||||||
AND S.date_debut <= %(date_debut_iso)s
|
AND S.date_debut <= %(date_debut_iso)s
|
||||||
AND S.date_fin >= %(date_debut_iso)s
|
AND S.date_fin >= %(date_debut_iso)s
|
||||||
|
AND S.dept_id = %(dept_id)s
|
||||||
""",
|
""",
|
||||||
sem,
|
sem,
|
||||||
)
|
)
|
||||||
|
@ -27,8 +27,8 @@
|
|||||||
|
|
||||||
"""Liste des notes d'une évaluation
|
"""Liste des notes d'une évaluation
|
||||||
"""
|
"""
|
||||||
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
import urllib
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, g
|
from flask import url_for, g
|
||||||
@ -50,6 +50,7 @@ from app.scodoc import sco_groups
|
|||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
|
from app.scodoc import sco_users
|
||||||
import sco_version
|
import sco_version
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc.htmlutils import histogram_notes
|
from app.scodoc.htmlutils import histogram_notes
|
||||||
@ -481,7 +482,7 @@ def _make_table_notes(
|
|||||||
# html_generate_cells=False # la derniere ligne (moyennes) est incomplete
|
# html_generate_cells=False # la derniere ligne (moyennes) est incomplete
|
||||||
)
|
)
|
||||||
|
|
||||||
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
|
t = tab.make_page(format=format, with_html_headers=False)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return t
|
return t
|
||||||
|
|
||||||
@ -569,7 +570,7 @@ def _add_eval_columns(
|
|||||||
comment = ""
|
comment = ""
|
||||||
explanation = "%s (%s) %s" % (
|
explanation = "%s (%s) %s" % (
|
||||||
NotesDB[etudid]["date"].strftime("%d/%m/%y %Hh%M"),
|
NotesDB[etudid]["date"].strftime("%d/%m/%y %Hh%M"),
|
||||||
NotesDB[etudid]["uid"],
|
sco_users.user_info(NotesDB[etudid]["uid"])["nomcomplet"],
|
||||||
comment,
|
comment,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -816,8 +817,8 @@ def evaluation_check_absences_html(
|
|||||||
'<a class="stdlink" href="Absences/doSignaleAbsence?etudid=%s&datedebut=%s&datefin=%s&demijournee=%s&moduleimpl_id=%s">signaler cette absence</a>'
|
'<a class="stdlink" href="Absences/doSignaleAbsence?etudid=%s&datedebut=%s&datefin=%s&demijournee=%s&moduleimpl_id=%s">signaler cette absence</a>'
|
||||||
% (
|
% (
|
||||||
etud["etudid"],
|
etud["etudid"],
|
||||||
six.moves.urllib.parse.quote(E["jour"]),
|
urllib.parse.quote(E["jour"]),
|
||||||
six.moves.urllib.parse.quote(E["jour"]),
|
urllib.parse.quote(E["jour"]),
|
||||||
demijournee,
|
demijournee,
|
||||||
E["moduleimpl_id"],
|
E["moduleimpl_id"],
|
||||||
)
|
)
|
||||||
|
@ -85,7 +85,7 @@ def scodoc_table_etuds_lycees(format="html", REQUEST=None):
|
|||||||
no_links=True,
|
no_links=True,
|
||||||
)
|
)
|
||||||
tab.base_url = REQUEST.URL0
|
tab.base_url = REQUEST.URL0
|
||||||
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
|
t = tab.make_page(format=format, with_html_headers=False)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return t
|
return t
|
||||||
H = [
|
H = [
|
||||||
@ -192,7 +192,7 @@ def formsemestre_etuds_lycees(
|
|||||||
tab.base_url += "&only_primo=1"
|
tab.base_url += "&only_primo=1"
|
||||||
if no_grouping:
|
if no_grouping:
|
||||||
tab.base_url += "&no_grouping=1"
|
tab.base_url += "&no_grouping=1"
|
||||||
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
|
t = tab.make_page(format=format, with_html_headers=False)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return t
|
return t
|
||||||
F = [
|
F = [
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
"""Tableau de bord module
|
"""Tableau de bord module
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
|
import urllib
|
||||||
|
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
@ -137,7 +137,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
|
|||||||
"title": "Absences ce jour",
|
"title": "Absences ce jour",
|
||||||
"endpoint": "absences.EtatAbsencesDate",
|
"endpoint": "absences.EtatAbsencesDate",
|
||||||
"args": {
|
"args": {
|
||||||
"date": six.moves.urllib.parse.quote(E["jour"], safe=""),
|
"date": urllib.parse.quote(E["jour"], safe=""),
|
||||||
"group_ids": group_id,
|
"group_ids": group_id,
|
||||||
},
|
},
|
||||||
"enabled": E["jour"],
|
"enabled": E["jour"],
|
||||||
|
@ -45,13 +45,20 @@ class Permission(object):
|
|||||||
NBITS = 1 # maximum bits used (for formatting)
|
NBITS = 1 # maximum bits used (for formatting)
|
||||||
ALL_PERMISSIONS = [-1]
|
ALL_PERMISSIONS = [-1]
|
||||||
description = {} # { symbol : blah blah }
|
description = {} # { symbol : blah blah }
|
||||||
|
permission_by_name = {} # { symbol : int }
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def init_permissions():
|
def init_permissions():
|
||||||
for (perm, symbol, description) in _SCO_PERMISSIONS:
|
for (perm, symbol, description) in _SCO_PERMISSIONS:
|
||||||
setattr(Permission, symbol, perm)
|
setattr(Permission, symbol, perm)
|
||||||
Permission.description[symbol] = description
|
Permission.description[symbol] = description
|
||||||
|
Permission.permission_by_name[symbol] = perm
|
||||||
Permission.NBITS = len(_SCO_PERMISSIONS)
|
Permission.NBITS = len(_SCO_PERMISSIONS)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_by_name(permission_name: str) -> int:
|
||||||
|
"""Return permission mode (integer bit field), or None if it doesn't exist."""
|
||||||
|
return Permission.permission_by_name.get(permission_name)
|
||||||
|
|
||||||
|
|
||||||
Permission.init_permissions()
|
Permission.init_permissions()
|
||||||
|
@ -217,6 +217,5 @@ def formsemestre_poursuite_report(formsemestre_id, format="html", REQUEST=None):
|
|||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
javascripts=["js/etud_info.js"],
|
javascripts=["js/etud_info.js"],
|
||||||
format=format,
|
format=format,
|
||||||
REQUEST=REQUEST,
|
|
||||||
with_html_headers=True,
|
with_html_headers=True,
|
||||||
)
|
)
|
||||||
|
@ -112,6 +112,7 @@ get_base_preferences(formsemestre_id)
|
|||||||
"""
|
"""
|
||||||
import flask
|
import flask
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
from app.models import Departement
|
from app.models import Departement
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
@ -180,7 +181,7 @@ def _convert_pref_type(p, pref_spec):
|
|||||||
|
|
||||||
def _get_pref_default_value_from_config(name, pref_spec):
|
def _get_pref_default_value_from_config(name, pref_spec):
|
||||||
"""get default value store in application level config.
|
"""get default value store in application level config.
|
||||||
If not found, use defalut value hardcoded in pref_spec.
|
If not found, use default value hardcoded in pref_spec.
|
||||||
"""
|
"""
|
||||||
# XXX va changer avec la nouvelle base
|
# XXX va changer avec la nouvelle base
|
||||||
# search in scu.CONFIG
|
# search in scu.CONFIG
|
||||||
@ -1408,7 +1409,7 @@ class BasePreferences(object):
|
|||||||
{
|
{
|
||||||
"initvalue": 1,
|
"initvalue": 1,
|
||||||
"title": "Indique si les bulletins sont publiés",
|
"title": "Indique si les bulletins sont publiés",
|
||||||
"explanation": "décocher si vous n'avez pas de portal étudiant publiant les bulletins",
|
"explanation": "décocher si vous n'avez pas de portail étudiant publiant les bulletins",
|
||||||
"input_type": "boolcheckbox",
|
"input_type": "boolcheckbox",
|
||||||
"labels": ["non", "oui"],
|
"labels": ["non", "oui"],
|
||||||
"category": "bul",
|
"category": "bul",
|
||||||
@ -1891,7 +1892,7 @@ class BasePreferences(object):
|
|||||||
|
|
||||||
def get(self, formsemestre_id, name):
|
def get(self, formsemestre_id, name):
|
||||||
"""Returns preference value.
|
"""Returns preference value.
|
||||||
If global_lookup, when no value defined for this semestre, returns global value.
|
when no value defined for this semestre, returns global value.
|
||||||
"""
|
"""
|
||||||
params = {
|
params = {
|
||||||
"dept_id": self.dept_id,
|
"dept_id": self.dept_id,
|
||||||
@ -1901,7 +1902,7 @@ class BasePreferences(object):
|
|||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
plist = self._editor.list(cnx, params)
|
plist = self._editor.list(cnx, params)
|
||||||
if not plist:
|
if not plist:
|
||||||
del params["formsemestre_id"]
|
params["formsemestre_id"] = None
|
||||||
plist = self._editor.list(cnx, params)
|
plist = self._editor.list(cnx, params)
|
||||||
if not plist:
|
if not plist:
|
||||||
return self.default[name]
|
return self.default[name]
|
||||||
@ -2022,7 +2023,9 @@ class BasePreferences(object):
|
|||||||
html_sco_header.sco_header(page_title="Préférences"),
|
html_sco_header.sco_header(page_title="Préférences"),
|
||||||
"<h2>Préférences globales pour %s</h2>" % scu.ScoURL(),
|
"<h2>Préférences globales pour %s</h2>" % scu.ScoURL(),
|
||||||
f"""<p><a href="{url_for("scolar.config_logos", scodoc_dept=g.scodoc_dept)
|
f"""<p><a href="{url_for("scolar.config_logos", scodoc_dept=g.scodoc_dept)
|
||||||
}">modification des logos du département (pour documents pdf)</a></p>""",
|
}">modification des logos du département (pour documents pdf)</a></p>"""
|
||||||
|
if current_user.is_administrator()
|
||||||
|
else "",
|
||||||
"""<p class="help">Ces paramètres s'appliquent par défaut à tous les semestres, sauf si ceux-ci définissent des valeurs spécifiques.</p>
|
"""<p class="help">Ces paramètres s'appliquent par défaut à tous les semestres, sauf si ceux-ci définissent des valeurs spécifiques.</p>
|
||||||
<p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
|
<p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
|
||||||
""",
|
""",
|
||||||
@ -2253,7 +2256,7 @@ function set_global_pref(el, pref_name) {
|
|||||||
|
|
||||||
#
|
#
|
||||||
def doc_preferences():
|
def doc_preferences():
|
||||||
""" Liste les preferences en MarkDown, pour la documentation"""
|
"""Liste les preferences en MarkDown, pour la documentation"""
|
||||||
L = []
|
L = []
|
||||||
for cat, cat_descr in PREF_CATEGORIES:
|
for cat, cat_descr in PREF_CATEGORIES:
|
||||||
L.append([""])
|
L.append([""])
|
||||||
|
@ -535,7 +535,6 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True, REQUEST=No
|
|||||||
return tab.make_page(
|
return tab.make_page(
|
||||||
format=format,
|
format=format,
|
||||||
with_html_headers=False,
|
with_html_headers=False,
|
||||||
REQUEST=REQUEST,
|
|
||||||
publish=publish,
|
publish=publish,
|
||||||
)
|
)
|
||||||
tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
|
tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
|
||||||
|
@ -27,8 +27,9 @@
|
|||||||
|
|
||||||
"""Tableau recapitulatif des notes d'un semestre
|
"""Tableau recapitulatif des notes d'un semestre
|
||||||
"""
|
"""
|
||||||
import time
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
|
import time
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
@ -227,11 +228,14 @@ def do_formsemestre_recapcomplet(
|
|||||||
if format == "xml" or format == "html":
|
if format == "xml" or format == "html":
|
||||||
return data
|
return data
|
||||||
elif format == "csv":
|
elif format == "csv":
|
||||||
return scu.sendCSVFile(REQUEST, data, filename)
|
return scu.send_file(data, filename=filename, mime=scu.CSV_MIMETYPE)
|
||||||
elif format[:3] == "xls":
|
elif format[:3] == "xls" or format[:3] == "xlsx":
|
||||||
return sco_excel.send_excel_file(REQUEST, data, filename)
|
return scu.send_file(data, filename=filename, mime=scu.XLSX_MIMETYPE)
|
||||||
elif format == "json":
|
elif format == "json":
|
||||||
return scu.sendJSON(REQUEST, data)
|
js = json.dumps(data, indent=1, cls=scu.ScoDocJSONEncoder)
|
||||||
|
return scu.send_file(
|
||||||
|
js, filename=filename, suffix=scu.JSON_SUFFIX, mime=scu.JSON_MIMETYPE
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError("unknown format %s" % format)
|
raise ValueError("unknown format %s" % format)
|
||||||
|
|
||||||
|
@ -355,7 +355,6 @@ def formsemestre_report_counts(
|
|||||||
t = tab.make_page(
|
t = tab.make_page(
|
||||||
title="""<h2 class="formsemestre">Comptes croisés</h2>""",
|
title="""<h2 class="formsemestre">Comptes croisés</h2>""",
|
||||||
format=format,
|
format=format,
|
||||||
REQUEST=REQUEST,
|
|
||||||
with_html_headers=False,
|
with_html_headers=False,
|
||||||
)
|
)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
@ -722,7 +721,7 @@ def formsemestre_suivi_cohorte(
|
|||||||
)
|
)
|
||||||
if only_primo:
|
if only_primo:
|
||||||
tab.base_url += "&only_primo=on"
|
tab.base_url += "&only_primo=on"
|
||||||
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
|
t = tab.make_page(format=format, with_html_headers=False)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return t
|
return t
|
||||||
|
|
||||||
@ -1210,7 +1209,7 @@ def formsemestre_suivi_parcours(
|
|||||||
tab.base_url += "&only_primo=1"
|
tab.base_url += "&only_primo=1"
|
||||||
if no_grouping:
|
if no_grouping:
|
||||||
tab.base_url += "&no_grouping=1"
|
tab.base_url += "&no_grouping=1"
|
||||||
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
|
t = tab.make_page(format=format, with_html_headers=False)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return t
|
return t
|
||||||
F = [
|
F = [
|
||||||
|
@ -9,48 +9,50 @@ from app.scodoc.sco_permissions import Permission as p
|
|||||||
SCO_ROLES_DEFAULTS = {
|
SCO_ROLES_DEFAULTS = {
|
||||||
"Observateur": (p.ScoObservateur,),
|
"Observateur": (p.ScoObservateur,),
|
||||||
"Ens": (
|
"Ens": (
|
||||||
p.ScoObservateur,
|
|
||||||
p.ScoView,
|
|
||||||
p.ScoEnsView,
|
|
||||||
p.ScoUsersView,
|
|
||||||
p.ScoEtudAddAnnotations,
|
|
||||||
p.ScoAbsChange,
|
|
||||||
p.ScoAbsAddBillet,
|
p.ScoAbsAddBillet,
|
||||||
|
p.ScoAbsChange,
|
||||||
|
p.ScoEnsView,
|
||||||
p.ScoEntrepriseView,
|
p.ScoEntrepriseView,
|
||||||
|
p.ScoEtudAddAnnotations,
|
||||||
|
p.ScoObservateur,
|
||||||
|
p.ScoUsersView,
|
||||||
|
p.ScoView,
|
||||||
),
|
),
|
||||||
"Secr": (
|
"Secr": (
|
||||||
p.ScoObservateur,
|
|
||||||
p.ScoView,
|
|
||||||
p.ScoUsersView,
|
|
||||||
p.ScoEtudAddAnnotations,
|
|
||||||
p.ScoAbsChange,
|
|
||||||
p.ScoAbsAddBillet,
|
p.ScoAbsAddBillet,
|
||||||
p.ScoEntrepriseView,
|
p.ScoAbsChange,
|
||||||
|
p.ScoEditApo,
|
||||||
p.ScoEntrepriseChange,
|
p.ScoEntrepriseChange,
|
||||||
|
p.ScoEntrepriseView,
|
||||||
|
p.ScoEtudAddAnnotations,
|
||||||
p.ScoEtudChangeAdr,
|
p.ScoEtudChangeAdr,
|
||||||
|
p.ScoObservateur,
|
||||||
|
p.ScoUsersView,
|
||||||
|
p.ScoView,
|
||||||
),
|
),
|
||||||
# Admin est le chef du département, pas le "super admin"
|
# Admin est le chef du département, pas le "super admin"
|
||||||
# on doit donc lister toutes ses permissions:
|
# on doit donc lister toutes ses permissions:
|
||||||
"Admin": (
|
"Admin": (
|
||||||
p.ScoObservateur,
|
|
||||||
p.ScoView,
|
|
||||||
p.ScoEnsView,
|
|
||||||
p.ScoUsersView,
|
|
||||||
p.ScoEtudAddAnnotations,
|
|
||||||
p.ScoAbsChange,
|
|
||||||
p.ScoAbsAddBillet,
|
p.ScoAbsAddBillet,
|
||||||
p.ScoEntrepriseView,
|
p.ScoAbsChange,
|
||||||
p.ScoEntrepriseChange,
|
|
||||||
p.ScoEtudChangeAdr,
|
|
||||||
p.ScoChangeFormation,
|
p.ScoChangeFormation,
|
||||||
p.ScoEditFormationTags,
|
p.ScoChangePreferences,
|
||||||
p.ScoEditAllNotes,
|
|
||||||
p.ScoEditAllEvals,
|
p.ScoEditAllEvals,
|
||||||
p.ScoImplement,
|
p.ScoEditAllNotes,
|
||||||
|
p.ScoEditApo,
|
||||||
|
p.ScoEditFormationTags,
|
||||||
|
p.ScoEnsView,
|
||||||
|
p.ScoEntrepriseChange,
|
||||||
|
p.ScoEntrepriseView,
|
||||||
|
p.ScoEtudAddAnnotations,
|
||||||
|
p.ScoEtudChangeAdr,
|
||||||
p.ScoEtudChangeGroups,
|
p.ScoEtudChangeGroups,
|
||||||
p.ScoEtudInscrit,
|
p.ScoEtudInscrit,
|
||||||
|
p.ScoImplement,
|
||||||
|
p.ScoObservateur,
|
||||||
p.ScoUsersAdmin,
|
p.ScoUsersAdmin,
|
||||||
p.ScoChangePreferences,
|
p.ScoUsersView,
|
||||||
|
p.ScoView,
|
||||||
),
|
),
|
||||||
# RespPE est le responsable poursuites d'études
|
# RespPE est le responsable poursuites d'études
|
||||||
# il peut ajouter des tags sur les formations:
|
# il peut ajouter des tags sur les formations:
|
||||||
|
@ -495,8 +495,9 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
|
|||||||
ndb.quote_dict(aa)
|
ndb.quote_dict(aa)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""INSERT INTO notes_notes
|
"""INSERT INTO notes_notes
|
||||||
(etudid,evaluation_id,value,comment,date,uid)
|
(etudid, evaluation_id, value, comment, date, uid)
|
||||||
VALUES (%(etudid)s,%(evaluation_id)s,%(value)s,%(comment)s,%(date)s,%(uid)s)""",
|
VALUES (%(etudid)s,%(evaluation_id)s,%(value)s,%(comment)s,%(date)s,%(uid)s)
|
||||||
|
""",
|
||||||
aa,
|
aa,
|
||||||
)
|
)
|
||||||
changed = True
|
changed = True
|
||||||
@ -589,18 +590,17 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
|
|||||||
|
|
||||||
def saisie_notes_tableur(evaluation_id, group_ids=[], REQUEST=None):
|
def saisie_notes_tableur(evaluation_id, group_ids=[], REQUEST=None):
|
||||||
"""Saisie des notes via un fichier Excel"""
|
"""Saisie des notes via un fichier Excel"""
|
||||||
authuser = REQUEST.AUTHENTICATED_USER
|
|
||||||
authusername = str(authuser)
|
|
||||||
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
|
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
|
||||||
if not evals:
|
if not evals:
|
||||||
raise ScoValueError("invalid evaluation_id")
|
raise ScoValueError("invalid evaluation_id")
|
||||||
E = evals[0]
|
E = evals[0]
|
||||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||||
formsemestre_id = M["formsemestre_id"]
|
formsemestre_id = M["formsemestre_id"]
|
||||||
if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_id"]):
|
if not sco_permissions_check.can_edit_notes(current_user, E["moduleimpl_id"]):
|
||||||
return (
|
return (
|
||||||
html_sco_header.sco_header()
|
html_sco_header.sco_header()
|
||||||
+ "<h2>Modification des notes impossible pour %s</h2>" % authusername
|
+ "<h2>Modification des notes impossible pour %s</h2>"
|
||||||
|
% current_user.user_name
|
||||||
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
||||||
avez l'autorisation d'effectuer cette opération)</p>
|
avez l'autorisation d'effectuer cette opération)</p>
|
||||||
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
|
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
|
||||||
@ -858,9 +858,7 @@ def has_existing_decision(M, E, etudid):
|
|||||||
|
|
||||||
def saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
|
def saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
|
||||||
"""Formulaire saisie notes d'une évaluation pour un groupe"""
|
"""Formulaire saisie notes d'une évaluation pour un groupe"""
|
||||||
authuser = REQUEST.AUTHENTICATED_USER
|
group_ids = [int(group_id) for group_id in group_ids]
|
||||||
authusername = str(authuser)
|
|
||||||
|
|
||||||
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
|
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
|
||||||
if not evals:
|
if not evals:
|
||||||
raise ScoValueError("invalid evaluation_id")
|
raise ScoValueError("invalid evaluation_id")
|
||||||
@ -871,10 +869,11 @@ def saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
|
|||||||
formsemestre_id = M["formsemestre_id"]
|
formsemestre_id = M["formsemestre_id"]
|
||||||
# Check access
|
# Check access
|
||||||
# (admin, respformation, and responsable_id)
|
# (admin, respformation, and responsable_id)
|
||||||
if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_id"]):
|
if not sco_permissions_check.can_edit_notes(current_user, E["moduleimpl_id"]):
|
||||||
return (
|
return (
|
||||||
html_sco_header.sco_header()
|
html_sco_header.sco_header()
|
||||||
+ "<h2>Modification des notes impossible pour %s</h2>" % authusername
|
+ "<h2>Modification des notes impossible pour %s</h2>"
|
||||||
|
% current_user.user_name
|
||||||
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
||||||
avez l'autorisation d'effectuer cette opération)</p>
|
avez l'autorisation d'effectuer cette opération)</p>
|
||||||
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
|
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
|
||||||
@ -1154,9 +1153,9 @@ def _form_saisie_notes(E, M, group_ids, destination="", REQUEST=None):
|
|||||||
"attributes": [
|
"attributes": [
|
||||||
'class="note%s"' % classdem,
|
'class="note%s"' % classdem,
|
||||||
disabled_attr,
|
disabled_attr,
|
||||||
"data-last-saved-value=%s" % e["val"],
|
'data-last-saved-value="%s"' % e["val"],
|
||||||
"data-orig-value=%s" % e["val"],
|
'data-orig-value="%s"' % e["val"],
|
||||||
"data-etudid=%s" % etudid,
|
'data-etudid="%s"' % etudid,
|
||||||
],
|
],
|
||||||
"template": """<tr%(item_dom_attr)s class="etud_elem """
|
"template": """<tr%(item_dom_attr)s class="etud_elem """
|
||||||
+ " ".join(etud_classes)
|
+ " ".join(etud_classes)
|
||||||
@ -1290,7 +1289,7 @@ def get_note_history_menu(evaluation_id, etudid):
|
|||||||
nv = "" # ne repete pas la valeur de la note courante
|
nv = "" # ne repete pas la valeur de la note courante
|
||||||
else:
|
else:
|
||||||
# ancienne valeur
|
# ancienne valeur
|
||||||
nv = '<span class="histvalue">: %s</span>' % dispnote
|
nv = ": %s" % dispnote
|
||||||
first = False
|
first = False
|
||||||
if i["comment"]:
|
if i["comment"]:
|
||||||
comment = ' <span class="histcomment">%s</span>' % i["comment"]
|
comment = ' <span class="histcomment">%s</span>' % i["comment"]
|
||||||
|
@ -418,7 +418,7 @@ def do_semset_remove_sem(semset_id, formsemestre_id):
|
|||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def semset_page(format="html", REQUEST=None):
|
def semset_page(format="html"):
|
||||||
"""Page avec liste semsets:
|
"""Page avec liste semsets:
|
||||||
Table avec : date_debut date_fin titre liste des semestres
|
Table avec : date_debut date_fin titre liste des semestres
|
||||||
"""
|
"""
|
||||||
@ -468,7 +468,7 @@ def semset_page(format="html", REQUEST=None):
|
|||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
page_title = "Ensembles de semestres"
|
page_title = "Ensembles de semestres"
|
||||||
H = [
|
H = [
|
||||||
|
@ -400,7 +400,10 @@ def list_synch(sem, anneeapogee=None):
|
|||||||
def key2etud(key, etud_apo=False):
|
def key2etud(key, etud_apo=False):
|
||||||
if not etud_apo:
|
if not etud_apo:
|
||||||
etudid = key2etudid[key]
|
etudid = key2etudid[key]
|
||||||
etud = sco_etud.identite_list(cnx, {"etudid": etudid})[0]
|
etuds = sco_etud.identite_list(cnx, {"etudid": etudid})
|
||||||
|
if not etuds: # ? cela ne devrait pas arriver XXX
|
||||||
|
log(f"XXX key2etud etudid={{etudid}}, type {{type(etudid)}}")
|
||||||
|
etud = etuds[0]
|
||||||
etud["inscrit"] = is_inscrit # checkbox state
|
etud["inscrit"] = is_inscrit # checkbox state
|
||||||
etud[
|
etud[
|
||||||
"datefinalisationinscription"
|
"datefinalisationinscription"
|
||||||
@ -508,7 +511,14 @@ def list_all(etudsapo_set):
|
|||||||
# d'interrogation par etudiant.
|
# d'interrogation par etudiant.
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
cursor.execute("SELECT " + EKEY_SCO + ", id AS etudid FROM identite")
|
cursor.execute(
|
||||||
|
"SELECT "
|
||||||
|
+ EKEY_SCO
|
||||||
|
+ """, id AS etudid
|
||||||
|
FROM identite WHERE dept_id=%(dept_id)s
|
||||||
|
""",
|
||||||
|
{"dept_id": g.scodoc_dept_id},
|
||||||
|
)
|
||||||
key2etudid = dict([(x[0], x[1]) for x in cursor.fetchall()])
|
key2etudid = dict([(x[0], x[1]) for x in cursor.fetchall()])
|
||||||
all_set = set(key2etudid.keys())
|
all_set = set(key2etudid.keys())
|
||||||
|
|
||||||
|
@ -167,7 +167,7 @@ def evaluation_list_operations(evaluation_id, REQUEST=None):
|
|||||||
% (E["description"], E["jour"]),
|
% (E["description"], E["jour"]),
|
||||||
preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
|
preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
|
||||||
)
|
)
|
||||||
return tab.make_page(REQUEST=REQUEST)
|
return tab.make_page()
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_list_saisies_notes(formsemestre_id, format="html", REQUEST=None):
|
def formsemestre_list_saisies_notes(formsemestre_id, format="html", REQUEST=None):
|
||||||
@ -222,7 +222,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html", REQUEST=None
|
|||||||
+ scu.timedate_human_repr()
|
+ scu.timedate_human_repr()
|
||||||
+ "",
|
+ "",
|
||||||
)
|
)
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
|
|
||||||
def get_note_history(evaluation_id, etudid, REQUEST=None, fmt=""):
|
def get_note_history(evaluation_id, etudid, REQUEST=None, fmt=""):
|
||||||
|
@ -217,7 +217,7 @@ def list_users(
|
|||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
|
return tab.make_page(format=format, with_html_headers=False)
|
||||||
|
|
||||||
|
|
||||||
def get_user_list(dept=None, with_inactives=False):
|
def get_user_list(dept=None, with_inactives=False):
|
||||||
@ -258,6 +258,7 @@ def user_info(user_name_or_id=None, user=None):
|
|||||||
info = u.to_dict()
|
info = u.to_dict()
|
||||||
else:
|
else:
|
||||||
info = None
|
info = None
|
||||||
|
user_name = "inconnu"
|
||||||
else:
|
else:
|
||||||
info = user.to_dict()
|
info = user.to_dict()
|
||||||
user_name = user.user_name
|
user_name = user.user_name
|
||||||
|
@ -2612,7 +2612,8 @@ div.maindiv {
|
|||||||
margin: 1em;
|
margin: 1em;
|
||||||
}
|
}
|
||||||
ul.main {
|
ul.main {
|
||||||
list-style-type: square;
|
list-style-type: square;
|
||||||
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.main li {
|
ul.main li {
|
||||||
|
BIN
app/static/icons/favicon.ico
Normal file
BIN
app/static/icons/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
25
app/templates/about.html
Normal file
25
app/templates/about.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
|
||||||
|
<h2>Système de gestion scolarité</h2>
|
||||||
|
|
||||||
|
<p>© Emmanuel Viennet 2021</p>
|
||||||
|
|
||||||
|
<p>Version {{ version }}</p>
|
||||||
|
|
||||||
|
<p>ScoDoc est un logiciel libre écrit en
|
||||||
|
<a href="http://www.python.org" target="_blank" rel="noopener noreferrer">Python</a>.
|
||||||
|
Information et documentation sur <a href="https://scodoc.org" target="_blank">scodoc.org</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Dernières évolutions</h2>
|
||||||
|
|
||||||
|
{{ news|safe }}
|
||||||
|
|
||||||
|
<div class="about-logo">
|
||||||
|
{{ logo|safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -2,14 +2,19 @@
|
|||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
<h1>Sign In</h1>
|
<h1>Connexion</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
{{ wtf.quick_form(form) }}
|
{{ wtf.quick_form(form) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
Forgot Your Password?
|
En cas d'oubli de votre mot de passe
|
||||||
<a href="{{ url_for('auth.reset_password_request') }}">Click to Reset It</a>
|
<a href="{{ url_for('auth.reset_password_request') }}">cliquez ici pour le réinitialiser</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p class="help">L'accès à ScoDoc est strictement réservé aux personnels de
|
||||||
|
l'établissement. Les étudiants n'y ont pas accès. Pour toute information,
|
||||||
|
contactez la personne responsable de votre établissement.</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -11,7 +11,7 @@
|
|||||||
ou écrire la liste "notes" <a href="mailto:notes@listes.univ-paris13.fr">notes@listes.univ-paris13.fr</a> en
|
ou écrire la liste "notes" <a href="mailto:notes@listes.univ-paris13.fr">notes@listes.univ-paris13.fr</a> en
|
||||||
indiquant la version du logiciel
|
indiquant la version du logiciel
|
||||||
<br />
|
<br />
|
||||||
(plus d'informations sur les listes de diffusion<a href="https://scodoc.org/ListesDeDiffusion/">voir
|
(plus d'informations sur les listes de diffusion <a href="https://scodoc.org/ListesDeDiffusion/">voir
|
||||||
cette page</a>).
|
cette page</a>).
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -24,9 +24,9 @@
|
|||||||
|
|
||||||
<p> Si le problème persiste après intervention de votre équipe locale,
|
<p> Si le problème persiste après intervention de votre équipe locale,
|
||||||
contacter la liste "notes" <a href="mailto:notes@listes.univ-paris13.fr">notes@listes.univ-paris13.fr</a> en
|
contacter la liste "notes" <a href="mailto:notes@listes.univ-paris13.fr">notes@listes.univ-paris13.fr</a> en
|
||||||
indiquant la version du logiciel (ScoDoc {SCOVERSION})
|
indiquant la version du logiciel (ScoDoc {{SCOVERSION}})
|
||||||
<br />(pour plus d'informations sur les listes de diffusion <a
|
<br />(pour plus d'informations sur les listes de diffusion
|
||||||
href="https://scodoc.org/ListesDeDiffusion/">voir cette page</a>).
|
<a href="https://scodoc.org/ListesDeDiffusion/">voir cette page</a>).
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
<h2>ScoDoc: gestion scolarité (version béta)</h2>
|
<h2>ScoDoc 9 - suivi scolarité</h2>
|
||||||
|
|
||||||
{% if not current_user.is_anonymous %}
|
{% if not current_user.is_anonymous %}
|
||||||
<p>Bonjour <font color="red"><b>{{current_user.get_nomcomplet()}}</b>
|
<p>Bonjour <font color="red"><b>{{current_user.get_nomcomplet()}}</b>
|
||||||
@ -24,10 +24,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>
|
|
||||||
<font color="red">Ceci est une version de test,
|
|
||||||
ne pas utiliser en production !</font>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
<form action="{{url_for('scodoc.table_etud_in_accessible_depts')}}" method="POST">
|
<form action="{{url_for('scodoc.table_etud_in_accessible_depts')}}" method="POST">
|
||||||
@ -43,4 +39,9 @@
|
|||||||
<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> -->
|
||||||
|
|
||||||
|
<div style="margin-top: 1cm;">
|
||||||
|
Service réservé aux personnels et enseignants, basé sur <a href="{{url_for('scodoc.about')}}">le logiciel libre
|
||||||
|
ScoDoc.</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
12
app/templates/sidebar_dept.html
Normal file
12
app/templates/sidebar_dept.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<h2 class="insidebar">Dépt. {{ prefs["DeptName"] }}</h2>
|
||||||
|
<a href="{{ url_for('scodoc.index') }}" class="sidebar">Accueil</a> <br/>
|
||||||
|
{% if prefs["DeptIntranetURL"] %}
|
||||||
|
<a href="{{ prefs["DeptIntranetURL"] }}" class="sidebar">
|
||||||
|
{{ prefs["DeptIntranetTitle"] }}</a>
|
||||||
|
{% endif %}
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
{#
|
||||||
|
# Entreprises pas encore supporté en ScoDoc8
|
||||||
|
# <br/><a href="%(ScoURL)s/Entreprises" class="sidebar">Entreprises</a> <br/>
|
||||||
|
#}
|
@ -47,20 +47,17 @@ L'API de plus bas niveau est en gros:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import calendar
|
import calendar
|
||||||
import cgi
|
|
||||||
import datetime
|
import datetime
|
||||||
import dateutil
|
import dateutil
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
import re
|
import re
|
||||||
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
|
|
||||||
import string
|
|
||||||
import time
|
import time
|
||||||
|
import urllib
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import g
|
from flask import g
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from flask import current_app
|
|
||||||
|
|
||||||
from app.decorators import (
|
from app.decorators import (
|
||||||
scodoc,
|
scodoc,
|
||||||
@ -79,7 +76,7 @@ from app.scodoc import notesdb as ndb
|
|||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.sco_exceptions import ScoValueError, ScoInvalidDateError
|
from app.scodoc.sco_exceptions import ScoValueError, APIInvalidParams
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
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
|
||||||
@ -277,8 +274,9 @@ def doSignaleAbsenceGrSemestre(
|
|||||||
Efface les absences aux dates indiquées par dates,
|
Efface les absences aux dates indiquées par dates,
|
||||||
ou bien ajoute celles de abslist.
|
ou bien ajoute celles de abslist.
|
||||||
"""
|
"""
|
||||||
|
moduleimpl_id = moduleimpl_id or None
|
||||||
if etudids:
|
if etudids:
|
||||||
etudids = etudids.split(",")
|
etudids = [int(x) for x in str(etudids).split(",")]
|
||||||
else:
|
else:
|
||||||
etudids = []
|
etudids = []
|
||||||
if dates:
|
if dates:
|
||||||
@ -306,14 +304,14 @@ def doSignaleAbsenceGrSemestre(
|
|||||||
@permission_required(Permission.ScoAbsChange)
|
@permission_required(Permission.ScoAbsChange)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def SignaleAbsenceGrHebdo(
|
def SignaleAbsenceGrHebdo(
|
||||||
datelundi, group_ids=[], destination="", moduleimpl_id=None, REQUEST=None
|
datelundi, group_ids=[], destination="", moduleimpl_id=None, formsemestre_id=None
|
||||||
):
|
):
|
||||||
"Saisie hebdomadaire des absences"
|
"Saisie hebdomadaire des absences"
|
||||||
if not moduleimpl_id:
|
if not moduleimpl_id:
|
||||||
moduleimpl_id = None
|
moduleimpl_id = None
|
||||||
|
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
group_ids, moduleimpl_id=moduleimpl_id
|
group_ids, moduleimpl_id=moduleimpl_id, formsemestre_id=formsemestre_id
|
||||||
)
|
)
|
||||||
if not groups_infos.members:
|
if not groups_infos.members:
|
||||||
return (
|
return (
|
||||||
@ -325,7 +323,7 @@ def SignaleAbsenceGrHebdo(
|
|||||||
base_url = "SignaleAbsenceGrHebdo?datelundi=%s&%s&destination=%s" % (
|
base_url = "SignaleAbsenceGrHebdo?datelundi=%s&%s&destination=%s" % (
|
||||||
datelundi,
|
datelundi,
|
||||||
groups_infos.groups_query_args,
|
groups_infos.groups_query_args,
|
||||||
six.moves.urllib.parse.quote(destination),
|
urllib.parse.quote(destination),
|
||||||
)
|
)
|
||||||
|
|
||||||
formsemestre_id = groups_infos.formsemestre_id
|
formsemestre_id = groups_infos.formsemestre_id
|
||||||
@ -509,7 +507,7 @@ def SignaleAbsenceGrSemestre(
|
|||||||
datedebut,
|
datedebut,
|
||||||
datefin,
|
datefin,
|
||||||
groups_infos.groups_query_args,
|
groups_infos.groups_query_args,
|
||||||
six.moves.urllib.parse.quote(destination),
|
urllib.parse.quote(destination),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
base_url = base_url_noweeks + "&nbweeks=%s" % nbweeks # sans le moduleimpl_id
|
base_url = base_url_noweeks + "&nbweeks=%s" % nbweeks # sans le moduleimpl_id
|
||||||
@ -809,7 +807,7 @@ def _gen_form_saisie_groupe(
|
|||||||
H.append('<input type="hidden" name="dates" value="%s"/>' % ",".join(dates))
|
H.append('<input type="hidden" name="dates" value="%s"/>' % ",".join(dates))
|
||||||
H.append(
|
H.append(
|
||||||
'<input type="hidden" name="destination" value="%s"/>'
|
'<input type="hidden" name="destination" value="%s"/>'
|
||||||
% six.moves.urllib.parse.quote(destination)
|
% urllib.parse.quote(destination)
|
||||||
)
|
)
|
||||||
#
|
#
|
||||||
# version pour formulaire avec AJAX (Yann LB)
|
# version pour formulaire avec AJAX (Yann LB)
|
||||||
@ -965,7 +963,7 @@ ou entrez une date pour visualiser les absents un jour donné :
|
|||||||
"""
|
"""
|
||||||
% (REQUEST.URL0, formsemestre_id, groups_infos.get_form_elem()),
|
% (REQUEST.URL0, formsemestre_id, groups_infos.get_form_elem()),
|
||||||
)
|
)
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/EtatAbsencesDate")
|
@bp.route("/EtatAbsencesDate")
|
||||||
@ -1101,7 +1099,7 @@ def AddBilletAbsence(
|
|||||||
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
|
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
|
||||||
tab = _tableBillets(billets, etud=etud)
|
tab = _tableBillets(billets, etud=etud)
|
||||||
log("AddBilletAbsence: new billet_id=%s (%gs)" % (billet_id, time.time() - t0))
|
log("AddBilletAbsence: new billet_id=%s (%gs)" % (billet_id, time.time() - t0))
|
||||||
return tab.make_page(REQUEST=REQUEST, format="xml")
|
return tab.make_page(format="xml")
|
||||||
else:
|
else:
|
||||||
return billet_id
|
return billet_id
|
||||||
|
|
||||||
@ -1232,7 +1230,7 @@ def listeBilletsEtud(etudid=False, REQUEST=None, format="html"):
|
|||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
billets = sco_abs.billet_absence_list(cnx, {"etudid": etud["etudid"]})
|
billets = sco_abs.billet_absence_list(cnx, {"etudid": etud["etudid"]})
|
||||||
tab = _tableBillets(billets, etud=etud)
|
tab = _tableBillets(billets, etud=etud)
|
||||||
return tab.make_page(REQUEST=REQUEST, format=format)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
|
|
||||||
@bp.route(
|
@bp.route(
|
||||||
@ -1479,20 +1477,24 @@ def ProcessBilletAbsenceForm(billet_id, REQUEST=None):
|
|||||||
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"""
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
etud = sco_etud.get_etud_info(filled=False)[0]
|
etuds = sco_etud.get_etud_info(filled=False)
|
||||||
|
if not etuds:
|
||||||
|
raise APIInvalidParams("étudiant inconnu")
|
||||||
|
# raise ScoValueError("étudiant inconnu")
|
||||||
|
etud = etuds[0]
|
||||||
exp = re.compile(r"^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$")
|
exp = re.compile(r"^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$")
|
||||||
if not exp.match(beg_date):
|
if not exp.match(beg_date):
|
||||||
raise ScoValueError("invalid date: %s" % beg_date)
|
raise ScoValueError("invalid date: %s" % beg_date)
|
||||||
if not exp.match(end_date):
|
if not exp.match(end_date):
|
||||||
raise ScoValueError("invalid date: %s" % end_date)
|
raise ScoValueError("invalid date: %s" % end_date)
|
||||||
|
|
||||||
Abs = sco_abs.list_abs_date(etud["etudid"], beg_date, end_date)
|
abs_list = sco_abs.list_abs_date(etud["etudid"], beg_date, end_date)
|
||||||
|
|
||||||
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
|
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
|
||||||
doc = ElementTree.Element(
|
doc = ElementTree.Element(
|
||||||
"absences", etudid=etud["etudid"], beg_date=beg_date, end_date=end_date
|
"absences", etudid=str(etud["etudid"]), beg_date=beg_date, end_date=end_date
|
||||||
)
|
)
|
||||||
for a in Abs:
|
for a in abs_list:
|
||||||
if a["estabs"]: # ne donne pas les justifications si pas d'absence
|
if a["estabs"]: # ne donne pas les justifications si pas d'absence
|
||||||
doc.append(
|
doc.append(
|
||||||
ElementTree.Element(
|
ElementTree.Element(
|
||||||
@ -1500,7 +1502,7 @@ def XMLgetAbsEtud(beg_date="", end_date="", REQUEST=None):
|
|||||||
begin=a["begin"],
|
begin=a["begin"],
|
||||||
end=a["end"],
|
end=a["end"],
|
||||||
description=a["description"],
|
description=a["description"],
|
||||||
justified=a["estjust"],
|
justified=str(int(a["estjust"])),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
log("XMLgetAbsEtud (%gs)" % (time.time() - t0))
|
log("XMLgetAbsEtud (%gs)" % (time.time() - t0))
|
||||||
|
@ -224,7 +224,7 @@ def index_html(REQUEST=None, etud_nom=None, limit=50, offset="", format="html"):
|
|||||||
preferences=context.get_preferences(),
|
preferences=context.get_preferences(),
|
||||||
)
|
)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
else:
|
else:
|
||||||
H = [
|
H = [
|
||||||
entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"),
|
entreprise_header(REQUEST=REQUEST, page_title="Suivi entreprises"),
|
||||||
@ -297,7 +297,7 @@ def entreprise_contact_list(entreprise_id=None, format="html", REQUEST=None):
|
|||||||
preferences=context.get_preferences(),
|
preferences=context.get_preferences(),
|
||||||
)
|
)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
H.append(tab.html())
|
H.append(tab.html())
|
||||||
|
|
||||||
@ -403,7 +403,7 @@ def entreprise_correspondant_list(
|
|||||||
preferences=context.get_preferences(),
|
preferences=context.get_preferences(),
|
||||||
)
|
)
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
H.append(tab.html())
|
H.append(tab.html())
|
||||||
|
|
||||||
|
@ -647,7 +647,11 @@ def XMLgetFormsemestres(etape_apo=None, formsemestre_id=None, REQUEST=None):
|
|||||||
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
|
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
|
||||||
doc = ElementTree.Element("formsemestrelist")
|
doc = ElementTree.Element("formsemestrelist")
|
||||||
for sem in sco_formsemestre.do_formsemestre_list(args=args):
|
for sem in sco_formsemestre.do_formsemestre_list(args=args):
|
||||||
doc.append("formsemestre", **sem)
|
for k in sem:
|
||||||
|
if isinstance(sem[k], int):
|
||||||
|
sem[k] = str(sem[k])
|
||||||
|
sem_elt = ElementTree.Element("formsemestre", **sem)
|
||||||
|
doc.append(sem_elt)
|
||||||
|
|
||||||
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
|
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
|
||||||
|
|
||||||
@ -1086,7 +1090,7 @@ def view_module_abs(REQUEST, moduleimpl_id, format="html"):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if format != "html":
|
if format != "html":
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
return "\n".join(H) + tab.html() + html_sco_header.sco_footer()
|
return "\n".join(H) + tab.html() + html_sco_header.sco_footer()
|
||||||
|
|
||||||
@ -1253,7 +1257,7 @@ def formsemestre_enseignants_list(REQUEST, formsemestre_id, format="html"):
|
|||||||
caption="Tous les enseignants (responsables ou associés aux modules de ce semestre) apparaissent. Le nombre de saisies d'absences est le nombre d'opérations d'ajout effectuées sur ce semestre, sans tenir compte des annulations ou double saisies.",
|
caption="Tous les enseignants (responsables ou associés aux modules de ce semestre) apparaissent. Le nombre de saisies d'absences est le nombre d'opérations d'ajout effectuées sur ce semestre, sans tenir compte des annulations ou double saisies.",
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
)
|
)
|
||||||
return T.make_page(page_title=title, title=title, REQUEST=REQUEST, format=format)
|
return T.make_page(page_title=title, title=title, format=format)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/edit_enseignants_form_delete", methods=["GET", "POST"])
|
@bp.route("/edit_enseignants_form_delete", methods=["GET", "POST"])
|
||||||
|
@ -37,6 +37,7 @@ import flask
|
|||||||
from flask import abort, flash, url_for, redirect, render_template, send_file
|
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
|
||||||
|
import flask_login
|
||||||
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 flask_wtf.file import FileField, FileAllowed
|
||||||
@ -57,6 +58,7 @@ from app.scodoc import sco_utils as scu
|
|||||||
from app.decorators import (
|
from app.decorators import (
|
||||||
admin_required,
|
admin_required,
|
||||||
scodoc7func,
|
scodoc7func,
|
||||||
|
scodoc,
|
||||||
permission_required_compat_scodoc7,
|
permission_required_compat_scodoc7,
|
||||||
)
|
)
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
@ -131,6 +133,43 @@ def get_etud_dept():
|
|||||||
return Departement.query.get(last_etud.dept_id).acronym
|
return Departement.query.get(last_etud.dept_id).acronym
|
||||||
|
|
||||||
|
|
||||||
|
# Bricolage pour le portail IUTV avec ScoDoc 7: (DEPRECATED: NE PAS UTILISER !)
|
||||||
|
@bp.route(
|
||||||
|
"/ScoDoc/search_inscr_etud_by_nip", methods=["GET"]
|
||||||
|
) # pour compat anciens clients PHP
|
||||||
|
@scodoc
|
||||||
|
@scodoc7func
|
||||||
|
def search_inscr_etud_by_nip(
|
||||||
|
code_nip, REQUEST=None, format="json", __ac_name="", __ac_password=""
|
||||||
|
):
|
||||||
|
auth_ok = False
|
||||||
|
user_name = __ac_name
|
||||||
|
user_password = __ac_password
|
||||||
|
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)
|
||||||
|
if not auth_ok:
|
||||||
|
abort(403)
|
||||||
|
else:
|
||||||
|
return sco_find_etud.search_inscr_etud_by_nip(
|
||||||
|
code_nip=code_nip, REQUEST=REQUEST, format=format
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/ScoDoc/about")
|
||||||
|
@bp.route("/ScoDoc/Scolarite/<scodoc_dept>/about")
|
||||||
|
def about(scodoc_dept=None):
|
||||||
|
"version info"
|
||||||
|
return render_template(
|
||||||
|
"about.html",
|
||||||
|
version=scu.get_scodoc_version(),
|
||||||
|
news=sco_version.SCONEWS,
|
||||||
|
logo=scu.icontag("borgne_img"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ---- CONFIGURATION
|
# ---- CONFIGURATION
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,34 +131,6 @@ def sco_publish(route, function, permission, methods=["GET"]):
|
|||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/about")
|
|
||||||
@scodoc
|
|
||||||
@permission_required(Permission.ScoView)
|
|
||||||
@scodoc7func
|
|
||||||
def about():
|
|
||||||
"version info"
|
|
||||||
H = [
|
|
||||||
"""<h2>Système de gestion scolarité</h2>
|
|
||||||
<p>© Emmanuel Viennet 1997-2021</p>
|
|
||||||
<p>Version %s</p>
|
|
||||||
"""
|
|
||||||
% (scu.get_scodoc_version())
|
|
||||||
]
|
|
||||||
H.append(
|
|
||||||
'<p>Logiciel libre écrit en <a href="http://www.python.org" target="_blank" rel="noopener noreferrer">Python</a>.</p>'
|
|
||||||
)
|
|
||||||
H.append("<h2>Dernières évolutions</h2>" + sco_version.SCONEWS)
|
|
||||||
H.append(
|
|
||||||
'<div class="about-logo">'
|
|
||||||
+ scu.icontag("borgne_img")
|
|
||||||
+ " <em>Au pays des aveugles...</em></div>"
|
|
||||||
)
|
|
||||||
d = ""
|
|
||||||
return (
|
|
||||||
html_sco_header.sco_header() + "\n".join(H) + d + html_sco_header.sco_footer()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# PREFERENCES
|
# PREFERENCES
|
||||||
@ -307,19 +279,10 @@ def showEtudLog(etudid, format="html", REQUEST=None):
|
|||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
|
|
||||||
# ---------- PAGE ACCUEIL (listes) --------------
|
# ---------- PAGE ACCUEIL (listes) --------------
|
||||||
# @bp.route("/")
|
|
||||||
@bp.route("/kimo")
|
|
||||||
@scodoc
|
|
||||||
@permission_required(Permission.ScoView)
|
|
||||||
@scodoc7func
|
|
||||||
def kimo(REQUEST=None, showcodes=0, showsemtable=0):
|
|
||||||
import time
|
|
||||||
|
|
||||||
return f"{time.time()} := {g.scodoc_dept}"
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/")
|
@bp.route("/")
|
||||||
|
@ -97,11 +97,12 @@ def user_info(user_name, format="json", REQUEST=None):
|
|||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoUsersAdmin)
|
@permission_required(Permission.ScoUsersAdmin)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def create_user_form(REQUEST, user_name=None, edit=0):
|
def create_user_form(REQUEST, user_name=None, edit=0, all_roles=1):
|
||||||
"form. creation ou edit utilisateur"
|
"form. création ou edition utilisateur"
|
||||||
auth_dept = current_user.dept
|
auth_dept = current_user.dept
|
||||||
initvalues = {}
|
initvalues = {}
|
||||||
edit = int(edit)
|
edit = int(edit)
|
||||||
|
all_roles = int(all_roles)
|
||||||
H = [html_sco_header.sco_header(bodyOnLoad="init_tf_form('')")]
|
H = [html_sco_header.sco_header(bodyOnLoad="init_tf_form('')")]
|
||||||
F = html_sco_header.sco_footer()
|
F = html_sco_header.sco_footer()
|
||||||
if edit:
|
if edit:
|
||||||
@ -120,11 +121,19 @@ def create_user_form(REQUEST, user_name=None, edit=0):
|
|||||||
H.append("""<p class="warning">Vous êtes super administrateur !</p>""")
|
H.append("""<p class="warning">Vous êtes super administrateur !</p>""")
|
||||||
is_super_admin = True
|
is_super_admin = True
|
||||||
|
|
||||||
# Les rôles standards créés à l'initialisation de ScoDoc:
|
if all_roles:
|
||||||
standard_roles = [
|
# tous sauf SuperAdmin
|
||||||
Role.get_named_role(r) for r in ("Ens", "Secr", "Admin", "RespPe")
|
standard_roles = [
|
||||||
]
|
r
|
||||||
# Rôles pouvant etre attribués aux utilisateurs via ce dialogue:
|
for r in Role.query.all()
|
||||||
|
if r.permissions != Permission.ALL_PERMISSIONS[0]
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
# Les rôles standards créés à l'initialisation de ScoDoc:
|
||||||
|
standard_roles = [
|
||||||
|
Role.get_named_role(r) for r in ("Ens", "Secr", "Admin", "RespPe")
|
||||||
|
]
|
||||||
|
# Départements auxquels ont peut associer des rôles 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
|
||||||
if is_super_admin:
|
if is_super_admin:
|
||||||
@ -209,7 +218,7 @@ def create_user_form(REQUEST, user_name=None, edit=0):
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"passwd",
|
"password",
|
||||||
{
|
{
|
||||||
"title": "Mot de passe",
|
"title": "Mot de passe",
|
||||||
"input_type": "password",
|
"input_type": "password",
|
||||||
@ -219,7 +228,7 @@ def create_user_form(REQUEST, user_name=None, edit=0):
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"passwd2",
|
"password2",
|
||||||
{
|
{
|
||||||
"title": "Confirmer mot de passe",
|
"title": "Confirmer mot de passe",
|
||||||
"input_type": "password",
|
"input_type": "password",
|
||||||
@ -392,11 +401,11 @@ def create_user_form(REQUEST, user_name=None, edit=0):
|
|||||||
|
|
||||||
return "\n".join(H) + "\n" + tf[1] + F
|
return "\n".join(H) + "\n" + tf[1] + F
|
||||||
|
|
||||||
if edit: # modif utilisateur (mais pas passwd ni user_name !)
|
if edit: # modif utilisateur (mais pas password ni user_name !)
|
||||||
if (not can_choose_dept) and "dept" in vals:
|
if (not can_choose_dept) and "dept" in vals:
|
||||||
del vals["dept"]
|
del vals["dept"]
|
||||||
if "passwd" in vals:
|
if "password" in vals:
|
||||||
del vals["passwd"]
|
del vals["passwordd"]
|
||||||
if "date_modif_passwd" in vals:
|
if "date_modif_passwd" in vals:
|
||||||
del vals["date_modif_passwd"]
|
del vals["date_modif_passwd"]
|
||||||
if "user_name" in vals:
|
if "user_name" in vals:
|
||||||
@ -442,13 +451,13 @@ def create_user_form(REQUEST, user_name=None, edit=0):
|
|||||||
)
|
)
|
||||||
return "\n".join(H) + msg + "\n" + tf[1] + F
|
return "\n".join(H) + msg + "\n" + tf[1] + F
|
||||||
# check passwords
|
# check passwords
|
||||||
if vals["passwd"]:
|
if vals["password"]:
|
||||||
if vals["passwd"] != vals["passwd2"]:
|
if vals["password"] != vals["password2"]:
|
||||||
msg = tf_error_message(
|
msg = tf_error_message(
|
||||||
"""Les deux mots de passes ne correspondent pas !"""
|
"""Les deux mots de passes ne correspondent pas !"""
|
||||||
)
|
)
|
||||||
return "\n".join(H) + msg + "\n" + tf[1] + F
|
return "\n".join(H) + msg + "\n" + tf[1] + F
|
||||||
if not sco_users.is_valid_password(vals["passwd"]):
|
if not sco_users.is_valid_password(vals["password"]):
|
||||||
msg = tf_error_message(
|
msg = tf_error_message(
|
||||||
"""Mot de passe trop simple, recommencez !"""
|
"""Mot de passe trop simple, recommencez !"""
|
||||||
)
|
)
|
||||||
|
@ -30,6 +30,8 @@ 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")
|
||||||
|
# evite confusion avec le log nginx scodoc_error.log:
|
||||||
|
SCODOC_ERR_FILE = os.path.join(SCODOC_VAR_DIR, "log", "scodoc_exc.log")
|
||||||
#
|
#
|
||||||
MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # Flask uploads
|
MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # Flask uploads
|
||||||
|
|
||||||
@ -53,7 +55,7 @@ class DevConfig(Config):
|
|||||||
DEBUG = True
|
DEBUG = True
|
||||||
TESTING = False
|
TESTING = False
|
||||||
SQLALCHEMY_DATABASE_URI = (
|
SQLALCHEMY_DATABASE_URI = (
|
||||||
os.environ.get("SCODOC_DEV_DATABASE_URI") or "postgresql:///SCODOC_DEV"
|
os.environ.get("SCODOC_DATABASE_URI") or "postgresql:///SCODOC_DEV"
|
||||||
)
|
)
|
||||||
SECRET_KEY = os.environ.get("DEV_SECRET_KEY") or "bb3faec7d9a34eb68a8e3e710087d87a"
|
SECRET_KEY = os.environ.get("DEV_SECRET_KEY") or "bb3faec7d9a34eb68a8e3e710087d87a"
|
||||||
|
|
||||||
@ -62,7 +64,7 @@ class TestConfig(DevConfig):
|
|||||||
TESTING = True
|
TESTING = True
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
SQLALCHEMY_DATABASE_URI = (
|
SQLALCHEMY_DATABASE_URI = (
|
||||||
os.environ.get("SCODOC_TEST_DATABASE_URI") or "postgresql:///SCODOC_TEST"
|
os.environ.get("SCODOC_DATABASE_URI") or "postgresql:///SCODOC_TEST"
|
||||||
)
|
)
|
||||||
SERVER_NAME = os.environ.get("SCODOC_TEST_SERVER_NAME") or "test.gr"
|
SERVER_NAME = os.environ.get("SCODOC_TEST_SERVER_NAME") or "test.gr"
|
||||||
DEPT_TEST = "TEST_" # nom du département, ne pas l'utiliser pour un "vrai"
|
DEPT_TEST = "TEST_" # nom du département, ne pas l'utiliser pour un "vrai"
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
"""Flag bloquage calcul moyennes
|
||||||
|
|
||||||
|
Revision ID: 669065fb2d20
|
||||||
|
Revises: a217bf588f4c
|
||||||
|
Create Date: 2021-09-16 22:04:11.624632
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '669065fb2d20'
|
||||||
|
down_revision = 'a217bf588f4c'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('notes_formsemestre', sa.Column('block_moyennes', sa.Boolean(), server_default='false', nullable=False))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('notes_formsemestre', 'block_moyennes')
|
||||||
|
# ### end Alembic commands ###
|
@ -1,68 +1,69 @@
|
|||||||
|
|
||||||
Notes sur la restauration de la base SQL complete
|
Notes sur la restauration de la base SQL complete (ScoDoc 9)
|
||||||
(dans le cas d'une réinstallation sur une autre machine, par exemple)
|
(dans le cas d'une réinstallation sur une autre machine, par exemple)
|
||||||
|
|
||||||
|
|
||||||
1) Sur la machine origine, faire un dump complet:
|
1) Sur la machine origine, faire un dump complet:
|
||||||
su postgres
|
su postgres
|
||||||
cd /tmp # ou ailleurs...
|
cd /tmp # ou ailleurs...
|
||||||
pg_dumpall > scodoc.dump.txt
|
pg_dumpall | gzip > scodoc.dump.txt.gz
|
||||||
|
|
||||||
On obtient un fichier texte assez volumineux (on peut utiliser gzip pour le compresser avant transfert).
|
On obtient un fichier texte assez volumineux (comprimé par gzip ci-dessus)
|
||||||
|
|
||||||
Le copier sur la machine destination.
|
Le copier sur la machine destination, et le décompresser (gunzip).
|
||||||
|
|
||||||
2) Sur la machine destination:
|
2) Sur la machine destination:
|
||||||
|
|
||||||
Avant toute chose, stopper scodoc:
|
Avant toute chose, stopper scodoc:
|
||||||
/etc/init.d/scodoc stop (ou systemctl stop scodoc))
|
|
||||||
|
systemctl stop scodoc9
|
||||||
|
|
||||||
1.1) Supprimer toutes les bases ScoDoc existantes s'il y en a:
|
1.1) Supprimer toutes les bases ScoDoc existantes s'il y en a:
|
||||||
su postgres
|
su scodoc
|
||||||
psql -l
|
psql -l
|
||||||
liste les bases: celles de ScoDoc sont SCO*
|
liste les bases: celles de ScoDoc sont SCO*
|
||||||
|
|
||||||
Pour chaque base SCO*, faire dropdb
|
Pour chaque base SCO*, faire dropdb
|
||||||
dropdb SCOUSERS
|
dropdb SCODOC
|
||||||
dropdb SCOGEII
|
dropdb SCODOC_DEV
|
||||||
...
|
...
|
||||||
|
|
||||||
Pour les feignants, voici un script (à lancer comme utilisateur postgres):
|
Pour les gens pressés, voici un script (à lancer comme utilisateur postgres):
|
||||||
for f in $(psql -l --no-align --field-separator . | grep SCO | cut -f 1 -d.); do
|
for f in $(psql -l --no-align --field-separator . | grep SCO | cut -f 1 -d.); do
|
||||||
echo dropping $f
|
echo dropping $f
|
||||||
dropdb $f
|
dropdb $f
|
||||||
done
|
done
|
||||||
|
|
||||||
1.2) Charger le dump (toujours comme utilisateur postgres):
|
1.2) Charger le dump (toujours comme utilisateur scodoc):
|
||||||
psql -f scodoc.dump.txt postgres
|
psql -f scodoc.dump.txt scodoc
|
||||||
|
|
||||||
1.3) Recopier les fichiers (photos, config, archives): copier le repertoire complet
|
1.3) Recopier les fichiers (photos, config, archives): copier le repertoire complet
|
||||||
/opt/scodoc/instance/var
|
/opt/scodoc-data
|
||||||
de la machine origine vers la nouvelle
|
de la machine origine vers la nouvelle
|
||||||
|
|
||||||
|
|
||||||
Puis redemarrer ScoDoc:
|
Puis redemarrer ScoDoc:
|
||||||
en tant que root: /etc/init.d/scodoc start (ou systemctl start scodoc)
|
en tant que root: systemctl start scodoc9
|
||||||
|
|
||||||
NB: si la version des sources a changée, lancer imperativement le script de mise a jour
|
NB: si la version des sources a changée, lancer imperativement le script de mise a jour
|
||||||
avant de redemarrer scodoc, afin qu'il change si besoin la base de donnees:
|
avant de redemarrer scodoc, afin qu'il change si besoin la base de donnees:
|
||||||
(en tant que root):
|
(en tant que root):
|
||||||
cd /opt/scodoc/instance/Products/ScoDoc/config
|
apt-get update &&
|
||||||
./upgrade.sh
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
Cas d'une seule base à copier: (eg un seul département, mais faire
|
Cas d'une seule base à copier (base production ou dev. par exemple)
|
||||||
attention aux utilisateurs definis dans la base SCOUSERS):
|
|
||||||
|
|
||||||
En tant qu'utilisateur "postgres":
|
En tant qu'utilisateur "scodoc":
|
||||||
Dump: (script avec commande de creation de la base)
|
Dump: permettant de la recharger en changeant son nom
|
||||||
pg_dump --create SCOINFO > /tmp/scoinfo.dump
|
pg_dump --format=custom --file=/tmp/SCODOC.dump SCODOC
|
||||||
|
|
||||||
Restore: (si necessaire, utiliser dropdb avant)
|
Restore: (si necessaire, utiliser dropdb avant)
|
||||||
psql -f /tmp/scoinfo.dump postgres
|
createdb SCODOC_IUTV
|
||||||
|
pg_restore -d SCODOC_IUTV SCODOC.dump
|
||||||
|
(si on veut garder le même nom de base que l'origine, utiliser --create )
|
||||||
|
|
||||||
---
|
--- à revoir
|
||||||
Cas d'un dump via sco_dump_db (assistance):
|
Cas d'un dump via sco_dump_db (assistance):
|
||||||
createdb -E UTF-8 SCOXXX
|
createdb -E UTF-8 SCOXXX
|
||||||
zcat xxx | psql SCOXXX
|
zcat xxx | psql SCOXXX
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
-- Creation des tables pour gestion notes
|
-- Creation des tables pour gestion notes ScoDoc 7 (OBSOLETE !)
|
||||||
-- E. Viennet, Sep 2005
|
-- E. Viennet, Sep 2005
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.0.13"
|
SCOVERSION = "9.0.28"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
SCONEWS = """
|
SCONEWS = """
|
||||||
<h4>Année 2021</h4>
|
<h4>Année 2021</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li>ScoDoc 9: nouvelle architecture logicielle</li>
|
<li>ScoDoc 9: nouvelle architecture logicielle (Flask/Python3/Debian 11)</li>
|
||||||
<li>Version mobile (en test)</li>
|
<li>Version mobile (en test)</li>
|
||||||
<li>Évaluations de type "deuxième session"</li>
|
<li>Évaluations de type "deuxième session"</li>
|
||||||
<li>Gestion du genre neutre (pas d'affichage de la civilité)</li>
|
<li>Gestion du genre neutre (pas d'affichage de la civilité)</li>
|
||||||
|
120
scodoc.py
120
scodoc.py
@ -6,24 +6,22 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
|
||||||
from pprint import pprint as pp
|
from pprint import pprint as pp
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import flask
|
import flask
|
||||||
from flask.cli import with_appcontext
|
from flask.cli import with_appcontext
|
||||||
|
|
||||||
from app import create_app, cli, db
|
from app import create_app, cli, db
|
||||||
from app import initialize_scodoc_database
|
from app import initialize_scodoc_database
|
||||||
from app import clear_scodoc_cache
|
from app import clear_scodoc_cache
|
||||||
|
from app import models
|
||||||
|
|
||||||
from app.auth.models import User, Role, UserRole
|
from app.auth.models import User, Role, UserRole
|
||||||
from app import models
|
|
||||||
from app.models import ScoPreference
|
from app.models import ScoPreference
|
||||||
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.views import notes, scolar, absences
|
from app.views import notes, scolar, absences
|
||||||
import tools
|
import tools
|
||||||
|
|
||||||
@ -45,6 +43,7 @@ def make_shell_context():
|
|||||||
"User": User,
|
"User": User,
|
||||||
"Role": Role,
|
"Role": Role,
|
||||||
"UserRole": UserRole,
|
"UserRole": UserRole,
|
||||||
|
"Permission": Permission,
|
||||||
"notes": notes,
|
"notes": notes,
|
||||||
"scolar": scolar,
|
"scolar": scolar,
|
||||||
"ndb": ndb,
|
"ndb": ndb,
|
||||||
@ -142,13 +141,90 @@ def user_password(username, password=None): # user-password
|
|||||||
return 1
|
return 1
|
||||||
u = User.query.filter_by(user_name=username).first()
|
u = User.query.filter_by(user_name=username).first()
|
||||||
if not u:
|
if not u:
|
||||||
sys.stderr.write("user_password: user {} does not exists".format(username))
|
sys.stderr.write(f"user_password: user {username} does not exists\n")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
u.set_password(password)
|
u.set_password(password)
|
||||||
db.session.add(u)
|
db.session.add(u)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
click.echo("changed password for user {}".format(u))
|
click.echo(f"changed password for user {u}")
|
||||||
|
|
||||||
|
|
||||||
|
@app.cli.command()
|
||||||
|
@click.argument("rolename")
|
||||||
|
@click.argument("permissions", nargs=-1)
|
||||||
|
def create_role(rolename, permissions): # create-role
|
||||||
|
"""Create a new role"""
|
||||||
|
# Check rolename
|
||||||
|
if not re.match(r"^[a-zA-Z0-9]+$", rolename):
|
||||||
|
sys.stderr.write(f"create_role: invalid rolename {rolename}\n")
|
||||||
|
return 1
|
||||||
|
# Check permissions
|
||||||
|
permission_list = []
|
||||||
|
for permission_name in permissions:
|
||||||
|
perm = Permission.get_by_name(permission_name)
|
||||||
|
if not perm:
|
||||||
|
sys.stderr.write(f"create_role: invalid permission name {perm}\n")
|
||||||
|
sys.stderr.write(
|
||||||
|
f"\tavailable permissions: {', '.join([ name for name in Permission.permission_by_name])}.\n"
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
permission_list.append(perm)
|
||||||
|
|
||||||
|
role = Role.query.filter_by(name=rolename).first()
|
||||||
|
if role:
|
||||||
|
sys.stderr.write(f"create_role: role {rolename} already exists\n")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
role = Role(name=rolename)
|
||||||
|
for perm in permission_list:
|
||||||
|
role.add_permission(perm)
|
||||||
|
db.session.add(role)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@app.cli.command()
|
||||||
|
@click.argument("rolename")
|
||||||
|
@click.option("-a", "--add", "addpermissionname")
|
||||||
|
@click.option("-r", "--remove", "removepermissionname")
|
||||||
|
def edit_role(rolename, addpermissionname=None, removepermissionname=None): # edit-role
|
||||||
|
"""Add [-a] and/or remove [-r] a permission to/from a role.
|
||||||
|
In ScoDoc, permissions are not associated to users but to roles.
|
||||||
|
Each user has a set of roles in each departement.
|
||||||
|
|
||||||
|
Example: `flask edit-role -a ScoEditApo Ens`
|
||||||
|
"""
|
||||||
|
if addpermissionname:
|
||||||
|
perm_to_add = Permission.get_by_name(addpermissionname)
|
||||||
|
if not perm_to_add:
|
||||||
|
sys.stderr.write(
|
||||||
|
f"edit_role: permission {addpermissionname} does not exists\n"
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
perm_to_add = None
|
||||||
|
if removepermissionname:
|
||||||
|
perm_to_remove = Permission.get_by_name(removepermissionname)
|
||||||
|
if not perm_to_remove:
|
||||||
|
sys.stderr.write(
|
||||||
|
f"edit_role: permission {removepermissionname} does not exists\n"
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
perm_to_remove = None
|
||||||
|
role = Role.query.filter_by(name=rolename).first()
|
||||||
|
if not role:
|
||||||
|
sys.stderr.write(f"edit_role: role {rolename} does not exists\n")
|
||||||
|
return 1
|
||||||
|
if perm_to_add:
|
||||||
|
role.add_permission(perm_to_add)
|
||||||
|
click.echo(f"adding permission {addpermissionname} to role {rolename}")
|
||||||
|
if perm_to_remove:
|
||||||
|
role.remove_permission(perm_to_remove)
|
||||||
|
click.echo(f"removing permission {removepermissionname} from role {rolename}")
|
||||||
|
if perm_to_add or perm_to_remove:
|
||||||
|
db.session.add(role)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
@ -191,7 +267,7 @@ def create_dept(dept): # create-dept
|
|||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
def import_scodoc7_users(): # import-scodoc7-users
|
def import_scodoc7_users(): # import-scodoc7-users
|
||||||
"""Import used defined in ScoDoc7 postgresql database into ScoDoc 9
|
"""Import users defined in ScoDoc7 postgresql database into ScoDoc 9
|
||||||
The old database SCOUSERS must be alive and readable by the current user.
|
The old database SCOUSERS must be alive and readable by the current user.
|
||||||
This script is typically run as unix user "scodoc".
|
This script is typically run as unix user "scodoc".
|
||||||
The original SCOUSERS database is left unmodified.
|
The original SCOUSERS database is left unmodified.
|
||||||
@ -206,18 +282,40 @@ def import_scodoc7_users(): # import-scodoc7-users
|
|||||||
@click.argument("dept")
|
@click.argument("dept")
|
||||||
@click.argument("dept_db_name")
|
@click.argument("dept_db_name")
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
def import_scodoc7_dept(dept: str, dept_db_name: str): # import-scodoc7-dept
|
def import_scodoc7_dept(dept: str, dept_db_name: str = ""): # import-scodoc7-dept
|
||||||
"""Import département ScoDoc 7: dept: InfoComm, dept_db_name: SCOINFOCOMM"""
|
"""Import département ScoDoc 7: dept: InfoComm, dept_db_name: SCOINFOCOMM"""
|
||||||
dept_db_uri = f"postgresql:///{dept_db_name}"
|
dept_db_uri = f"postgresql:///{dept_db_name}"
|
||||||
tools.import_scodoc7_dept(dept, dept_db_uri)
|
tools.import_scodoc7_dept(dept, dept_db_uri)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cli.command()
|
||||||
|
@click.argument("dept", default="")
|
||||||
|
@with_appcontext
|
||||||
|
def migrate_scodoc7_dept_archive(dept: str): # migrate-scodoc7-dept-archive
|
||||||
|
"""Post-migration: renomme les archives en fonction des id de ScoDoc 9"""
|
||||||
|
tools.migrate_scodoc7_dept_archive(dept)
|
||||||
|
|
||||||
|
|
||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
def clear_cache(): # clear-cache
|
def clear_cache(): # clear-cache
|
||||||
"""Clear ScoDoc cache
|
"""Clear ScoDoc cache
|
||||||
This cache (currently Redis) is persistent between invocation
|
This cache (currently Redis) is persistent between invocation
|
||||||
and it may be necessary to clear it during developement or tests.
|
and it may be necessary to clear it during development or tests.
|
||||||
"""
|
"""
|
||||||
clear_scodoc_cache()
|
clear_scodoc_cache()
|
||||||
click.echo("Redis caches flushed.")
|
click.echo("Redis caches flushed.")
|
||||||
|
|
||||||
|
|
||||||
|
def recursive_help(cmd, parent=None):
|
||||||
|
ctx = click.core.Context(cmd, info_name=cmd.name, parent=parent)
|
||||||
|
print(cmd.get_help(ctx))
|
||||||
|
print()
|
||||||
|
commands = getattr(cmd, "commands", {})
|
||||||
|
for sub in commands.values():
|
||||||
|
recursive_help(sub, ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cli.command()
|
||||||
|
def dumphelp():
|
||||||
|
recursive_help(app.cli)
|
||||||
|
@ -219,6 +219,7 @@ class ScoFake(object):
|
|||||||
etat=None,
|
etat=None,
|
||||||
gestion_compensation=None,
|
gestion_compensation=None,
|
||||||
bul_hide_xml=None,
|
bul_hide_xml=None,
|
||||||
|
block_moyennes=None,
|
||||||
gestion_semestrielle=None,
|
gestion_semestrielle=None,
|
||||||
bul_bgcolor=None,
|
bul_bgcolor=None,
|
||||||
modalite=NotesFormModalite.DEFAULT_MODALITE,
|
modalite=NotesFormModalite.DEFAULT_MODALITE,
|
||||||
|
@ -6,3 +6,4 @@
|
|||||||
|
|
||||||
from tools.import_scodoc7_user_db import import_scodoc7_user_db
|
from tools.import_scodoc7_user_db import import_scodoc7_user_db
|
||||||
from tools.import_scodoc7_dept import import_scodoc7_dept
|
from tools.import_scodoc7_dept import import_scodoc7_dept
|
||||||
|
from tools.migrate_scodoc7_archives import migrate_scodoc7_dept_archive
|
||||||
|
@ -1,29 +1,28 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# usage: backup_db2 dbname
|
# usage: backup_db9 dbname
|
||||||
# Dump une base postgresql, et garde plusieurs dumps dans le passe
|
# Dump une base postgresql, et garde plusieurs dumps dans le passe
|
||||||
# (configurable dans le script backup_rotation.sh)
|
# (configurable dans le script backup_rotation.sh)
|
||||||
# Les dumps sont compresses (gzip).
|
# Les dumps sont compresses (gzip).
|
||||||
#
|
#
|
||||||
# E. Viennet pour ScoDoc, 2014
|
# E. Viennet pour ScoDoc, 2014, 2021 pour ScoDoc 9
|
||||||
# (ce script est meilleur que l'ancien backup-db, car l'espace entre
|
# (ce script est meilleur que l'ancien backup-db, car l'espace entre
|
||||||
# deux sauvegardes dépend de leur anciennete)
|
# deux sauvegardes dépend de leur anciennete)
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# Note: pour restaurer un backup (en supprimant la base existante !):
|
# Note: pour restaurer un backup (en supprimant la base existante !):
|
||||||
#
|
#
|
||||||
# 0- Arreter scodoc: /etc/init.d/scodoc stop (ou systemctl stop scodoc)
|
# 0- Arreter scodoc: systemctl stop scodoc
|
||||||
#
|
#
|
||||||
# Puis en tant qu'utilisateur postgres: su postgres
|
# Puis en tant qu'utilisateur scodoc: su scodoc
|
||||||
# 1- supprimer la base existante si elle existe: dropdb SCOXXX
|
# 1- supprimer la base existante si elle existe: dropdb SCODOC
|
||||||
#
|
#
|
||||||
# 2- recreer la base, vide: createdb -E UTF-8 SCOXXX
|
# 2- recreer la base, vide: createdb -E UTF-8 SCOXXX
|
||||||
# (nom de la base: SCOXXX ou XXX=departement)
|
# /opt/scodoc/tools/create_database.sh SCODOC
|
||||||
#
|
# 3- pg_restore -d SCODOC SCODOC_pgdump
|
||||||
# 3- pg_restore -d SCOXXX SCOXXX_pgdump
|
|
||||||
#
|
#
|
||||||
# Revenir a l'utilisateur root: exit
|
# Revenir a l'utilisateur root: exit
|
||||||
# 4- Relancer scodoc: /etc/init.d/scodoc start (ou systemctl start scodoc)
|
# 4- Relancer scodoc: systemctl start scodoc
|
||||||
|
|
||||||
DBNAME=$1
|
DBNAME=$1
|
||||||
DUMPBASE="$DBNAME"-BACKUPS
|
DUMPBASE="$DBNAME"-BACKUPS
|
||||||
@ -50,4 +49,4 @@ pg_dump --format=t "$DBNAME" -f $DUMPFILE
|
|||||||
gzip $DUMPFILE
|
gzip $DUMPFILE
|
||||||
|
|
||||||
# 3- Rotate backups: remove unneeded copies
|
# 3- Rotate backups: remove unneeded copies
|
||||||
/opt/scodoc/Products/ScoDoc/misc/backup_rotation.sh "$DUMPBASE"
|
/opt/scodoc/tools/backups/backup_rotation.sh "$DUMPBASE"
|
@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Backup rotation
|
# Backup rotation
|
||||||
# Usage example: backup_rotation.sh /var/lib/postgresql/BACKUP-SCOGEII
|
# Usage example: backup_rotation.sh /var/lib/postgresql/BACKUP-SCODOC
|
||||||
#
|
#
|
||||||
# This script is designed to run each hour
|
# This script is designed to run each hour
|
||||||
#
|
#
|
@ -11,7 +11,7 @@
|
|||||||
#
|
#
|
||||||
# A adapter a vos besoins. Utilisation a vos risques et perils.
|
# A adapter a vos besoins. Utilisation a vos risques et perils.
|
||||||
#
|
#
|
||||||
# E. Viennet, 2002
|
# E. Viennet, 2002, 2021
|
||||||
|
|
||||||
# Installation:
|
# Installation:
|
||||||
# 1- Installer rsync:
|
# 1- Installer rsync:
|
||||||
@ -30,8 +30,8 @@ logfile=/var/log/rsynclog # log sur serveur scodoc
|
|||||||
# A qui envoyer un mail en cas d'erreur de la sauvegarde:
|
# A qui envoyer un mail en cas d'erreur de la sauvegarde:
|
||||||
SUPERVISORMAIL=emmanuel.viennet@example.com
|
SUPERVISORMAIL=emmanuel.viennet@example.com
|
||||||
|
|
||||||
CALLER=`basename $0`
|
CALLER=$(basename $0)
|
||||||
MACHINE=`hostname -s`
|
MACHINE=$(hostname -s)
|
||||||
|
|
||||||
# -----------------------------------------------------
|
# -----------------------------------------------------
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ MACHINE=`hostname -s`
|
|||||||
# ----------------------------------
|
# ----------------------------------
|
||||||
terminate()
|
terminate()
|
||||||
{
|
{
|
||||||
dateTest=`date`
|
dateTest=$(date)
|
||||||
|
|
||||||
mail -s "Attention: Probleme sauvegarde ScoDoc" $SUPERVISORMAIL <<EOF
|
mail -s "Attention: Probleme sauvegarde ScoDoc" $SUPERVISORMAIL <<EOF
|
||||||
The execution of script $CALLER was not successful on $MACHINE.
|
The execution of script $CALLER was not successful on $MACHINE.
|
||||||
@ -57,7 +57,7 @@ EOF
|
|||||||
echo "Look at logfile $logfile"
|
echo "Look at logfile $logfile"
|
||||||
echo
|
echo
|
||||||
echo "$CALLER terminated, exiting now with rc=1."
|
echo "$CALLER terminated, exiting now with rc=1."
|
||||||
dateTest=`date`
|
dateTest=$(date)
|
||||||
echo "End of script at: $dateTest"
|
echo "End of script at: $dateTest"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@ -74,16 +74,16 @@ EOF
|
|||||||
# --------------------------------------
|
# --------------------------------------
|
||||||
rsync_mirror_to_remote()
|
rsync_mirror_to_remote()
|
||||||
{
|
{
|
||||||
echo "--------------- mirroring " $MACHINE:$srcdir " to " $remotehost:$destdir >> $logfile 2>&1
|
echo "--------------- mirroring $MACHINE:$srcdir to $remotehost:$destdir" >> $logfile 2>&1
|
||||||
echo "starting at" `date` >> $logfile 2>&1
|
echo "starting at $(date)" >> $logfile 2>&1
|
||||||
rsync -vaze ssh --delete --rsync-path=/usr/bin/rsync $srcdir $remotehost":"$destdir >> $logfile 2>&1
|
rsync -vaze ssh --delete --rsync-path=/usr/bin/rsync "$srcdir" "$remotehost:$destdir" >> $logfile 2>&1
|
||||||
if [ $? -ne 0 ]
|
if [ $? -ne 0 ]
|
||||||
then
|
then
|
||||||
echo Error in rsync: code=$?
|
echo Error in rsync: code=$?
|
||||||
terminate
|
terminate
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "ending at" `date` >> $logfile 2>&1
|
echo "ending at $(date)" >> $logfile 2>&1
|
||||||
echo "---------------" >> $logfile 2>&1
|
echo "---------------" >> $logfile 2>&1
|
||||||
}
|
}
|
||||||
|
|
@ -15,9 +15,11 @@ source "$SCRIPT_DIR/utils.sh"
|
|||||||
SCODOC_RELEASE=$(grep SCOVERSION "$SCRIPT_DIR/../sco_version.py" | awk '{ print substr($3, 2, length($3)-2) }')
|
SCODOC_RELEASE=$(grep SCOVERSION "$SCRIPT_DIR/../sco_version.py" | awk '{ print substr($3, 2, length($3)-2) }')
|
||||||
|
|
||||||
# Dernière release
|
# Dernière release
|
||||||
GITEA_RELEASE_URL="https://scodoc.org/git/api/v1/repos/viennet/ScoDoc/releases?pre-release=true"
|
GITEA_RELEASE_URL="https://scodoc.org/git/api/v1/repos/viennet/ScoDoc/releases" # ?pre-release=true"
|
||||||
|
|
||||||
LAST_RELEASE_TAG=$(curl "$GITEA_RELEASE_URL" | jq ".[].tag_name" | sort | tail -1 | awk '{ print substr($1, 2, length($1)-2) }')
|
# suppose que les realse sont nommées 9.0.17, ne considère pas les caractères non numériques
|
||||||
|
LAST_RELEASE_TAG=$(curl "$GITEA_RELEASE_URL" | jq ".[].tag_name" | tr -d -c "0-9.\n" | sort --version-sort | tail -1)
|
||||||
|
# | awk '{ print substr($1, 2, length($1)-2) }')
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Version détectée dans le source: $SCODOC_RELEASE"
|
echo "Version détectée dans le source: $SCODOC_RELEASE"
|
||||||
@ -89,7 +91,8 @@ fi
|
|||||||
# Puis on déplace les fichiers de config (nginx, systemd, ...)
|
# Puis on déplace les fichiers de config (nginx, systemd, ...)
|
||||||
# nginx:
|
# nginx:
|
||||||
mkdir -p "$slash"/etc/nginx/sites-available || die "can't mkdir nginx config"
|
mkdir -p "$slash"/etc/nginx/sites-available || die "can't mkdir nginx config"
|
||||||
cp -p "$SCODOC_DIR"/tools/etc/scodoc9.nginx "$slash"/etc/nginx/sites-available/ || die "can't copy nginx config"
|
cp -p "$SCODOC_DIR"/tools/etc/scodoc9.nginx "$slash"/etc/nginx/sites-available/scodoc9.nginx.distrib || die "can't copy nginx config"
|
||||||
|
|
||||||
# systemd
|
# systemd
|
||||||
mkdir -p "$slash"/etc/systemd/system/ || die "can't mkdir systemd config"
|
mkdir -p "$slash"/etc/systemd/system/ || die "can't mkdir systemd config"
|
||||||
cp -p "$SCODOC_DIR"/tools/etc/scodoc9.service "$slash"/etc/systemd/system/ || die "can't copy scodoc9.service"
|
cp -p "$SCODOC_DIR"/tools/etc/scodoc9.service "$slash"/etc/systemd/system/ || die "can't copy scodoc9.service"
|
||||||
|
@ -80,6 +80,11 @@ su -c "(cd $SCODOC_DIR && python3 -m venv venv)" "$SCODOC_USER" || die "Error cr
|
|||||||
su -c "(cd $SCODOC_DIR && source venv/bin/activate && pip install wheel && pip install -r requirements-3.9.txt)" "$SCODOC_USER" || die "Error installing python packages"
|
su -c "(cd $SCODOC_DIR && source venv/bin/activate && pip install wheel && pip install -r requirements-3.9.txt)" "$SCODOC_USER" || die "Error installing python packages"
|
||||||
|
|
||||||
# --- NGINX
|
# --- NGINX
|
||||||
|
# Evite d'écraser: il faudrait ici présenter un dialogue "fichier local modifié, ..."
|
||||||
|
if [ ! -e /etc/nginx/sites-available/scodoc9.nginx ]
|
||||||
|
then
|
||||||
|
cp -p /etc/nginx/sites-available/scodoc9.nginx.distrib /etc/nginx/sites-available/scodoc9.nginx || die "can't copy nginx config"
|
||||||
|
fi
|
||||||
if [ ! -L /etc/nginx/sites-enabled/scodoc9.nginx ]
|
if [ ! -L /etc/nginx/sites-enabled/scodoc9.nginx ]
|
||||||
then
|
then
|
||||||
echo "Enabling scodoc9 in nginx"
|
echo "Enabling scodoc9 in nginx"
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
/opt/scodoc-datalog/scodoc.log {
|
/opt/scodoc-data/log/scodoc.log {
|
||||||
|
weekly
|
||||||
|
missingok
|
||||||
|
rotate 64
|
||||||
|
compress
|
||||||
|
notifempty
|
||||||
|
dateext
|
||||||
|
create 0644 scodoc scodoc
|
||||||
|
}
|
||||||
|
/opt/scodoc-datalog/scodoc_exc.log {
|
||||||
weekly
|
weekly
|
||||||
missingok
|
missingok
|
||||||
rotate 64
|
rotate 64
|
||||||
|
@ -27,6 +27,7 @@ server {
|
|||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
}
|
}
|
||||||
location /ScoDoc/static {
|
location /ScoDoc/static {
|
||||||
# handle static files directly, without forwarding to the application
|
# handle static files directly, without forwarding to the application
|
||||||
@ -41,4 +42,7 @@ server {
|
|||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
}
|
}
|
||||||
|
location /favicon.ico {
|
||||||
|
alias /opt/scodoc/app/static/icons/favicon.ico;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -380,6 +380,9 @@ def convert_object(
|
|||||||
"absences",
|
"absences",
|
||||||
"absences_notifications",
|
"absences_notifications",
|
||||||
"itemsuivi", # etudid n'était pas une clé
|
"itemsuivi", # etudid n'était pas une clé
|
||||||
|
"adresse", # etudid n'était pas une clé
|
||||||
|
"admissions", # idem
|
||||||
|
"scolar_events",
|
||||||
}:
|
}:
|
||||||
# tables avec "fausses" clés
|
# tables avec "fausses" clés
|
||||||
# (l'object référencé a pu disparaitre)
|
# (l'object référencé a pu disparaitre)
|
||||||
|
@ -204,6 +204,9 @@ do
|
|||||||
systemctl restart postgresql
|
systemctl restart postgresql
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# ----- Post-Migration: renomme archives en fonction des nouveaux ids
|
||||||
|
su -c "(cd $SCODOC_DIR && source venv/bin/activate && flask migrate-scodoc7-dept-archive)" "$SCODOC_USER" || die "Erreur de la post-migration des archives"
|
||||||
|
|
||||||
|
|
||||||
# --- Si migration "en place", désactive ScoDoc 7
|
# --- Si migration "en place", désactive ScoDoc 7
|
||||||
if [ "$INPLACE" == 1 ]
|
if [ "$INPLACE" == 1 ]
|
||||||
|
72
tools/migrate_scodoc7_archives.py
Normal file
72
tools/migrate_scodoc7_archives.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from app.models import Departement
|
||||||
|
from app.models.formsemestre import FormSemestre
|
||||||
|
from app.models.etudiants import Identite
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_scodoc7_dept_archive(dept_name=""):
|
||||||
|
if dept_name:
|
||||||
|
depts = Departement.query.filter_by(acronym=dept_name)
|
||||||
|
else:
|
||||||
|
depts = Departement.query
|
||||||
|
for dept in depts:
|
||||||
|
print(f"Migrating {dept.acronym} archives...")
|
||||||
|
# SemsArchiver
|
||||||
|
# /opt/scodoc-data/archives/<dept>/<scodoc7id> -> formsemestre_id
|
||||||
|
migrate_sem_archives(dept)
|
||||||
|
|
||||||
|
# EtudsArchiver:
|
||||||
|
migrate_docetuds(dept)
|
||||||
|
|
||||||
|
# ApoCSVArchiver:
|
||||||
|
# /opt/scodoc-data/archives/apo_csv/<dept>/ ne bouge pas
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_sem_archives(dept):
|
||||||
|
"/opt/scodoc-data/archives/<dept>/<scodoc7id> -> formsemestre_id"
|
||||||
|
n = 0
|
||||||
|
n_moves = 0
|
||||||
|
for sem in FormSemestre.query.filter_by(dept_id=dept.id):
|
||||||
|
n += 1
|
||||||
|
arch_dir7 = f"/opt/scodoc-data/archives/{dept.acronym}/{sem.scodoc7_id}"
|
||||||
|
arch_dir9 = f"/opt/scodoc-data/archives/{dept.acronym}/{sem.id}"
|
||||||
|
if os.path.exists(arch_dir7):
|
||||||
|
n_moves += 1
|
||||||
|
if not os.path.exists(arch_dir9):
|
||||||
|
# print(f"renaming {arch_dir7} to {arch_dir9}")
|
||||||
|
shutil.move(arch_dir7, arch_dir9)
|
||||||
|
else:
|
||||||
|
# print(f"merging {arch_dir7} with {arch_dir9}")
|
||||||
|
for arch in glob.glob(f"{arch_dir7}/*"):
|
||||||
|
# print(f"\tmoving {arch}")
|
||||||
|
shutil.move(arch, arch_dir9)
|
||||||
|
# print(f"moved {n_moves}/{n} sems")
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_docetuds(dept):
|
||||||
|
"/opt/scodoc-data/archives/docetuds/<dept>/<scodoc7_id>/ -> etudid"
|
||||||
|
n = 0
|
||||||
|
n_moves = 0
|
||||||
|
for etud in Identite.query.filter_by(dept_id=dept.id):
|
||||||
|
n += 1
|
||||||
|
arch_dir7 = (
|
||||||
|
f"/opt/scodoc-data/archives/docetuds/{dept.acronym}/{etud.scodoc7_id}"
|
||||||
|
)
|
||||||
|
arch_dir9 = f"/opt/scodoc-data/archives/docetuds/{dept.acronym}/{etud.id}"
|
||||||
|
if os.path.exists(arch_dir7):
|
||||||
|
n_moves += 1
|
||||||
|
if not os.path.exists(arch_dir9):
|
||||||
|
# print(f"renaming {arch_dir7} to {arch_dir9}")
|
||||||
|
shutil.move(arch_dir7, arch_dir9)
|
||||||
|
else:
|
||||||
|
# print(f"merging {arch_dir7} with {arch_dir9}")
|
||||||
|
for arch in glob.glob(f"{arch_dir7}/*"):
|
||||||
|
# print(f"\tmoving {arch}")
|
||||||
|
shutil.move(arch, arch_dir9)
|
||||||
|
# print(f"moved {n_moves}/{n} etuds")
|
@ -44,7 +44,7 @@ fi
|
|||||||
# Safety check
|
# Safety check
|
||||||
echo "Ce script recharge les donnees de votre installation ScoDoc 7"
|
echo "Ce script recharge les donnees de votre installation ScoDoc 7"
|
||||||
echo "sur ce serveur pour migration vers ScoDoc 9."
|
echo "sur ce serveur pour migration vers ScoDoc 9."
|
||||||
echo "Ce fichier doit avoir ete cree par le script save_scodoc_data.sh, sur une machine ScoDoc 7."
|
echo "Ce fichier doit avoir ete cree par le script save_scodoc7_data.sh, sur une machine ScoDoc 7."
|
||||||
echo
|
echo
|
||||||
echo -n "Voulez-vous poursuivre cette operation ? (y/n) [n]"
|
echo -n "Voulez-vous poursuivre cette operation ? (y/n) [n]"
|
||||||
read -r ans
|
read -r ans
|
||||||
|
Loading…
Reference in New Issue
Block a user