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-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
|
||||
|
||||
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
|
||||
`tools/build_release.sh`.
|
||||
|
101
app/__init__.py
101
app/__init__.py
@ -2,6 +2,7 @@
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
@ -12,19 +13,19 @@ from logging.handlers import SMTPHandler, WatchedFileHandler
|
||||
|
||||
from flask import current_app, g, request
|
||||
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.logging import default_handler
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_migrate import Migrate
|
||||
from flask_login import LoginManager
|
||||
from flask_login import LoginManager, current_user
|
||||
from flask_mail import Mail
|
||||
from flask_bootstrap import Bootstrap
|
||||
from flask_moment import Moment
|
||||
from flask_caching import Cache
|
||||
import sqlalchemy
|
||||
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_exceptions import ScoValueError, APIInvalidParams
|
||||
from config import DevConfig
|
||||
import sco_version
|
||||
|
||||
@ -32,7 +33,7 @@ db = SQLAlchemy()
|
||||
migrate = Migrate(compare_type=True)
|
||||
login = LoginManager()
|
||||
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()
|
||||
bootstrap = Bootstrap()
|
||||
moment = Moment()
|
||||
@ -56,6 +57,12 @@ def internal_server_error(e):
|
||||
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:
|
||||
"""Load and render an HTML file _without_ using Flask
|
||||
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
|
||||
|
||||
|
||||
class RequestFormatter(logging.Formatter):
|
||||
class LogRequestFormatter(logging.Formatter):
|
||||
"""Ajoute URL et remote_addr for logging"""
|
||||
|
||||
def format(self, record):
|
||||
@ -86,12 +93,64 @@ class RequestFormatter(logging.Formatter):
|
||||
else:
|
||||
record.url = None
|
||||
record.remote_addr = None
|
||||
record.sco_user = current_user
|
||||
|
||||
return super().format(record)
|
||||
|
||||
|
||||
class LogExceptionFormatter(logging.Formatter):
|
||||
"""Formatteur pour les exceptions: ajoute détails"""
|
||||
|
||||
def format(self, record):
|
||||
if has_request_context():
|
||||
record.url = request.url
|
||||
record.remote_addr = request.environ.get(
|
||||
"HTTP_X_FORWARDED_FOR", request.remote_addr
|
||||
)
|
||||
record.http_referrer = request.referrer
|
||||
record.http_method = request.method
|
||||
if request.method == "GET":
|
||||
record.http_params = str(request.args)
|
||||
else:
|
||||
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):
|
||||
app = Flask(__name__, static_url_path="/ScoDoc/static", static_folder="static")
|
||||
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||
app.logger.setLevel(logging.DEBUG)
|
||||
app.config.from_object(config_class)
|
||||
|
||||
@ -107,6 +166,7 @@ def create_app(config_class=DevConfig):
|
||||
app.register_error_handler(ScoValueError, handle_sco_value_error)
|
||||
app.register_error_handler(500, internal_server_error)
|
||||
app.register_error_handler(503, postgresql_server_error)
|
||||
app.register_error_handler(APIInvalidParams, handle_invalid_usage)
|
||||
|
||||
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"
|
||||
)
|
||||
app.register_blueprint(api_bp, url_prefix="/ScoDoc/api")
|
||||
scodoc_exc_formatter = RequestFormatter(
|
||||
"[%(asctime)s] %(remote_addr)s requested %(url)s\n"
|
||||
"%(levelname)s in %(module)s: %(message)s"
|
||||
scodoc_log_formatter = LogRequestFormatter(
|
||||
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
|
||||
"%(levelname)s: %(message)s"
|
||||
)
|
||||
scodoc_exc_formatter = LogExceptionFormatter(
|
||||
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
|
||||
"%(levelname)s: %(message)s\n"
|
||||
"Referrer: %(http_referrer)s\n"
|
||||
"Method: %(http_method)s\n"
|
||||
"Params: %(http_params)s\n"
|
||||
)
|
||||
if not app.testing:
|
||||
if not app.debug:
|
||||
@ -150,11 +217,11 @@ def create_app(config_class=DevConfig):
|
||||
if app.config["MAIL_USE_TLS"]:
|
||||
secure = ()
|
||||
host_name = socket.gethostname()
|
||||
mail_handler = SMTPHandler(
|
||||
mail_handler = ScoSMTPHandler(
|
||||
mailhost=(app.config["MAIL_SERVER"], app.config["MAIL_PORT"]),
|
||||
fromaddr="no-reply@" + app.config["MAIL_SERVER"],
|
||||
toaddrs=["exception@scodoc.org"],
|
||||
subject="ScoDoc Exception from " + host_name,
|
||||
subject="ScoDoc Exception", # unused see ScoSMTPHandler
|
||||
credentials=auth,
|
||||
secure=secure,
|
||||
)
|
||||
@ -163,7 +230,7 @@ def create_app(config_class=DevConfig):
|
||||
app.logger.addHandler(mail_handler)
|
||||
else:
|
||||
# Pour logs en DEV uniquement:
|
||||
default_handler.setFormatter(scodoc_exc_formatter)
|
||||
default_handler.setFormatter(scodoc_log_formatter)
|
||||
|
||||
# Config logs pour DEV et PRODUCTION
|
||||
# Configuration des logs (actifs aussi en mode development)
|
||||
@ -172,9 +239,17 @@ def create_app(config_class=DevConfig):
|
||||
file_handler = WatchedFileHandler(
|
||||
app.config["SCODOC_LOG_FILE"], encoding="utf-8"
|
||||
)
|
||||
file_handler.setFormatter(scodoc_exc_formatter)
|
||||
file_handler.setFormatter(scodoc_log_formatter)
|
||||
file_handler.setLevel(logging.INFO)
|
||||
app.logger.addHandler(file_handler)
|
||||
# Log pour les erreurs (exceptions) uniquement:
|
||||
# usually /opt/scodoc-data/log/scodoc_exc.log
|
||||
file_handler = WatchedFileHandler(
|
||||
app.config["SCODOC_ERR_FILE"], encoding="utf-8"
|
||||
)
|
||||
file_handler.setFormatter(scodoc_exc_formatter)
|
||||
file_handler.setLevel(logging.ERROR)
|
||||
app.logger.addHandler(file_handler)
|
||||
|
||||
# app.logger.setLevel(logging.INFO)
|
||||
app.logger.info(f"{sco_version.SCONAME} {sco_version.SCOVERSION} startup")
|
||||
@ -353,4 +428,4 @@ from app.scodoc import sco_cache
|
||||
# click.echo(
|
||||
# "Warning: user database not initialized !\n (use: flask user-db-init)"
|
||||
# )
|
||||
# admin = None
|
||||
# admin = None
|
||||
|
@ -16,20 +16,20 @@ _l = _
|
||||
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
user_name = StringField(_l("Username"), validators=[DataRequired()])
|
||||
password = PasswordField(_l("Password"), validators=[DataRequired()])
|
||||
remember_me = BooleanField(_l("Remember Me"))
|
||||
submit = SubmitField(_l("Sign In"))
|
||||
user_name = StringField(_l("Nom d'utilisateur"), validators=[DataRequired()])
|
||||
password = PasswordField(_l("Mot de passe"), validators=[DataRequired()])
|
||||
remember_me = BooleanField(_l("mémoriser la connexion"))
|
||||
submit = SubmitField(_l("Suivant"))
|
||||
|
||||
|
||||
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()])
|
||||
password = PasswordField(_l("Password"), validators=[DataRequired()])
|
||||
password = PasswordField(_l("Mot de passe"), validators=[DataRequired()])
|
||||
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):
|
||||
user = User.query.filter_by(user_name=user_name.data).first()
|
||||
@ -48,9 +48,9 @@ class ResetPasswordRequestForm(FlaskForm):
|
||||
|
||||
|
||||
class ResetPasswordForm(FlaskForm):
|
||||
password = PasswordField(_l("Password"), validators=[DataRequired()])
|
||||
password = PasswordField(_l("Mot de passe"), validators=[DataRequired()])
|
||||
password2 = PasswordField(
|
||||
_l("Repeat Password"), validators=[DataRequired(), EqualTo("password")]
|
||||
_l("Répéter"), validators=[DataRequired(), EqualTo("password")]
|
||||
)
|
||||
submit = SubmitField(_l("Request Password Reset"))
|
||||
|
||||
|
@ -38,7 +38,7 @@ def login():
|
||||
user = User.query.filter_by(user_name=form.user_name.data).first()
|
||||
if user is None or not user.check_password(form.password.data):
|
||||
current_app.logger.info("login: invalid (%s)", form.user_name.data)
|
||||
flash(_("Invalid user name or password"))
|
||||
flash(_("Nom ou mot de passe invalide"))
|
||||
return redirect(url_for("auth.login"))
|
||||
login_user(user, remember=form.remember_me.data)
|
||||
current_app.logger.info("login: success (%s)", form.user_name.data)
|
||||
@ -95,7 +95,7 @@ def reset_password_request():
|
||||
current_app.logger.info(
|
||||
"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 render_template(
|
||||
"auth/reset_password_request.html", title=_("Reset Password"), form=form
|
||||
|
@ -43,12 +43,14 @@ class ZRequest(object):
|
||||
"Emulating Zope 2 REQUEST"
|
||||
|
||||
def __init__(self):
|
||||
if current_app.config["DEBUG"]:
|
||||
self.URL = request.base_url
|
||||
self.BASE0 = request.url_root
|
||||
else:
|
||||
self.URL = request.base_url.replace("http://", "https://")
|
||||
self.BASE0 = request.url_root.replace("http://", "https://")
|
||||
# if current_app.config["DEBUG"]:
|
||||
|
||||
# le ReverseProxied se charge maintenant de mettre le bon protocole http ou https
|
||||
self.URL = request.base_url
|
||||
self.BASE0 = request.url_root
|
||||
# else:
|
||||
# self.URL = request.base_url.replace("http://", "https://")
|
||||
# self.BASE0 = request.url_root.replace("http://", "https://")
|
||||
self.URL0 = self.URL
|
||||
# query_string is bytes:
|
||||
self.QUERY_STRING = request.query_string.decode("utf-8")
|
||||
|
@ -41,6 +41,7 @@ class Identite(db.Model):
|
||||
code_nip = db.Column(db.Text())
|
||||
code_ine = db.Column(db.Text())
|
||||
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
||||
# ne pas utiliser après migrate_scodoc7_dept_archive
|
||||
scodoc7_id = db.Column(db.Text(), nullable=True)
|
||||
|
||||
|
||||
|
@ -19,7 +19,7 @@ class FormSemestre(db.Model):
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
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
|
||||
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
|
||||
formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
|
||||
@ -41,6 +41,10 @@ class FormSemestre(db.Model):
|
||||
bul_hide_xml = db.Column(
|
||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||
)
|
||||
# Bloque le calcul des moyennes (générale et d'UE)
|
||||
block_moyennes = db.Column(
|
||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||
)
|
||||
# semestres decales (pour gestion jurys):
|
||||
gestion_semestrielle = db.Column(
|
||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||
@ -70,6 +74,7 @@ class FormSemestre(db.Model):
|
||||
"NotesFormsemestreEtape", cascade="all,delete", backref="notes_formsemestre"
|
||||
)
|
||||
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
||||
# ne pas utiliser après migrate_scodoc7_dept_archive
|
||||
scodoc7_id = db.Column(db.Text(), nullable=True)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
v 1.3 (python3)
|
||||
"""
|
||||
import html
|
||||
|
||||
|
||||
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]):
|
||||
R.append('<span class="tf-ro-value">%s</span>' % labels[i])
|
||||
elif input_type == "textarea":
|
||||
R.append('<div class="tf-ro-textarea">%s</div>' % self.values[field])
|
||||
R.append(
|
||||
'<div class="tf-ro-textarea">%s</div>' % html.escape(self.values[field])
|
||||
)
|
||||
elif input_type == "separator" or input_type == "hidden":
|
||||
pass
|
||||
elif input_type == "file":
|
||||
|
@ -87,10 +87,6 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
|
||||
)
|
||||
|
||||
|
||||
_TOP_LEVEL_CSS = """
|
||||
<style type="text/css">
|
||||
</style>"""
|
||||
|
||||
_HTML_BEGIN = """<?xml version="1.0" encoding="%(encoding)s"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
@ -105,31 +101,30 @@ _HTML_BEGIN = """<?xml version="1.0" encoding="%(encoding)s"?>
|
||||
|
||||
<link href="/ScoDoc/static/css/scodoc.css" rel="stylesheet" type="text/css" />
|
||||
<link href="/ScoDoc/static/css/menu.css" rel="stylesheet" type="text/css" />
|
||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/menu.js"></script>
|
||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/sorttable.js"></script>
|
||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/bubble.js"></script>
|
||||
<script type="text/javascript">
|
||||
<script src="/ScoDoc/static/libjs/menu.js"></script>
|
||||
<script src="/ScoDoc/static/libjs/sorttable.js"></script>
|
||||
<script src="/ScoDoc/static/libjs/bubble.js"></script>
|
||||
<script>
|
||||
window.onload=function(){enableTooltips("gtrcontent")};
|
||||
</script>
|
||||
|
||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery.js"></script>
|
||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery.field.min.js"></script>
|
||||
<script src="/ScoDoc/static/jQuery/jquery.js"></script>
|
||||
<script src="/ScoDoc/static/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
||||
<script src="/ScoDoc/static/libjs/jquery.field.min.js"></script>
|
||||
|
||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||
<script src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||
|
||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
<script src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.css" />
|
||||
|
||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/scodoc.js"></script>
|
||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/etud_info.js"></script>
|
||||
<script src="/ScoDoc/static/js/scodoc.js"></script>
|
||||
<script src="/ScoDoc/static/js/etud_info.js"></script>
|
||||
"""
|
||||
|
||||
|
||||
def scodoc_top_html_header(page_title="ScoDoc: bienvenue"):
|
||||
H = [
|
||||
_HTML_BEGIN % {"page_title": page_title, "encoding": scu.SCO_ENCODING},
|
||||
_TOP_LEVEL_CSS,
|
||||
"""</head><body class="gtrcontent" id="gtrcontent">""",
|
||||
scu.CUSTOM_HTML_HEADER_CNX,
|
||||
]
|
||||
@ -185,13 +180,10 @@ def sco_header(
|
||||
init_jquery = True
|
||||
|
||||
H = [
|
||||
"""<?xml version="1.0" encoding="%(encoding)s"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
"""<!DOCTYPE html><html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>%(page_title)s</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=%(encoding)s" />
|
||||
<meta http-equiv="Content-Style-Type" content="text/css" />
|
||||
<meta name="LANG" content="fr" />
|
||||
<meta name="DESCRIPTION" content="ScoDoc" />
|
||||
|
||||
@ -206,9 +198,7 @@ def sco_header(
|
||||
)
|
||||
if init_google_maps:
|
||||
# It may be necessary to add an API key:
|
||||
H.append(
|
||||
'<script type="text/javascript" src="https://maps.google.com/maps/api/js"></script>'
|
||||
)
|
||||
H.append('<script src="https://maps.google.com/maps/api/js"></script>')
|
||||
|
||||
# Feuilles de style additionnelles:
|
||||
for cssstyle in cssstyles:
|
||||
@ -223,9 +213,9 @@ def sco_header(
|
||||
<link href="/ScoDoc/static/css/menu.css" rel="stylesheet" type="text/css" />
|
||||
<link href="/ScoDoc/static/css/gt_table.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/menu.js"></script>
|
||||
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/bubble.js"></script>
|
||||
<script type="text/javascript">
|
||||
<script src="/ScoDoc/static/libjs/menu.js"></script>
|
||||
<script src="/ScoDoc/static/libjs/bubble.js"></script>
|
||||
<script>
|
||||
window.onload=function(){enableTooltips("gtrcontent")};
|
||||
|
||||
var SCO_URL="%(ScoURL)s";
|
||||
@ -236,16 +226,14 @@ def sco_header(
|
||||
# jQuery
|
||||
if init_jquery:
|
||||
H.append(
|
||||
"""<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery.js"></script>
|
||||
"""<script src="/ScoDoc/static/jQuery/jquery.js"></script>
|
||||
"""
|
||||
)
|
||||
H.append(
|
||||
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery.field.min.js"></script>'
|
||||
)
|
||||
H.append('<script src="/ScoDoc/static/libjs/jquery.field.min.js"></script>')
|
||||
# qTip
|
||||
if init_qtip:
|
||||
H.append(
|
||||
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>'
|
||||
'<script src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>'
|
||||
)
|
||||
H.append(
|
||||
'<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.css" />'
|
||||
@ -253,32 +241,25 @@ def sco_header(
|
||||
|
||||
if init_jquery_ui:
|
||||
H.append(
|
||||
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>'
|
||||
)
|
||||
# H.append('<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery-ui/js/jquery-ui-i18n.js"></script>')
|
||||
H.append(
|
||||
'<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/scodoc.js"></script>'
|
||||
'<script src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>'
|
||||
)
|
||||
# H.append('<script src="/ScoDoc/static/libjs/jquery-ui/js/jquery-ui-i18n.js"></script>')
|
||||
H.append('<script src="/ScoDoc/static/js/scodoc.js"></script>')
|
||||
if init_google_maps:
|
||||
H.append(
|
||||
'<script type="text/javascript" src="/ScoDoc/static/libjs/jquery.ui.map.full.min.js"></script>'
|
||||
'<script src="/ScoDoc/static/libjs/jquery.ui.map.full.min.js"></script>'
|
||||
)
|
||||
if init_datatables:
|
||||
H.append(
|
||||
'<link rel="stylesheet" type="text/css" href="/ScoDoc/static/DataTables/datatables.min.css"/>'
|
||||
)
|
||||
H.append(
|
||||
'<script type="text/javascript" src="/ScoDoc/static/DataTables/datatables.min.js"></script>'
|
||||
)
|
||||
H.append('<script src="/ScoDoc/static/DataTables/datatables.min.js"></script>')
|
||||
# JS additionels
|
||||
for js in javascripts:
|
||||
H.append(
|
||||
"""<script language="javascript" type="text/javascript" src="/ScoDoc/static/%s"></script>\n"""
|
||||
% js
|
||||
)
|
||||
H.append("""<script src="/ScoDoc/static/%s"></script>\n""" % js)
|
||||
|
||||
H.append(
|
||||
"""<style type="text/css">
|
||||
"""<style>
|
||||
.gtrcontent {
|
||||
margin-left: %(margin_left)s;
|
||||
height: 100%%;
|
||||
@ -290,7 +271,7 @@ def sco_header(
|
||||
)
|
||||
# Scripts de la page:
|
||||
if scripts:
|
||||
H.append("""<script language="javascript" type="text/javascript">""")
|
||||
H.append("""<script>""")
|
||||
for script in scripts:
|
||||
H.append(script)
|
||||
H.append("""</script>""")
|
||||
|
@ -28,9 +28,8 @@
|
||||
"""
|
||||
Génération de la "sidebar" (marge gauche des pages HTML)
|
||||
"""
|
||||
from flask import url_for
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask import render_template, url_for
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
@ -152,11 +151,12 @@ def sidebar():
|
||||
# Logo
|
||||
H.append(
|
||||
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>
|
||||
</div></div>
|
||||
<div class="logo-logo"><a href= { url_for( 'scolar.about', scodoc_dept=g.scodoc_dept ) }
|
||||
">{ scu.icontag("scologo_img", no_size=True) }</a>
|
||||
<div class="logo-logo">
|
||||
<a href="{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }">
|
||||
{ scu.icontag("scologo_img", no_size=True) }</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of sidebar -->
|
||||
@ -167,19 +167,7 @@ def sidebar():
|
||||
|
||||
def sidebar_dept():
|
||||
"""Partie supérieure de la marge de gauche"""
|
||||
H = [
|
||||
f"""<h2 class="insidebar">Dépt. {sco_preferences.get_preference("DeptName")}</h2>
|
||||
<a href="{url_for("scodoc.index")}" class="sidebar">Accueil</a> <br/> """
|
||||
]
|
||||
dept_intranet_url = sco_preferences.get_preference("DeptIntranetURL")
|
||||
if dept_intranet_url:
|
||||
H.append(
|
||||
f"""<a href="{dept_intranet_url}" class="sidebar">{
|
||||
sco_preferences.get_preference("DeptIntranetTitle")}</a> <br/>"""
|
||||
)
|
||||
# Entreprises pas encore supporté en ScoDoc8
|
||||
# H.append(
|
||||
# """<br/><a href="%(ScoURL)s/Entreprises" class="sidebar">Entreprises</a> <br/>"""
|
||||
# % infos
|
||||
# )
|
||||
return "\n".join(H)
|
||||
return render_template(
|
||||
"sidebar_dept.html",
|
||||
prefs=sco_preferences.SemPreferences(),
|
||||
)
|
||||
|
@ -186,6 +186,8 @@ class NotesTable(object):
|
||||
self.use_ue_coefs = sco_preferences.get_preference(
|
||||
"use_ue_coefs", formsemestre_id
|
||||
)
|
||||
# si vrai, bloque calcul des moy gen. et d'UE.:
|
||||
self.block_moyennes = self.sem["block_moyennes"]
|
||||
# Infos sur les etudiants
|
||||
self.inscrlist = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||
args={"formsemestre_id": formsemestre_id}
|
||||
@ -738,6 +740,7 @@ class NotesTable(object):
|
||||
block_computation = (
|
||||
self.inscrdict[etudid]["etat"] == "D"
|
||||
or self.inscrdict[etudid]["etat"] == DEF
|
||||
or self.block_moyennes
|
||||
)
|
||||
|
||||
moy_ues = {}
|
||||
|
@ -324,15 +324,14 @@ def list_abs_in_range(etudid, debut, fin, matin=None, moduleimpl_id=None, cursor
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT DISTINCT A.JOUR, A.MATIN
|
||||
FROM ABSENCES A
|
||||
WHERE A.ETUDID = %(etudid)s
|
||||
AND A.ESTABS"""
|
||||
"""SELECT DISTINCT A.JOUR, A.MATIN
|
||||
FROM ABSENCES A
|
||||
WHERE A.ETUDID = %(etudid)s
|
||||
AND A.ESTABS"""
|
||||
+ ismatin
|
||||
+ modul
|
||||
+ """
|
||||
AND A.JOUR BETWEEN %(debut)s AND %(fin)s
|
||||
AND A.JOUR BETWEEN %(debut)s AND %(fin)s
|
||||
""",
|
||||
{
|
||||
"etudid": etudid,
|
||||
@ -639,7 +638,10 @@ def add_absence(
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
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(),
|
||||
)
|
||||
logdb(
|
||||
|
@ -218,7 +218,10 @@ def user_nbdays_since_last_notif(email_addr, etudid):
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
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},
|
||||
)
|
||||
res = cursor.dictfetchone()
|
||||
|
@ -628,14 +628,18 @@ def AnnuleAbsencesDatesNoJust(etudid, dates, moduleimpl_id=None):
|
||||
# supr les absences non justifiees
|
||||
for date in dates:
|
||||
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(),
|
||||
)
|
||||
sco_abs.invalidate_abs_etud_date(etudid, date)
|
||||
# s'assure que les justificatifs ne sont pas "absents"
|
||||
for date in dates:
|
||||
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(),
|
||||
)
|
||||
if dates:
|
||||
@ -840,9 +844,9 @@ def ListeAbsEtud(
|
||||
# Formats non HTML et demande d'une seule table:
|
||||
if format != "html" and format != "text":
|
||||
if absjust_only == 1:
|
||||
return tab_absjust.make_page(format=format, REQUEST=REQUEST)
|
||||
return tab_absjust.make_page(format=format)
|
||||
else:
|
||||
return tab_absnonjust.make_page(format=format, REQUEST=REQUEST)
|
||||
return tab_absnonjust.make_page(format=format)
|
||||
|
||||
if format == "html":
|
||||
# Mise en forme HTML:
|
||||
|
@ -34,11 +34,11 @@
|
||||
|
||||
Les PV de jurys et documents associés sont stockées dans un sous-repertoire de la forme
|
||||
<archivedir>/<dept>/<formsemestre_id>/<YYYY-MM-DD-HH-MM-SS>
|
||||
(formsemestre_id est ici FormSemestre.scodoc7_id ou à défaut FormSemestre.id)
|
||||
(formsemestre_id est ici FormSemestre.id)
|
||||
|
||||
Les documents liés à l'étudiant sont dans
|
||||
<archivedir>/docetuds/<dept>/<etudid>/<YYYY-MM-DD-HH-MM-SS>
|
||||
(etudid est ici soit Identite.scodoc7id, soit à défaut Identite.id)
|
||||
(etudid est ici Identite.id)
|
||||
|
||||
Les maquettes Apogée pour l'export des notes sont dans
|
||||
<archivedir>/apo_csv/<dept>/<annee_scolaire>-<sem_id>/<YYYY-MM-DD-HH-MM-SS>/<code_etape>.csv
|
||||
@ -47,12 +47,13 @@
|
||||
qui est une description (humaine, format libre) de l'archive.
|
||||
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
import datetime
|
||||
import glob
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import glob
|
||||
import time
|
||||
|
||||
import flask
|
||||
from flask import g
|
||||
@ -244,31 +245,15 @@ class BaseArchiver(object):
|
||||
log("reading archive file %s" % fname)
|
||||
return open(fname, "rb").read()
|
||||
|
||||
def get_archived_file(self, REQUEST, oid, archive_name, filename):
|
||||
def get_archived_file(self, oid, archive_name, filename):
|
||||
"""Recupere donnees du fichier indiqué et envoie au client"""
|
||||
# XXX très incomplet: devrait inférer et assigner un type MIME
|
||||
archive_id = self.get_id_from_name(oid, archive_name)
|
||||
data = self.get(archive_id, filename)
|
||||
ext = os.path.splitext(filename.lower())[1]
|
||||
if ext == ".html" or ext == ".htm":
|
||||
return data
|
||||
elif ext == ".xml":
|
||||
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
|
||||
return data
|
||||
elif ext == ".xls":
|
||||
return sco_excel.send_excel_file(
|
||||
REQUEST, data, filename, mime=scu.XLS_MIMETYPE
|
||||
)
|
||||
elif ext == ".xlsx":
|
||||
return sco_excel.send_excel_file(
|
||||
REQUEST, data, filename, mime=scu.XLSX_MIMETYPE
|
||||
)
|
||||
elif ext == ".csv":
|
||||
return scu.sendCSVFile(REQUEST, data, filename)
|
||||
elif ext == ".pdf":
|
||||
return scu.sendPDFFile(REQUEST, data, filename)
|
||||
REQUEST.RESPONSE.setHeader("content-type", "application/octet-stream")
|
||||
return data # should set mimetype for known files like images
|
||||
mime = mimetypes.guess_type(filename)[0]
|
||||
if mime is None:
|
||||
mime = "application/octet-stream"
|
||||
|
||||
return scu.send_file(data, filename, mime=mime)
|
||||
|
||||
|
||||
class SemsArchiver(BaseArchiver):
|
||||
@ -305,7 +290,7 @@ def do_formsemestre_archive(
|
||||
from app.scodoc.sco_recapcomplet import make_formsemestre_recapcomplet
|
||||
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
|
||||
sem_archive_id = formsemestre_id
|
||||
archive_id = PVArchive.create_obj_archive(sem_archive_id, description)
|
||||
date = PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y à %H:%M")
|
||||
|
||||
@ -519,7 +504,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
||||
def formsemestre_list_archives(REQUEST, formsemestre_id):
|
||||
"""Page listing archives"""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
|
||||
sem_archive_id = formsemestre_id
|
||||
L = []
|
||||
for archive_id in PVArchive.list_obj_archives(sem_archive_id):
|
||||
a = {
|
||||
@ -559,11 +544,11 @@ def formsemestre_list_archives(REQUEST, formsemestre_id):
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
def formsemestre_get_archived_file(REQUEST, formsemestre_id, archive_name, filename):
|
||||
def formsemestre_get_archived_file(formsemestre_id, archive_name, filename):
|
||||
"""Send file to client."""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
|
||||
return PVArchive.get_archived_file(REQUEST, sem_archive_id, archive_name, filename)
|
||||
sem_archive_id = formsemestre_id
|
||||
return PVArchive.get_archived_file(sem_archive_id, archive_name, filename)
|
||||
|
||||
|
||||
def formsemestre_delete_archive(
|
||||
@ -575,7 +560,7 @@ def formsemestre_delete_archive(
|
||||
"opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER)
|
||||
)
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
sem_archive_id = sem["scodoc7_id"] or formsemestre_id
|
||||
sem_archive_id = formsemestre_id
|
||||
archive_id = PVArchive.get_id_from_name(sem_archive_id, archive_name)
|
||||
|
||||
dest_url = "formsemestre_list_archives?formsemestre_id=%s" % (formsemestre_id)
|
||||
|
@ -65,7 +65,7 @@ def etud_list_archives_html(REQUEST, etudid):
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
etud_archive_id = etud["scodoc7_id"] or etudid
|
||||
etud_archive_id = etudid
|
||||
L = []
|
||||
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
||||
a = {
|
||||
@ -118,7 +118,7 @@ def add_archives_info_to_etud_list(etuds):
|
||||
"""
|
||||
for etud in etuds:
|
||||
l = []
|
||||
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
|
||||
etud_archive_id = etud["etudid"]
|
||||
for archive_id in EtudsArchive.list_obj_archives(etud_archive_id):
|
||||
l.append(
|
||||
"%s (%s)"
|
||||
@ -181,7 +181,7 @@ def etud_upload_file_form(REQUEST, etudid):
|
||||
data = tf[2]["datafile"].read()
|
||||
descr = tf[2]["description"]
|
||||
filename = tf[2]["datafile"].filename
|
||||
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
|
||||
etud_archive_id = etud["etudid"]
|
||||
_store_etud_file_to_new_archive(
|
||||
etud_archive_id, data, filename, description=descr
|
||||
)
|
||||
@ -210,7 +210,7 @@ def etud_delete_archive(REQUEST, etudid, archive_name, dialog_confirmed=False):
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
|
||||
etud_archive_id = etud["etudid"]
|
||||
archive_id = EtudsArchive.get_id_from_name(etud_archive_id, archive_name)
|
||||
if not dialog_confirmed:
|
||||
return scu.confirm_dialog(
|
||||
@ -242,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."""
|
||||
etuds = sco_etud.get_etud_info(filled=True)
|
||||
etuds = sco_etud.get_etud_info(etudid=etudid, filled=True)
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
etud = etuds[0]
|
||||
etud_archive_id = etud["scodoc7_id"] or etud["etudid"]
|
||||
return EtudsArchive.get_archived_file(
|
||||
REQUEST, etud_archive_id, archive_name, filename
|
||||
)
|
||||
etud_archive_id = etud["etudid"]
|
||||
return EtudsArchive.get_archived_file(etud_archive_id, archive_name, filename)
|
||||
|
||||
|
||||
# --- Upload d'un ensemble de fichiers (pour un groupe d'étudiants)
|
||||
|
@ -35,9 +35,8 @@ from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.base import MIMEBase
|
||||
from email.header import Header
|
||||
|
||||
from reportlab.lib.colors import Color
|
||||
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
|
||||
import urllib
|
||||
|
||||
from flask import g
|
||||
from flask import 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":
|
||||
mod["mod_moy_txt"] = "-"
|
||||
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_coef_txt"] = "Malus"
|
||||
elif mod_moy < 0:
|
||||
@ -1061,7 +1063,7 @@ def _formsemestre_bulletinetud_header_html(
|
||||
# Menu
|
||||
endpoint = "notes.formsemestre_bulletinetud"
|
||||
url = REQUEST.URL0
|
||||
qurl = six.moves.urllib.parse.quote_plus(url + "?" + REQUEST.QUERY_STRING)
|
||||
qurl = urllib.parse.quote_plus(url + "?" + REQUEST.QUERY_STRING)
|
||||
|
||||
menuBul = [
|
||||
{
|
||||
|
@ -100,7 +100,7 @@ class ScoDocCache:
|
||||
log("Error: cache set failed !")
|
||||
except:
|
||||
log("XXX CACHE Warning: error in set !!!")
|
||||
|
||||
status = None
|
||||
return status
|
||||
|
||||
@classmethod
|
||||
|
@ -197,4 +197,4 @@ def formsemestre_estim_cost(
|
||||
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,
|
||||
javascripts=["js/etud_info.js"],
|
||||
format=format,
|
||||
REQUEST=REQUEST,
|
||||
with_html_headers=True,
|
||||
)
|
||||
|
||||
|
@ -1039,7 +1039,7 @@ def formation_table_recap(formation_id, format="html", REQUEST=None):
|
||||
pdf_title=title,
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
||||
return tab.make_page(format=format)
|
||||
|
||||
|
||||
def ue_list_semestre_ids(ue):
|
||||
|
@ -578,7 +578,7 @@ def _view_etuds_page(
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
if format != "html":
|
||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
||||
return tab.make_page(format=format)
|
||||
|
||||
H.append(tab.html())
|
||||
|
||||
@ -678,7 +678,8 @@ def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
|
||||
sem_id = semset["sem_id"]
|
||||
csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id)
|
||||
if format == "raw":
|
||||
return scu.sendCSVFile(REQUEST, csv_data, etape_apo + ".txt")
|
||||
scu.send_file(csv_data, etape_apo, suffix=".txt", mime=scu.CSV_MIMETYPE)
|
||||
|
||||
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
|
||||
|
||||
(
|
||||
@ -753,7 +754,7 @@ def view_apo_csv(etape_apo="", semset_id="", format="html", REQUEST=None):
|
||||
)
|
||||
|
||||
if format != "html":
|
||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
||||
return tab.make_page(format=format)
|
||||
|
||||
H += [
|
||||
tab.html(),
|
||||
|
@ -31,27 +31,21 @@
|
||||
# Ancien module "scolars"
|
||||
import os
|
||||
import time
|
||||
|
||||
from flask import url_for, g, request
|
||||
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.header import Header
|
||||
from email.mime.base import MIMEBase
|
||||
from operator import itemgetter
|
||||
|
||||
from flask import url_for, g, request
|
||||
from flask_mail import Message
|
||||
|
||||
from app import email
|
||||
from app import log
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import SCO_ENCODING
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
||||
|
||||
from app import log
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc import safehtml
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.scolog import logdb
|
||||
from flask_mail import Message
|
||||
from app import mail
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
|
||||
MONTH_NAMES_ABBREV = [
|
||||
"Jan ",
|
||||
@ -256,7 +250,6 @@ _identiteEditor = ndb.EditableTable(
|
||||
"photo_filename",
|
||||
"code_ine",
|
||||
"code_nip",
|
||||
"scodoc7_id",
|
||||
),
|
||||
filter_dept=True,
|
||||
sortkey="nom",
|
||||
@ -307,7 +300,7 @@ def check_nom_prenom(cnx, nom="", prenom="", etudid=None):
|
||||
# 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):
|
||||
return False, 0
|
||||
# Now count homonyms:
|
||||
# Now count homonyms (dans tous les départements):
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
req = """SELECT id
|
||||
FROM identite
|
||||
@ -440,7 +433,7 @@ def notify_etud_change(email_addr, etud, before, after, subject):
|
||||
"Civilité: " + etud["civilite_str"],
|
||||
"Nom: " + etud["nom"],
|
||||
"Prénom: " + etud["prenom"],
|
||||
"Etudid: " + etud["etudid"],
|
||||
"Etudid: " + str(etud["etudid"]),
|
||||
"\n",
|
||||
"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: subject: %s" % subject)
|
||||
log(txt)
|
||||
mail.send_email(
|
||||
email.send_email(
|
||||
subject, sco_preferences.get_preference("email_from_addr"), [email_addr], txt
|
||||
)
|
||||
return txt
|
||||
@ -896,7 +889,7 @@ def list_scolog(etudid):
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
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},
|
||||
)
|
||||
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é
|
||||
"""
|
||||
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:
|
||||
# toto()
|
||||
if style is None:
|
||||
|
@ -96,3 +96,20 @@ class ScoGenError(ScoException):
|
||||
|
||||
class ScoInvalidDateError(ScoValueError):
|
||||
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]),
|
||||
)
|
||||
if format != "html":
|
||||
return tab.make_page(
|
||||
format=format, with_html_headers=False, REQUEST=REQUEST
|
||||
)
|
||||
return tab.make_page(format=format, with_html_headers=False)
|
||||
tab_html = tab.html()
|
||||
nb_rows = tab.get_nb_rows()
|
||||
else:
|
||||
|
@ -225,7 +225,7 @@ def search_etuds_infos(expnom=None, code_nip=None):
|
||||
else:
|
||||
code_nip = code_nip or expnom
|
||||
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:
|
||||
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)
|
||||
|
||||
return tab.make_page(
|
||||
format=format, with_html_headers=False, REQUEST=REQUEST, publish=True
|
||||
)
|
||||
return tab.make_page(format=format, with_html_headers=False, publish=True)
|
||||
|
@ -27,6 +27,7 @@
|
||||
|
||||
"""Operations de base sur les formsemestres
|
||||
"""
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
import time
|
||||
from operator import itemgetter
|
||||
|
||||
@ -60,6 +61,7 @@ _formsemestreEditor = ndb.EditableTable(
|
||||
"gestion_semestrielle",
|
||||
"etat",
|
||||
"bul_hide_xml",
|
||||
"block_moyennes",
|
||||
"bul_bgcolor",
|
||||
"modalite",
|
||||
"resp_can_edit",
|
||||
@ -67,7 +69,6 @@ _formsemestreEditor = ndb.EditableTable(
|
||||
"ens_can_edit_eval",
|
||||
"elt_sem_apo",
|
||||
"elt_annee_apo",
|
||||
"scodoc7_id",
|
||||
),
|
||||
filter_dept=True,
|
||||
sortkey="date_debut",
|
||||
@ -81,6 +82,7 @@ _formsemestreEditor = ndb.EditableTable(
|
||||
"etat": bool,
|
||||
"gestion_compensation": bool,
|
||||
"bul_hide_xml": bool,
|
||||
"block_moyennes": bool,
|
||||
"gestion_semestrielle": bool,
|
||||
"gestion_compensation": bool,
|
||||
"gestion_semestrielle": bool,
|
||||
@ -93,6 +95,10 @@ _formsemestreEditor = ndb.EditableTable(
|
||||
|
||||
def get_formsemestre(formsemestre_id):
|
||||
"list ONE formsemestre"
|
||||
if not isinstance(formsemestre_id, int):
|
||||
raise ScoValueError(
|
||||
"""Semestre invalide, reprenez l'opération au départ ou si le problème persiste signalez l'erreur sur scodoc-devel@listes.univ-paris13.fr"""
|
||||
)
|
||||
try:
|
||||
sem = do_formsemestre_list(args={"formsemestre_id": formsemestre_id})[0]
|
||||
return sem
|
||||
@ -578,7 +584,7 @@ def view_formsemestre_by_etape(etape_apo=None, format="html", REQUEST=None):
|
||||
</form>""",
|
||||
)
|
||||
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):
|
||||
|
@ -501,6 +501,14 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||
"labels": [""],
|
||||
},
|
||||
),
|
||||
(
|
||||
"block_moyennes",
|
||||
{
|
||||
"input_type": "boolcheckbox",
|
||||
"title": "Bloquer moyennes",
|
||||
"explanation": "empêcher le calcul des moyennes d'UE et générale.",
|
||||
},
|
||||
),
|
||||
(
|
||||
"sep",
|
||||
{
|
||||
@ -693,7 +701,6 @@ def do_formsemestre_createwithmodules(REQUEST=None, edit=False):
|
||||
tf[2]["bul_hide_xml"] = False
|
||||
else:
|
||||
tf[2]["bul_hide_xml"] = True
|
||||
|
||||
# remap les identifiants de responsables:
|
||||
tf[2]["responsable_id"] = User.get_user_id_from_nomplogin(
|
||||
tf[2]["responsable_id"]
|
||||
|
@ -64,7 +64,7 @@ from app.scodoc.sco_formsemestre_custommenu import formsemestre_custommenu_html
|
||||
|
||||
|
||||
def _build_menu_stats(formsemestre_id):
|
||||
"Définition du menu 'Statistiques' "
|
||||
"Définition du menu 'Statistiques'"
|
||||
return [
|
||||
{
|
||||
"title": "Statistiques...",
|
||||
@ -495,6 +495,7 @@ def formsemestre_page_title():
|
||||
if not formsemestre_id:
|
||||
return ""
|
||||
try:
|
||||
formsemestre_id = int(formsemestre_id)
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id).copy()
|
||||
except:
|
||||
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 += ">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
|
||||
@ -913,10 +914,10 @@ def formsemestre_status_head(formsemestre_id=None, REQUEST=None, page_title=None
|
||||
html_sco_header.html_sem_header(
|
||||
REQUEST, page_title, sem, with_page_header=False, with_h2=False
|
||||
),
|
||||
"""<table>
|
||||
f"""<table>
|
||||
<tr><td class="fichetitre2">Formation: </td><td>
|
||||
<a href="Notes/ue_list?formation_id=%(formation_id)s" class="discretelink" title="Formation %(acronyme)s, v%(version)s">%(titre)s</a>"""
|
||||
% F,
|
||||
<a href="{url_for('notes.ue_list', scodoc_dept=g.scodoc_dept, formation_id=F['formation_id'])}"
|
||||
class="discretelink" title="Formation {F['acronyme']}, v{F['version']}">{F['titre']}</a>""",
|
||||
]
|
||||
if sem["semestre_id"] >= 0:
|
||||
H.append(", %s %s" % (parcours.SESSION_NAME, sem["semestre_id"]))
|
||||
@ -947,10 +948,13 @@ Il y a des notes en attente ! Le classement des étudiants n'a qu'une valeur ind
|
||||
</td></tr>"""
|
||||
)
|
||||
H.append("</table>")
|
||||
sem_warning = ""
|
||||
if sem["bul_hide_xml"]:
|
||||
H.append(
|
||||
'<p class="fontorange"><em>Bulletins non publiés sur le portail</em></p>'
|
||||
)
|
||||
sem_warning += "Bulletins non publiés sur le portail. "
|
||||
if sem["block_moyennes"]:
|
||||
sem_warning += "Calcul des moyennes bloqué !"
|
||||
if sem_warning:
|
||||
H.append('<p class="fontorange"><em>' + sem_warning + "</em></p>")
|
||||
if sem["semestre_id"] >= 0 and not sco_formsemestre.sem_une_annee(sem):
|
||||
H.append(
|
||||
'<p class="fontorange"><em>Attention: ce semestre couvre plusieurs années scolaires !</em></p>'
|
||||
|
@ -1499,7 +1499,7 @@ def _sortgroups(groups):
|
||||
# Tri: place 'all' en tête, puis groupe par partition / nom de groupe
|
||||
R = [g for g in groups if g["partition_name"] is None]
|
||||
o = [g for g in groups if g["partition_name"] != None]
|
||||
o.sort(key=lambda x: (x["numero"], x["group_name"]))
|
||||
o.sort(key=lambda x: (x["numero"] or 0, x["group_name"]))
|
||||
|
||||
return R + o
|
||||
|
||||
|
@ -477,6 +477,9 @@ def groups_table(
|
||||
[(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)
|
||||
columns_ids.append("etat")
|
||||
@ -500,11 +503,7 @@ def groups_table(
|
||||
if with_annotations:
|
||||
sco_etud.add_annotations_to_etud_list(groups_infos.members)
|
||||
columns_ids += ["annotations_str"]
|
||||
|
||||
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_sem_name = groups_infos.formsemestre["session_id"]
|
||||
moodle_groupenames = set()
|
||||
# ajoute liens
|
||||
for etud in groups_infos.members:
|
||||
@ -529,23 +528,27 @@ def groups_table(
|
||||
# et groupes:
|
||||
for partition_id in etud["partitions"]:
|
||||
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 = []
|
||||
if groups_infos.selected_partitions:
|
||||
# il y a des groupes selectionnes, utilise leurs partitions
|
||||
for partition_id in groups_infos.selected_partitions:
|
||||
if partition_id in etud["partitions"]:
|
||||
moodle_groupename.append(
|
||||
etud["partitions"][partition_id]["group_name"]
|
||||
partitions_name[partition_id]
|
||||
+ "-"
|
||||
+ etud["partitions"][partition_id]["group_name"]
|
||||
)
|
||||
else:
|
||||
# pas de groupes sélectionnés: prend le premier s'il y en a un
|
||||
moodle_groupename = ["tous"]
|
||||
if etud["partitions"]:
|
||||
for p in etud["partitions"].items(): # partitions is an OrderedDict
|
||||
moodle_groupename = [
|
||||
partitions_name[p[0]] + "-" + p[1]["group_name"]
|
||||
]
|
||||
break
|
||||
moodle_groupename = [p[1]["group_name"]]
|
||||
else:
|
||||
moodle_groupename = ["tous"]
|
||||
|
||||
moodle_groupenames |= set(moodle_groupename)
|
||||
etud["semestre_groupe"] = moodle_sem_name + "-" + "+".join(moodle_groupename)
|
||||
|
||||
@ -706,7 +709,7 @@ def groups_table(
|
||||
):
|
||||
if format == "moodlecsv":
|
||||
format = "csv"
|
||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
||||
return tab.make_page(format=format)
|
||||
|
||||
elif format == "xlsappel":
|
||||
xls = sco_excel.excel_feuille_listeappel(
|
||||
@ -935,7 +938,7 @@ def form_choix_saisie_semaine(groups_infos, REQUEST=None):
|
||||
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
|
||||
Each (student,group) will be listed on a separate line
|
||||
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"],
|
||||
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):
|
||||
"""Liste les etudiants inscrits dans n'importe quel semestre
|
||||
du même département
|
||||
SAUF sem à la date de début de sem.
|
||||
"""
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
|
||||
cursor.execute(
|
||||
"""SELECT I.etudid
|
||||
FROM notes_formsemestre_inscription I, notes_formsemestre S
|
||||
WHERE I.formsemestre_id = S.id
|
||||
"""SELECT ins.etudid
|
||||
FROM
|
||||
notes_formsemestre_inscription ins,
|
||||
notes_formsemestre S
|
||||
WHERE ins.formsemestre_id = S.id
|
||||
AND S.id != %(formsemestre_id)s
|
||||
AND S.date_debut <= %(date_debut_iso)s
|
||||
AND S.date_fin >= %(date_debut_iso)s
|
||||
AND S.dept_id = %(dept_id)s
|
||||
""",
|
||||
sem,
|
||||
)
|
||||
|
@ -27,8 +27,8 @@
|
||||
|
||||
"""Liste des notes d'une évaluation
|
||||
"""
|
||||
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
|
||||
from operator import itemgetter
|
||||
import urllib
|
||||
|
||||
import flask
|
||||
from flask import url_for, g
|
||||
@ -50,6 +50,7 @@ from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_moduleimpl
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_users
|
||||
import sco_version
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
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
|
||||
)
|
||||
|
||||
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
|
||||
t = tab.make_page(format=format, with_html_headers=False)
|
||||
if format != "html":
|
||||
return t
|
||||
|
||||
@ -569,7 +570,7 @@ def _add_eval_columns(
|
||||
comment = ""
|
||||
explanation = "%s (%s) %s" % (
|
||||
NotesDB[etudid]["date"].strftime("%d/%m/%y %Hh%M"),
|
||||
NotesDB[etudid]["uid"],
|
||||
sco_users.user_info(NotesDB[etudid]["uid"])["nomcomplet"],
|
||||
comment,
|
||||
)
|
||||
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>'
|
||||
% (
|
||||
etud["etudid"],
|
||||
six.moves.urllib.parse.quote(E["jour"]),
|
||||
six.moves.urllib.parse.quote(E["jour"]),
|
||||
urllib.parse.quote(E["jour"]),
|
||||
urllib.parse.quote(E["jour"]),
|
||||
demijournee,
|
||||
E["moduleimpl_id"],
|
||||
)
|
||||
|
@ -85,7 +85,7 @@ def scodoc_table_etuds_lycees(format="html", REQUEST=None):
|
||||
no_links=True,
|
||||
)
|
||||
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":
|
||||
return t
|
||||
H = [
|
||||
@ -192,7 +192,7 @@ def formsemestre_etuds_lycees(
|
||||
tab.base_url += "&only_primo=1"
|
||||
if no_grouping:
|
||||
tab.base_url += "&no_grouping=1"
|
||||
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
|
||||
t = tab.make_page(format=format, with_html_headers=False)
|
||||
if format != "html":
|
||||
return t
|
||||
F = [
|
||||
|
@ -529,10 +529,10 @@ def do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id, REQUEST=None):
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
cursor.execute(
|
||||
"""DELETE FROM notes_moduleimpl_inscription
|
||||
"""DELETE FROM notes_moduleimpl_inscription
|
||||
WHERE moduleimpl_inscription_id IN (
|
||||
SELECT i.moduleimpl_inscription_id FROM
|
||||
notes_moduleimpl mi, notes_modules mod,
|
||||
notes_moduleimpl mi, notes_modules mod,
|
||||
notes_formsemestre sem, notes_moduleimpl_inscription i
|
||||
WHERE sem.formsemestre_id = %(formsemestre_id)s
|
||||
AND mi.formsemestre_id = sem.formsemestre_id
|
||||
|
@ -28,7 +28,7 @@
|
||||
"""Tableau de bord module
|
||||
"""
|
||||
import time
|
||||
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
|
||||
import urllib
|
||||
|
||||
from flask import g, url_for
|
||||
from flask_login import current_user
|
||||
@ -137,7 +137,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0, REQUEST=None):
|
||||
"title": "Absences ce jour",
|
||||
"endpoint": "absences.EtatAbsencesDate",
|
||||
"args": {
|
||||
"date": six.moves.urllib.parse.quote(E["jour"], safe=""),
|
||||
"date": urllib.parse.quote(E["jour"], safe=""),
|
||||
"group_ids": group_id,
|
||||
},
|
||||
"enabled": E["jour"],
|
||||
|
@ -45,13 +45,20 @@ class Permission(object):
|
||||
NBITS = 1 # maximum bits used (for formatting)
|
||||
ALL_PERMISSIONS = [-1]
|
||||
description = {} # { symbol : blah blah }
|
||||
permission_by_name = {} # { symbol : int }
|
||||
|
||||
@staticmethod
|
||||
def init_permissions():
|
||||
for (perm, symbol, description) in _SCO_PERMISSIONS:
|
||||
setattr(Permission, symbol, perm)
|
||||
Permission.description[symbol] = description
|
||||
Permission.permission_by_name[symbol] = perm
|
||||
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()
|
||||
|
@ -217,6 +217,5 @@ def formsemestre_poursuite_report(formsemestre_id, format="html", REQUEST=None):
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
format=format,
|
||||
REQUEST=REQUEST,
|
||||
with_html_headers=True,
|
||||
)
|
||||
|
@ -112,6 +112,7 @@ get_base_preferences(formsemestre_id)
|
||||
"""
|
||||
import flask
|
||||
from flask import g, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
from app.models import Departement
|
||||
from app.scodoc import sco_cache
|
||||
@ -180,7 +181,7 @@ def _convert_pref_type(p, pref_spec):
|
||||
|
||||
def _get_pref_default_value_from_config(name, pref_spec):
|
||||
"""get default value store in application level config.
|
||||
If not found, use defalut value hardcoded in pref_spec.
|
||||
If not found, use default value hardcoded in pref_spec.
|
||||
"""
|
||||
# XXX va changer avec la nouvelle base
|
||||
# search in scu.CONFIG
|
||||
@ -1408,7 +1409,7 @@ class BasePreferences(object):
|
||||
{
|
||||
"initvalue": 1,
|
||||
"title": "Indique si les bulletins sont publiés",
|
||||
"explanation": "décocher si vous n'avez pas de portal étudiant publiant les bulletins",
|
||||
"explanation": "décocher si vous n'avez pas de portail étudiant publiant les bulletins",
|
||||
"input_type": "boolcheckbox",
|
||||
"labels": ["non", "oui"],
|
||||
"category": "bul",
|
||||
@ -1891,7 +1892,7 @@ class BasePreferences(object):
|
||||
|
||||
def get(self, formsemestre_id, name):
|
||||
"""Returns preference value.
|
||||
If global_lookup, when no value defined for this semestre, returns global value.
|
||||
when no value defined for this semestre, returns global value.
|
||||
"""
|
||||
params = {
|
||||
"dept_id": self.dept_id,
|
||||
@ -1901,7 +1902,7 @@ class BasePreferences(object):
|
||||
cnx = ndb.GetDBConnexion()
|
||||
plist = self._editor.list(cnx, params)
|
||||
if not plist:
|
||||
del params["formsemestre_id"]
|
||||
params["formsemestre_id"] = None
|
||||
plist = self._editor.list(cnx, params)
|
||||
if not plist:
|
||||
return self.default[name]
|
||||
@ -2022,7 +2023,9 @@ class BasePreferences(object):
|
||||
html_sco_header.sco_header(page_title="Préférences"),
|
||||
"<h2>Préférences globales pour %s</h2>" % scu.ScoURL(),
|
||||
f"""<p><a href="{url_for("scolar.config_logos", scodoc_dept=g.scodoc_dept)
|
||||
}">modification des logos du département (pour documents pdf)</a></p>""",
|
||||
}">modification des logos du département (pour documents pdf)</a></p>"""
|
||||
if current_user.is_administrator()
|
||||
else "",
|
||||
"""<p class="help">Ces paramètres s'appliquent par défaut à tous les semestres, sauf si ceux-ci définissent des valeurs spécifiques.</p>
|
||||
<p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
|
||||
""",
|
||||
@ -2253,7 +2256,7 @@ function set_global_pref(el, pref_name) {
|
||||
|
||||
#
|
||||
def doc_preferences():
|
||||
""" Liste les preferences en MarkDown, pour la documentation"""
|
||||
"""Liste les preferences en MarkDown, pour la documentation"""
|
||||
L = []
|
||||
for cat, cat_descr in PREF_CATEGORIES:
|
||||
L.append([""])
|
||||
|
@ -535,7 +535,6 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True, REQUEST=No
|
||||
return tab.make_page(
|
||||
format=format,
|
||||
with_html_headers=False,
|
||||
REQUEST=REQUEST,
|
||||
publish=publish,
|
||||
)
|
||||
tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
|
||||
|
@ -27,8 +27,9 @@
|
||||
|
||||
"""Tableau recapitulatif des notes d'un semestre
|
||||
"""
|
||||
import time
|
||||
import datetime
|
||||
import json
|
||||
import time
|
||||
from xml.etree import ElementTree
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
@ -227,11 +228,14 @@ def do_formsemestre_recapcomplet(
|
||||
if format == "xml" or format == "html":
|
||||
return data
|
||||
elif format == "csv":
|
||||
return scu.sendCSVFile(REQUEST, data, filename)
|
||||
elif format[:3] == "xls":
|
||||
return sco_excel.send_excel_file(REQUEST, data, filename)
|
||||
return scu.send_file(data, filename=filename, mime=scu.CSV_MIMETYPE)
|
||||
elif format[:3] == "xls" or format[:3] == "xlsx":
|
||||
return scu.send_file(data, filename=filename, mime=scu.XLSX_MIMETYPE)
|
||||
elif format == "json":
|
||||
return scu.sendJSON(REQUEST, data)
|
||||
js = json.dumps(data, indent=1, cls=scu.ScoDocJSONEncoder)
|
||||
return scu.send_file(
|
||||
js, filename=filename, suffix=scu.JSON_SUFFIX, mime=scu.JSON_MIMETYPE
|
||||
)
|
||||
else:
|
||||
raise ValueError("unknown format %s" % format)
|
||||
|
||||
|
@ -355,7 +355,6 @@ def formsemestre_report_counts(
|
||||
t = tab.make_page(
|
||||
title="""<h2 class="formsemestre">Comptes croisés</h2>""",
|
||||
format=format,
|
||||
REQUEST=REQUEST,
|
||||
with_html_headers=False,
|
||||
)
|
||||
if format != "html":
|
||||
@ -722,7 +721,7 @@ def formsemestre_suivi_cohorte(
|
||||
)
|
||||
if only_primo:
|
||||
tab.base_url += "&only_primo=on"
|
||||
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
|
||||
t = tab.make_page(format=format, with_html_headers=False)
|
||||
if format != "html":
|
||||
return t
|
||||
|
||||
@ -1210,7 +1209,7 @@ def formsemestre_suivi_parcours(
|
||||
tab.base_url += "&only_primo=1"
|
||||
if no_grouping:
|
||||
tab.base_url += "&no_grouping=1"
|
||||
t = tab.make_page(format=format, with_html_headers=False, REQUEST=REQUEST)
|
||||
t = tab.make_page(format=format, with_html_headers=False)
|
||||
if format != "html":
|
||||
return t
|
||||
F = [
|
||||
|
@ -9,48 +9,50 @@ from app.scodoc.sco_permissions import Permission as p
|
||||
SCO_ROLES_DEFAULTS = {
|
||||
"Observateur": (p.ScoObservateur,),
|
||||
"Ens": (
|
||||
p.ScoObservateur,
|
||||
p.ScoView,
|
||||
p.ScoEnsView,
|
||||
p.ScoUsersView,
|
||||
p.ScoEtudAddAnnotations,
|
||||
p.ScoAbsChange,
|
||||
p.ScoAbsAddBillet,
|
||||
p.ScoAbsChange,
|
||||
p.ScoEnsView,
|
||||
p.ScoEntrepriseView,
|
||||
p.ScoEtudAddAnnotations,
|
||||
p.ScoObservateur,
|
||||
p.ScoUsersView,
|
||||
p.ScoView,
|
||||
),
|
||||
"Secr": (
|
||||
p.ScoObservateur,
|
||||
p.ScoView,
|
||||
p.ScoUsersView,
|
||||
p.ScoEtudAddAnnotations,
|
||||
p.ScoAbsChange,
|
||||
p.ScoAbsAddBillet,
|
||||
p.ScoEntrepriseView,
|
||||
p.ScoAbsChange,
|
||||
p.ScoEditApo,
|
||||
p.ScoEntrepriseChange,
|
||||
p.ScoEntrepriseView,
|
||||
p.ScoEtudAddAnnotations,
|
||||
p.ScoEtudChangeAdr,
|
||||
p.ScoObservateur,
|
||||
p.ScoUsersView,
|
||||
p.ScoView,
|
||||
),
|
||||
# Admin est le chef du département, pas le "super admin"
|
||||
# on doit donc lister toutes ses permissions:
|
||||
"Admin": (
|
||||
p.ScoObservateur,
|
||||
p.ScoView,
|
||||
p.ScoEnsView,
|
||||
p.ScoUsersView,
|
||||
p.ScoEtudAddAnnotations,
|
||||
p.ScoAbsChange,
|
||||
p.ScoAbsAddBillet,
|
||||
p.ScoEntrepriseView,
|
||||
p.ScoEntrepriseChange,
|
||||
p.ScoEtudChangeAdr,
|
||||
p.ScoAbsChange,
|
||||
p.ScoChangeFormation,
|
||||
p.ScoEditFormationTags,
|
||||
p.ScoEditAllNotes,
|
||||
p.ScoChangePreferences,
|
||||
p.ScoEditAllEvals,
|
||||
p.ScoImplement,
|
||||
p.ScoEditAllNotes,
|
||||
p.ScoEditApo,
|
||||
p.ScoEditFormationTags,
|
||||
p.ScoEnsView,
|
||||
p.ScoEntrepriseChange,
|
||||
p.ScoEntrepriseView,
|
||||
p.ScoEtudAddAnnotations,
|
||||
p.ScoEtudChangeAdr,
|
||||
p.ScoEtudChangeGroups,
|
||||
p.ScoEtudInscrit,
|
||||
p.ScoImplement,
|
||||
p.ScoObservateur,
|
||||
p.ScoUsersAdmin,
|
||||
p.ScoChangePreferences,
|
||||
p.ScoUsersView,
|
||||
p.ScoView,
|
||||
),
|
||||
# RespPE est le responsable poursuites d'études
|
||||
# il peut ajouter des tags sur les formations:
|
||||
|
@ -494,9 +494,10 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
|
||||
}
|
||||
ndb.quote_dict(aa)
|
||||
cursor.execute(
|
||||
"""INSERT INTO notes_notes
|
||||
(etudid,evaluation_id,value,comment,date,uid)
|
||||
VALUES (%(etudid)s,%(evaluation_id)s,%(value)s,%(comment)s,%(date)s,%(uid)s)""",
|
||||
"""INSERT INTO notes_notes
|
||||
(etudid, evaluation_id, value, comment, date, uid)
|
||||
VALUES (%(etudid)s,%(evaluation_id)s,%(value)s,%(comment)s,%(date)s,%(uid)s)
|
||||
""",
|
||||
aa,
|
||||
)
|
||||
changed = True
|
||||
@ -515,10 +516,10 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
|
||||
# recopie l'ancienne note dans notes_notes_log, puis update
|
||||
if do_it:
|
||||
cursor.execute(
|
||||
"""INSERT INTO notes_notes_log
|
||||
"""INSERT INTO notes_notes_log
|
||||
(etudid,evaluation_id,value,comment,date,uid)
|
||||
SELECT etudid, evaluation_id, value, comment, date, uid
|
||||
FROM notes_notes
|
||||
FROM notes_notes
|
||||
WHERE etudid=%(etudid)s
|
||||
and evaluation_id=%(evaluation_id)s
|
||||
""",
|
||||
@ -536,8 +537,8 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
|
||||
if value != scu.NOTES_SUPPRESS:
|
||||
if do_it:
|
||||
cursor.execute(
|
||||
"""UPDATE notes_notes
|
||||
SET value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
|
||||
"""UPDATE notes_notes
|
||||
SET value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
|
||||
WHERE etudid = %(etudid)s
|
||||
and evaluation_id = %(evaluation_id)s
|
||||
""",
|
||||
@ -550,7 +551,7 @@ def _notes_add(user, evaluation_id: int, notes: list, comment=None, do_it=True):
|
||||
% (evaluation_id, etudid, oldval)
|
||||
)
|
||||
cursor.execute(
|
||||
"""DELETE FROM notes_notes
|
||||
"""DELETE FROM notes_notes
|
||||
WHERE etudid = %(etudid)s
|
||||
AND evaluation_id = %(evaluation_id)s
|
||||
""",
|
||||
@ -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):
|
||||
"""Saisie des notes via un fichier Excel"""
|
||||
authuser = REQUEST.AUTHENTICATED_USER
|
||||
authusername = str(authuser)
|
||||
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
|
||||
if not evals:
|
||||
raise ScoValueError("invalid evaluation_id")
|
||||
E = evals[0]
|
||||
M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||
formsemestre_id = M["formsemestre_id"]
|
||||
if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_id"]):
|
||||
if not sco_permissions_check.can_edit_notes(current_user, E["moduleimpl_id"]):
|
||||
return (
|
||||
html_sco_header.sco_header()
|
||||
+ "<h2>Modification des notes impossible pour %s</h2>" % authusername
|
||||
+ "<h2>Modification des notes impossible pour %s</h2>"
|
||||
% current_user.user_name
|
||||
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
||||
avez l'autorisation d'effectuer cette opération)</p>
|
||||
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
|
||||
@ -858,9 +858,7 @@ def has_existing_decision(M, E, etudid):
|
||||
|
||||
def saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
|
||||
"""Formulaire saisie notes d'une évaluation pour un groupe"""
|
||||
authuser = REQUEST.AUTHENTICATED_USER
|
||||
authusername = str(authuser)
|
||||
|
||||
group_ids = [int(group_id) for group_id in group_ids]
|
||||
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
|
||||
if not evals:
|
||||
raise ScoValueError("invalid evaluation_id")
|
||||
@ -871,10 +869,11 @@ def saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
|
||||
formsemestre_id = M["formsemestre_id"]
|
||||
# Check access
|
||||
# (admin, respformation, and responsable_id)
|
||||
if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_id"]):
|
||||
if not sco_permissions_check.can_edit_notes(current_user, E["moduleimpl_id"]):
|
||||
return (
|
||||
html_sco_header.sco_header()
|
||||
+ "<h2>Modification des notes impossible pour %s</h2>" % authusername
|
||||
+ "<h2>Modification des notes impossible pour %s</h2>"
|
||||
% current_user.user_name
|
||||
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
||||
avez l'autorisation d'effectuer cette opération)</p>
|
||||
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
|
||||
@ -1154,9 +1153,9 @@ def _form_saisie_notes(E, M, group_ids, destination="", REQUEST=None):
|
||||
"attributes": [
|
||||
'class="note%s"' % classdem,
|
||||
disabled_attr,
|
||||
"data-last-saved-value=%s" % e["val"],
|
||||
"data-orig-value=%s" % e["val"],
|
||||
"data-etudid=%s" % etudid,
|
||||
'data-last-saved-value="%s"' % e["val"],
|
||||
'data-orig-value="%s"' % e["val"],
|
||||
'data-etudid="%s"' % etudid,
|
||||
],
|
||||
"template": """<tr%(item_dom_attr)s class="etud_elem """
|
||||
+ " ".join(etud_classes)
|
||||
@ -1290,7 +1289,7 @@ def get_note_history_menu(evaluation_id, etudid):
|
||||
nv = "" # ne repete pas la valeur de la note courante
|
||||
else:
|
||||
# ancienne valeur
|
||||
nv = '<span class="histvalue">: %s</span>' % dispnote
|
||||
nv = ": %s" % dispnote
|
||||
first = False
|
||||
if i["comment"]:
|
||||
comment = ' <span class="histcomment">%s</span>' % i["comment"]
|
||||
|
@ -418,7 +418,7 @@ def do_semset_remove_sem(semset_id, formsemestre_id):
|
||||
# ----------------------------------------
|
||||
|
||||
|
||||
def semset_page(format="html", REQUEST=None):
|
||||
def semset_page(format="html"):
|
||||
"""Page avec liste semsets:
|
||||
Table avec : date_debut date_fin titre liste des semestres
|
||||
"""
|
||||
@ -468,7 +468,7 @@ def semset_page(format="html", REQUEST=None):
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
if format != "html":
|
||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
||||
return tab.make_page(format=format)
|
||||
|
||||
page_title = "Ensembles de semestres"
|
||||
H = [
|
||||
|
@ -400,7 +400,10 @@ def list_synch(sem, anneeapogee=None):
|
||||
def key2etud(key, etud_apo=False):
|
||||
if not etud_apo:
|
||||
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[
|
||||
"datefinalisationinscription"
|
||||
@ -508,7 +511,14 @@ def list_all(etudsapo_set):
|
||||
# d'interrogation par etudiant.
|
||||
cnx = ndb.GetDBConnexion()
|
||||
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()])
|
||||
all_set = set(key2etudid.keys())
|
||||
|
||||
|
@ -167,7 +167,7 @@ def evaluation_list_operations(evaluation_id, REQUEST=None):
|
||||
% (E["description"], E["jour"]),
|
||||
preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
|
||||
)
|
||||
return tab.make_page(REQUEST=REQUEST)
|
||||
return tab.make_page()
|
||||
|
||||
|
||||
def formsemestre_list_saisies_notes(formsemestre_id, format="html", REQUEST=None):
|
||||
@ -222,7 +222,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html", REQUEST=None
|
||||
+ 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=""):
|
||||
|
@ -217,7 +217,7 @@ def list_users(
|
||||
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):
|
||||
@ -258,6 +258,7 @@ def user_info(user_name_or_id=None, user=None):
|
||||
info = u.to_dict()
|
||||
else:
|
||||
info = None
|
||||
user_name = "inconnu"
|
||||
else:
|
||||
info = user.to_dict()
|
||||
user_name = user.user_name
|
||||
|
@ -2612,7 +2612,8 @@ div.maindiv {
|
||||
margin: 1em;
|
||||
}
|
||||
ul.main {
|
||||
list-style-type: square;
|
||||
list-style-type: square;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
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 %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1>Sign In</h1>
|
||||
<h1>Connexion</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
{{ wtf.quick_form(form) }}
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
Forgot Your Password?
|
||||
<a href="{{ url_for('auth.reset_password_request') }}">Click to Reset It</a>
|
||||
En cas d'oubli de votre mot de passe
|
||||
<a href="{{ url_for('auth.reset_password_request') }}">cliquez ici pour le réinitialiser</a>.
|
||||
</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 %}
|
@ -11,7 +11,7 @@
|
||||
ou écrire la liste "notes" <a href="mailto:notes@listes.univ-paris13.fr">notes@listes.univ-paris13.fr</a> en
|
||||
indiquant la version du logiciel
|
||||
<br />
|
||||
(plus d'informations sur les listes de diffusion<a href="https://scodoc.org/ListesDeDiffusion/">voir
|
||||
(plus d'informations sur les listes de diffusion <a href="https://scodoc.org/ListesDeDiffusion/">voir
|
||||
cette page</a>).
|
||||
</p>
|
||||
|
||||
|
@ -24,9 +24,9 @@
|
||||
|
||||
<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
|
||||
indiquant la version du logiciel (ScoDoc {SCOVERSION})
|
||||
<br />(pour plus d'informations sur les listes de diffusion <a
|
||||
href="https://scodoc.org/ListesDeDiffusion/">voir cette page</a>).
|
||||
indiquant la version du logiciel (ScoDoc {{SCOVERSION}})
|
||||
<br />(pour plus d'informations sur les listes de diffusion
|
||||
<a href="https://scodoc.org/ListesDeDiffusion/">voir cette page</a>).
|
||||
</p>
|
||||
|
||||
</body>
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% block app_content %}
|
||||
<h2>ScoDoc: gestion scolarité (version béta)</h2>
|
||||
<h2>ScoDoc 9 - suivi scolarité</h2>
|
||||
|
||||
{% if not current_user.is_anonymous %}
|
||||
<p>Bonjour <font color="red"><b>{{current_user.get_nomcomplet()}}</b>
|
||||
@ -24,10 +24,6 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<font color="red">Ceci est une version de test,
|
||||
ne pas utiliser en production !</font>
|
||||
</p>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<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>
|
||||
</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 %}
|
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 cgi
|
||||
import datetime
|
||||
import dateutil
|
||||
import dateutil.parser
|
||||
import re
|
||||
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
|
||||
import string
|
||||
import time
|
||||
import urllib
|
||||
from xml.etree import ElementTree
|
||||
|
||||
import flask
|
||||
from flask import g
|
||||
from flask import url_for
|
||||
from flask import current_app
|
||||
|
||||
from app.decorators import (
|
||||
scodoc,
|
||||
@ -79,7 +76,7 @@ from app.scodoc import notesdb as ndb
|
||||
from app import log
|
||||
from app.scodoc.scolog import logdb
|
||||
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.gen_tables import GenTable
|
||||
from app.scodoc import html_sco_header
|
||||
@ -277,8 +274,9 @@ def doSignaleAbsenceGrSemestre(
|
||||
Efface les absences aux dates indiquées par dates,
|
||||
ou bien ajoute celles de abslist.
|
||||
"""
|
||||
moduleimpl_id = moduleimpl_id or None
|
||||
if etudids:
|
||||
etudids = etudids.split(",")
|
||||
etudids = [int(x) for x in str(etudids).split(",")]
|
||||
else:
|
||||
etudids = []
|
||||
if dates:
|
||||
@ -306,14 +304,14 @@ def doSignaleAbsenceGrSemestre(
|
||||
@permission_required(Permission.ScoAbsChange)
|
||||
@scodoc7func
|
||||
def SignaleAbsenceGrHebdo(
|
||||
datelundi, group_ids=[], destination="", moduleimpl_id=None, REQUEST=None
|
||||
datelundi, group_ids=[], destination="", moduleimpl_id=None, formsemestre_id=None
|
||||
):
|
||||
"Saisie hebdomadaire des absences"
|
||||
if not moduleimpl_id:
|
||||
moduleimpl_id = None
|
||||
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
group_ids, moduleimpl_id=moduleimpl_id
|
||||
group_ids, moduleimpl_id=moduleimpl_id, formsemestre_id=formsemestre_id
|
||||
)
|
||||
if not groups_infos.members:
|
||||
return (
|
||||
@ -325,7 +323,7 @@ def SignaleAbsenceGrHebdo(
|
||||
base_url = "SignaleAbsenceGrHebdo?datelundi=%s&%s&destination=%s" % (
|
||||
datelundi,
|
||||
groups_infos.groups_query_args,
|
||||
six.moves.urllib.parse.quote(destination),
|
||||
urllib.parse.quote(destination),
|
||||
)
|
||||
|
||||
formsemestre_id = groups_infos.formsemestre_id
|
||||
@ -509,7 +507,7 @@ def SignaleAbsenceGrSemestre(
|
||||
datedebut,
|
||||
datefin,
|
||||
groups_infos.groups_query_args,
|
||||
six.moves.urllib.parse.quote(destination),
|
||||
urllib.parse.quote(destination),
|
||||
)
|
||||
)
|
||||
base_url = base_url_noweeks + "&nbweeks=%s" % nbweeks # sans le moduleimpl_id
|
||||
@ -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="destination" value="%s"/>'
|
||||
% six.moves.urllib.parse.quote(destination)
|
||||
% urllib.parse.quote(destination)
|
||||
)
|
||||
#
|
||||
# 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()),
|
||||
)
|
||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
||||
return tab.make_page(format=format)
|
||||
|
||||
|
||||
@bp.route("/EtatAbsencesDate")
|
||||
@ -1101,7 +1099,7 @@ def AddBilletAbsence(
|
||||
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
|
||||
tab = _tableBillets(billets, etud=etud)
|
||||
log("AddBilletAbsence: new billet_id=%s (%gs)" % (billet_id, time.time() - t0))
|
||||
return tab.make_page(REQUEST=REQUEST, format="xml")
|
||||
return tab.make_page(format="xml")
|
||||
else:
|
||||
return billet_id
|
||||
|
||||
@ -1232,7 +1230,7 @@ def listeBilletsEtud(etudid=False, REQUEST=None, format="html"):
|
||||
cnx = ndb.GetDBConnexion()
|
||||
billets = sco_abs.billet_absence_list(cnx, {"etudid": etud["etudid"]})
|
||||
tab = _tableBillets(billets, etud=etud)
|
||||
return tab.make_page(REQUEST=REQUEST, format=format)
|
||||
return tab.make_page(format=format)
|
||||
|
||||
|
||||
@bp.route(
|
||||
@ -1479,20 +1477,24 @@ def ProcessBilletAbsenceForm(billet_id, REQUEST=None):
|
||||
def XMLgetAbsEtud(beg_date="", end_date="", REQUEST=None):
|
||||
"""returns list of absences in date interval"""
|
||||
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])$")
|
||||
if not exp.match(beg_date):
|
||||
raise ScoValueError("invalid date: %s" % beg_date)
|
||||
if not exp.match(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)
|
||||
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
|
||||
doc.append(
|
||||
ElementTree.Element(
|
||||
@ -1500,7 +1502,7 @@ def XMLgetAbsEtud(beg_date="", end_date="", REQUEST=None):
|
||||
begin=a["begin"],
|
||||
end=a["end"],
|
||||
description=a["description"],
|
||||
justified=a["estjust"],
|
||||
justified=str(int(a["estjust"])),
|
||||
)
|
||||
)
|
||||
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(),
|
||||
)
|
||||
if format != "html":
|
||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
||||
return tab.make_page(format=format)
|
||||
else:
|
||||
H = [
|
||||
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(),
|
||||
)
|
||||
if format != "html":
|
||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
||||
return tab.make_page(format=format)
|
||||
|
||||
H.append(tab.html())
|
||||
|
||||
@ -403,7 +403,7 @@ def entreprise_correspondant_list(
|
||||
preferences=context.get_preferences(),
|
||||
)
|
||||
if format != "html":
|
||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
||||
return tab.make_page(format=format)
|
||||
|
||||
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)
|
||||
doc = ElementTree.Element("formsemestrelist")
|
||||
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)
|
||||
|
||||
@ -1086,7 +1090,7 @@ def view_module_abs(REQUEST, moduleimpl_id, 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()
|
||||
|
||||
@ -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.",
|
||||
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"])
|
||||
|
@ -37,6 +37,7 @@ import flask
|
||||
from flask import abort, flash, url_for, redirect, render_template, send_file
|
||||
from flask import request
|
||||
from flask.app import Flask
|
||||
import flask_login
|
||||
from flask_login.utils import login_required
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import FileField, FileAllowed
|
||||
@ -57,6 +58,7 @@ from app.scodoc import sco_utils as scu
|
||||
from app.decorators import (
|
||||
admin_required,
|
||||
scodoc7func,
|
||||
scodoc,
|
||||
permission_required_compat_scodoc7,
|
||||
)
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
@ -131,6 +133,43 @@ def get_etud_dept():
|
||||
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
|
||||
|
||||
|
||||
|
@ -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
|
||||
@ -307,19 +279,10 @@ def showEtudLog(etudid, format="html", REQUEST=None):
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
)
|
||||
|
||||
return tab.make_page(format=format, REQUEST=REQUEST)
|
||||
return tab.make_page(format=format)
|
||||
|
||||
|
||||
# ---------- 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("/")
|
||||
|
@ -97,11 +97,12 @@ def user_info(user_name, format="json", REQUEST=None):
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoUsersAdmin)
|
||||
@scodoc7func
|
||||
def create_user_form(REQUEST, user_name=None, edit=0):
|
||||
"form. creation ou edit utilisateur"
|
||||
def create_user_form(REQUEST, user_name=None, edit=0, all_roles=1):
|
||||
"form. création ou edition utilisateur"
|
||||
auth_dept = current_user.dept
|
||||
initvalues = {}
|
||||
edit = int(edit)
|
||||
all_roles = int(all_roles)
|
||||
H = [html_sco_header.sco_header(bodyOnLoad="init_tf_form('')")]
|
||||
F = html_sco_header.sco_footer()
|
||||
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>""")
|
||||
is_super_admin = True
|
||||
|
||||
# Les rôles standards créés à l'initialisation de ScoDoc:
|
||||
standard_roles = [
|
||||
Role.get_named_role(r) for r in ("Ens", "Secr", "Admin", "RespPe")
|
||||
]
|
||||
# Rôles pouvant etre attribués aux utilisateurs via ce dialogue:
|
||||
if all_roles:
|
||||
# tous sauf SuperAdmin
|
||||
standard_roles = [
|
||||
r
|
||||
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
|
||||
# sinon, les départements dans lesquels l'utilisateur a le droit
|
||||
if is_super_admin:
|
||||
@ -209,7 +218,7 @@ def create_user_form(REQUEST, user_name=None, edit=0):
|
||||
},
|
||||
),
|
||||
(
|
||||
"passwd",
|
||||
"password",
|
||||
{
|
||||
"title": "Mot de passe",
|
||||
"input_type": "password",
|
||||
@ -219,7 +228,7 @@ def create_user_form(REQUEST, user_name=None, edit=0):
|
||||
},
|
||||
),
|
||||
(
|
||||
"passwd2",
|
||||
"password2",
|
||||
{
|
||||
"title": "Confirmer mot de passe",
|
||||
"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
|
||||
|
||||
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:
|
||||
del vals["dept"]
|
||||
if "passwd" in vals:
|
||||
del vals["passwd"]
|
||||
if "password" in vals:
|
||||
del vals["passwordd"]
|
||||
if "date_modif_passwd" in vals:
|
||||
del vals["date_modif_passwd"]
|
||||
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
|
||||
# check passwords
|
||||
if vals["passwd"]:
|
||||
if vals["passwd"] != vals["passwd2"]:
|
||||
if vals["password"]:
|
||||
if vals["password"] != vals["password2"]:
|
||||
msg = tf_error_message(
|
||||
"""Les deux mots de passes ne correspondent pas !"""
|
||||
)
|
||||
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(
|
||||
"""Mot de passe trop simple, recommencez !"""
|
||||
)
|
||||
|
@ -30,6 +30,8 @@ class Config:
|
||||
SCODOC_DIR = os.environ.get("SCODOC_DIR", "/opt/scodoc")
|
||||
SCODOC_VAR_DIR = os.environ.get("SCODOC_VAR_DIR", "/opt/scodoc-data")
|
||||
SCODOC_LOG_FILE = os.path.join(SCODOC_VAR_DIR, "log", "scodoc.log")
|
||||
# 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
|
||||
|
||||
@ -53,7 +55,7 @@ class DevConfig(Config):
|
||||
DEBUG = True
|
||||
TESTING = False
|
||||
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"
|
||||
|
||||
@ -62,7 +64,7 @@ class TestConfig(DevConfig):
|
||||
TESTING = True
|
||||
DEBUG = True
|
||||
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"
|
||||
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)
|
||||
|
||||
|
||||
1) Sur la machine origine, faire un dump complet:
|
||||
su postgres
|
||||
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:
|
||||
|
||||
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:
|
||||
su postgres
|
||||
su scodoc
|
||||
psql -l
|
||||
liste les bases: celles de ScoDoc sont SCO*
|
||||
|
||||
Pour chaque base SCO*, faire dropdb
|
||||
dropdb SCOUSERS
|
||||
dropdb SCOGEII
|
||||
dropdb SCODOC
|
||||
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
|
||||
echo dropping $f
|
||||
dropdb $f
|
||||
done
|
||||
|
||||
1.2) Charger le dump (toujours comme utilisateur postgres):
|
||||
psql -f scodoc.dump.txt postgres
|
||||
1.2) Charger le dump (toujours comme utilisateur scodoc):
|
||||
psql -f scodoc.dump.txt scodoc
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
avant de redemarrer scodoc, afin qu'il change si besoin la base de donnees:
|
||||
(en tant que root):
|
||||
cd /opt/scodoc/instance/Products/ScoDoc/config
|
||||
./upgrade.sh
|
||||
apt-get update &&
|
||||
|
||||
|
||||
----
|
||||
Cas d'une seule base à copier: (eg un seul département, mais faire
|
||||
attention aux utilisateurs definis dans la base SCOUSERS):
|
||||
Cas d'une seule base à copier (base production ou dev. par exemple)
|
||||
|
||||
En tant qu'utilisateur "postgres":
|
||||
Dump: (script avec commande de creation de la base)
|
||||
pg_dump --create SCOINFO > /tmp/scoinfo.dump
|
||||
En tant qu'utilisateur "scodoc":
|
||||
Dump: permettant de la recharger en changeant son nom
|
||||
pg_dump --format=custom --file=/tmp/SCODOC.dump SCODOC
|
||||
|
||||
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):
|
||||
createdb -E UTF-8 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
|
||||
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.0.13"
|
||||
SCOVERSION = "9.0.28"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
SCONEWS = """
|
||||
<h4>Année 2021</h4>
|
||||
<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>Évaluations de type "deuxième session"</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
|
||||
import re
|
||||
import sys
|
||||
|
||||
import click
|
||||
import flask
|
||||
from flask.cli import with_appcontext
|
||||
|
||||
from app import create_app, cli, db
|
||||
from app import initialize_scodoc_database
|
||||
from app import clear_scodoc_cache
|
||||
from app import models
|
||||
|
||||
from app.auth.models import User, Role, UserRole
|
||||
from app import models
|
||||
from app.models import ScoPreference
|
||||
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.views import notes, scolar, absences
|
||||
import tools
|
||||
|
||||
@ -45,6 +43,7 @@ def make_shell_context():
|
||||
"User": User,
|
||||
"Role": Role,
|
||||
"UserRole": UserRole,
|
||||
"Permission": Permission,
|
||||
"notes": notes,
|
||||
"scolar": scolar,
|
||||
"ndb": ndb,
|
||||
@ -142,13 +141,90 @@ def user_password(username, password=None): # user-password
|
||||
return 1
|
||||
u = User.query.filter_by(user_name=username).first()
|
||||
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
|
||||
|
||||
u.set_password(password)
|
||||
db.session.add(u)
|
||||
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()
|
||||
@ -191,7 +267,7 @@ def create_dept(dept): # create-dept
|
||||
@app.cli.command()
|
||||
@with_appcontext
|
||||
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.
|
||||
This script is typically run as unix user "scodoc".
|
||||
The original SCOUSERS database is left unmodified.
|
||||
@ -206,18 +282,40 @@ def import_scodoc7_users(): # import-scodoc7-users
|
||||
@click.argument("dept")
|
||||
@click.argument("dept_db_name")
|
||||
@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"""
|
||||
dept_db_uri = f"postgresql:///{dept_db_name}"
|
||||
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()
|
||||
@with_appcontext
|
||||
def clear_cache(): # clear-cache
|
||||
"""Clear ScoDoc cache
|
||||
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()
|
||||
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,
|
||||
gestion_compensation=None,
|
||||
bul_hide_xml=None,
|
||||
block_moyennes=None,
|
||||
gestion_semestrielle=None,
|
||||
bul_bgcolor=None,
|
||||
modalite=NotesFormModalite.DEFAULT_MODALITE,
|
||||
|
@ -6,3 +6,4 @@
|
||||
|
||||
from tools.import_scodoc7_user_db import import_scodoc7_user_db
|
||||
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
|
||||
|
||||
# usage: backup_db2 dbname
|
||||
# usage: backup_db9 dbname
|
||||
# Dump une base postgresql, et garde plusieurs dumps dans le passe
|
||||
# (configurable dans le script backup_rotation.sh)
|
||||
# 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
|
||||
# deux sauvegardes dépend de leur anciennete)
|
||||
#
|
||||
#
|
||||
# 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
|
||||
# 1- supprimer la base existante si elle existe: dropdb SCOXXX
|
||||
# Puis en tant qu'utilisateur scodoc: su scodoc
|
||||
# 1- supprimer la base existante si elle existe: dropdb SCODOC
|
||||
#
|
||||
# 2- recreer la base, vide: createdb -E UTF-8 SCOXXX
|
||||
# (nom de la base: SCOXXX ou XXX=departement)
|
||||
#
|
||||
# 3- pg_restore -d SCOXXX SCOXXX_pgdump
|
||||
# /opt/scodoc/tools/create_database.sh SCODOC
|
||||
# 3- pg_restore -d SCODOC SCODOC_pgdump
|
||||
#
|
||||
# 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
|
||||
DUMPBASE="$DBNAME"-BACKUPS
|
||||
@ -50,4 +49,4 @@ pg_dump --format=t "$DBNAME" -f $DUMPFILE
|
||||
gzip $DUMPFILE
|
||||
|
||||
# 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
|
||||
# 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
|
||||
#
|
@ -11,7 +11,7 @@
|
||||
#
|
||||
# A adapter a vos besoins. Utilisation a vos risques et perils.
|
||||
#
|
||||
# E. Viennet, 2002
|
||||
# E. Viennet, 2002, 2021
|
||||
|
||||
# Installation:
|
||||
# 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:
|
||||
SUPERVISORMAIL=emmanuel.viennet@example.com
|
||||
|
||||
CALLER=`basename $0`
|
||||
MACHINE=`hostname -s`
|
||||
CALLER=$(basename $0)
|
||||
MACHINE=$(hostname -s)
|
||||
|
||||
# -----------------------------------------------------
|
||||
|
||||
@ -40,7 +40,7 @@ MACHINE=`hostname -s`
|
||||
# ----------------------------------
|
||||
terminate()
|
||||
{
|
||||
dateTest=`date`
|
||||
dateTest=$(date)
|
||||
|
||||
mail -s "Attention: Probleme sauvegarde ScoDoc" $SUPERVISORMAIL <<EOF
|
||||
The execution of script $CALLER was not successful on $MACHINE.
|
||||
@ -57,7 +57,7 @@ EOF
|
||||
echo "Look at logfile $logfile"
|
||||
echo
|
||||
echo "$CALLER terminated, exiting now with rc=1."
|
||||
dateTest=`date`
|
||||
dateTest=$(date)
|
||||
echo "End of script at: $dateTest"
|
||||
echo ""
|
||||
|
||||
@ -74,16 +74,16 @@ EOF
|
||||
# --------------------------------------
|
||||
rsync_mirror_to_remote()
|
||||
{
|
||||
echo "--------------- mirroring " $MACHINE:$srcdir " to " $remotehost:$destdir >> $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
|
||||
echo "--------------- mirroring $MACHINE:$srcdir to $remotehost:$destdir" >> $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
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
echo Error in rsync: code=$?
|
||||
terminate
|
||||
fi
|
||||
|
||||
echo "ending at" `date` >> $logfile 2>&1
|
||||
echo "ending at $(date)" >> $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) }')
|
||||
|
||||
# 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 "Version détectée dans le source: $SCODOC_RELEASE"
|
||||
@ -89,7 +91,8 @@ fi
|
||||
# Puis on déplace les fichiers de config (nginx, systemd, ...)
|
||||
# nginx:
|
||||
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
|
||||
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"
|
||||
|
@ -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"
|
||||
|
||||
# --- 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 ]
|
||||
then
|
||||
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
|
||||
missingok
|
||||
rotate 64
|
||||
|
@ -27,6 +27,7 @@ server {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
}
|
||||
location /ScoDoc/static {
|
||||
# handle static files directly, without forwarding to the application
|
||||
@ -40,5 +41,8 @@ server {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
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_notifications",
|
||||
"itemsuivi", # etudid n'était pas une clé
|
||||
"adresse", # etudid n'était pas une clé
|
||||
"admissions", # idem
|
||||
"scolar_events",
|
||||
}:
|
||||
# tables avec "fausses" clés
|
||||
# (l'object référencé a pu disparaitre)
|
||||
|
@ -204,6 +204,9 @@ do
|
||||
systemctl restart postgresql
|
||||
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
|
||||
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
|
||||
echo "Ce script recharge les donnees de votre installation ScoDoc 7"
|
||||
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 -n "Voulez-vous poursuivre cette operation ? (y/n) [n]"
|
||||
read -r ans
|
||||
|
Loading…
Reference in New Issue
Block a user