forked from ScoDoc/ScoDoc
WIP: migration de ZNotes, decorateurs, etc.
This commit is contained in:
parent
4864fa5040
commit
369b45a8c4
@ -33,6 +33,13 @@ pour régénerer ce fichier:
|
||||
|
||||
pip freeze > requirements.txt
|
||||
|
||||
### Bidouilles temporaires
|
||||
|
||||
Installer le bon vieux `pyExcelerator` dans l'environnement:
|
||||
|
||||
(cd /tmp; tar xfz /opt/scodoc/Products/ScoDoc/config/softs/pyExcelerator-0.6.3a.patched.tgz )
|
||||
(cd /tmp/pyExcelerator-0.6.3a.patched/; python setup.py install)
|
||||
|
||||
## Lancement serveur (développement, sur VM Linux)
|
||||
|
||||
export FLASK_APP=scodoc.py
|
||||
|
17
app/__init__.py
Executable file → Normal file
17
app/__init__.py
Executable file → Normal file
@ -43,9 +43,22 @@ def create_app(config_class=Config):
|
||||
|
||||
app.register_blueprint(auth_bp, url_prefix="/auth")
|
||||
|
||||
from app.views import notes_bp
|
||||
from app.views import essais_bp
|
||||
|
||||
app.register_blueprint(notes_bp, url_prefix="/ScoDoc")
|
||||
app.register_blueprint(essais_bp, url_prefix="/Essais")
|
||||
|
||||
from app.views import scolar_bp
|
||||
from app.views import notes_bp
|
||||
from app.views import absences_bp
|
||||
|
||||
# https://scodoc.fr/ScoDoc/RT/Scolarite/...
|
||||
app.register_blueprint(scolar_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite")
|
||||
# https://scodoc.fr/ScoDoc/RT/Scolarite/Notes/...
|
||||
app.register_blueprint(notes_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Notes")
|
||||
# https://scodoc.fr/ScoDoc/RT/Scolarite/Absences/...
|
||||
app.register_blueprint(
|
||||
absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences"
|
||||
)
|
||||
|
||||
from app.main import bp as main_bp
|
||||
|
||||
|
@ -16,26 +16,6 @@ from werkzeug.exceptions import BadRequest
|
||||
from app.auth.models import Permission
|
||||
|
||||
|
||||
def permission_required(permission):
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
current_app.logger.info(
|
||||
"permission_required: %s in %s" % (permission, g.scodoc_dept)
|
||||
)
|
||||
if not current_user.has_permission(permission, g.scodoc_dept):
|
||||
abort(403)
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return decorated_function
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def admin_required(f):
|
||||
return permission_required(Permission.ScoSuperAdmin)(f)
|
||||
|
||||
|
||||
class ZUser(object):
|
||||
"Emulating Zope User"
|
||||
|
||||
@ -99,13 +79,37 @@ class ZResponse(object):
|
||||
self.headers[header.tolower()] = value
|
||||
|
||||
|
||||
def scodoc7func(func):
|
||||
def permission_required(permission):
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if "scodoc_dept" in kwargs:
|
||||
g.scodoc_dept = kwargs["scodoc_dept"]
|
||||
del kwargs["scodoc_dept"]
|
||||
current_app.logger.info(
|
||||
"permission_required: %s in %s" % (permission, g.scodoc_dept)
|
||||
)
|
||||
if not current_user.has_permission(permission, g.scodoc_dept):
|
||||
abort(403)
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return decorated_function
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def admin_required(f):
|
||||
return permission_required(Permission.ScoSuperAdmin)(f)
|
||||
|
||||
|
||||
def scodoc7func(context):
|
||||
"""Décorateur pour intégrer les fonctions Zope 2 de ScoDoc 7.
|
||||
Si on a un kwarg `scodoc_dept`(venant de la route), le stocke dans `g.scodoc_dept`.
|
||||
Ajoute l'argument REQUEST s'il est dans la signature de la fonction.
|
||||
Les paramètres de la query string deviennent des (keywords) paramètres de la fonction.
|
||||
"""
|
||||
|
||||
def s7_decorator(func):
|
||||
@wraps(func)
|
||||
def scodoc7func_decorator(*args, **kwargs):
|
||||
"""Decorator allowing legacy Zope published methods to be called via Flask
|
||||
@ -119,11 +123,11 @@ def scodoc7func(func):
|
||||
and `g.scodoc_dept` if present in the argument (for routes like `/<scodoc_dept>/Scolarite/sco_exemple`).
|
||||
"""
|
||||
assert not args
|
||||
if hasattr(g, "zrequest"):
|
||||
top_level = False
|
||||
else:
|
||||
# Détermine si on est appelé via une route ("toplevel")
|
||||
# ou par un appel de fonction python normal.
|
||||
top_level = not hasattr(g, "zrequest")
|
||||
if top_level:
|
||||
g.zrequest = None
|
||||
top_level = True
|
||||
#
|
||||
if "scodoc_dept" in kwargs:
|
||||
g.scodoc_dept = kwargs["scodoc_dept"]
|
||||
@ -147,6 +151,8 @@ def scodoc7func(func):
|
||||
for arg_name in arg_names:
|
||||
if arg_name == "REQUEST": # special case
|
||||
pos_arg_values.append(REQUEST)
|
||||
elif arg_name == "context":
|
||||
pos_arg_values.append(context)
|
||||
else:
|
||||
pos_arg_values.append(req_args[arg_name])
|
||||
current_app.logger.info("pos_arg_values=%s" % pos_arg_values)
|
||||
@ -184,6 +190,8 @@ def scodoc7func(func):
|
||||
|
||||
return scodoc7func_decorator
|
||||
|
||||
return s7_decorator
|
||||
|
||||
|
||||
# Le "context" de ScoDoc7
|
||||
class ScoDoc7Context(object):
|
||||
@ -193,3 +201,6 @@ class ScoDoc7Context(object):
|
||||
|
||||
def __init__(self, globals_dict):
|
||||
self.__dict__ = globals_dict
|
||||
|
||||
def __repr__(self):
|
||||
return "ScoDoc7Context()"
|
||||
|
@ -14,6 +14,8 @@ from app.main import bp
|
||||
|
||||
from app.decorators import scodoc7func, admin_required
|
||||
|
||||
context = None
|
||||
|
||||
|
||||
@bp.route("/")
|
||||
@bp.route("/index")
|
||||
@ -47,7 +49,7 @@ D = {"count": 0}
|
||||
|
||||
@bp.route("/zopefunction", methods=["POST", "GET"])
|
||||
@login_required
|
||||
@scodoc7func
|
||||
@scodoc7func(context)
|
||||
def a_zope_function(y, x="defaut", REQUEST=None):
|
||||
"""Une fonction typique de ScoDoc7"""
|
||||
H = get_request_infos() + [
|
||||
@ -64,7 +66,7 @@ def a_zope_function(y, x="defaut", REQUEST=None):
|
||||
|
||||
|
||||
@bp.route("/zopeform_get")
|
||||
@scodoc7func
|
||||
@scodoc7func(context)
|
||||
def a_zope_form_get(REQUEST=None):
|
||||
H = [
|
||||
"""<h2>Formulaire GET</h2>
|
||||
@ -81,7 +83,7 @@ def a_zope_form_get(REQUEST=None):
|
||||
|
||||
|
||||
@bp.route("/zopeform_post")
|
||||
@scodoc7func
|
||||
@scodoc7func(context)
|
||||
def a_zope_form_post(REQUEST=None):
|
||||
H = [
|
||||
"""<h2>Formulaire POST</h2>
|
||||
@ -98,7 +100,7 @@ def a_zope_form_post(REQUEST=None):
|
||||
|
||||
|
||||
@bp.route("/ScoDoc/<dept_id>/Scolarite/Notes/formsemestre_status")
|
||||
@scodoc7func
|
||||
@scodoc7func(context)
|
||||
def formsemestre_status(dept_id=None, formsemestre_id=None, REQUEST=None):
|
||||
"""Essai méthode de département
|
||||
Le contrôle d'accès doit vérifier les bons rôles : ici Ens<dept_id>
|
||||
|
3316
app/scodoc/ZNotes.py
3316
app/scodoc/ZNotes.py
File diff suppressed because it is too large
Load Diff
@ -416,11 +416,6 @@ REQUEST.URL0=%s<br/>
|
||||
# GESTION DE LA BD
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
security.declareProtected(ScoSuperAdmin, "GetDBConnexionString")
|
||||
|
||||
def GetDBConnexionString(self):
|
||||
# should not be published (but used from contained classes via acquisition)
|
||||
return self._db_cnx_string
|
||||
|
||||
security.declareProtected(ScoSuperAdmin, "GetDBConnexion")
|
||||
GetDBConnexion = ndb.GetDBConnexion
|
||||
@ -780,7 +775,9 @@ REQUEST.URL0=%s<br/>
|
||||
# -------------------------- INFOS SUR ETUDIANTS --------------------------
|
||||
security.declareProtected(ScoView, "getEtudInfo")
|
||||
|
||||
def getEtudInfo(self, etudid=False, code_nip=False, filled=False, REQUEST=None, format=None):
|
||||
def getEtudInfo(
|
||||
self, etudid=False, code_nip=False, filled=False, REQUEST=None, format=None
|
||||
):
|
||||
"""infos sur un etudiant pour utilisation en Zope DTML
|
||||
On peut specifier etudid
|
||||
ou bien cherche dans REQUEST.form: etudid, code_nip, code_ine
|
||||
|
@ -66,6 +66,12 @@ class FormatError(ScoValueError):
|
||||
pass
|
||||
|
||||
|
||||
class ScoConfigurationError(ScoValueError):
|
||||
"""Configuration invalid"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ScoLockedFormError(ScoException):
|
||||
def __init__(self, msg="", REQUEST=None):
|
||||
msg = (
|
||||
|
@ -49,6 +49,8 @@ class Permission:
|
||||
def init_permissions():
|
||||
for (perm, symbol, description) in _SCO_PERMISSIONS:
|
||||
setattr(Permission, symbol, perm)
|
||||
# Crée aussi les attributs dans le module (ScoDoc7 compat)
|
||||
globals()[symbol] = perm
|
||||
Permission.description[symbol] = description
|
||||
Permission.NBITS = len(_SCO_PERMISSIONS)
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1>Essais Flask pour ScoDoc 8: accueil</h1>
|
||||
<h1>Protoype ScoDoc 8: accueil</h1>
|
||||
<div class="row">
|
||||
<h2>Avec login requis</h2>
|
||||
<ul>
|
||||
|
@ -3,6 +3,9 @@
|
||||
"""
|
||||
from flask import Blueprint
|
||||
|
||||
scolar_bp = Blueprint("scolar", __name__)
|
||||
notes_bp = Blueprint("notes", __name__)
|
||||
absences_bp = Blueprint("absences", __name__)
|
||||
essais_bp = Blueprint("essais", __name__)
|
||||
|
||||
from app.views import notes
|
||||
from app.views import notes, scolar, absences
|
37
app/views/absences.py
Normal file
37
app/views/absences.py
Normal file
@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Module absences: issu de ScoDoc7 / ZAbsences.py
|
||||
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
|
||||
from flask import g
|
||||
from flask import current_app
|
||||
|
||||
from app.decorators import (
|
||||
scodoc7func,
|
||||
ScoDoc7Context,
|
||||
permission_required,
|
||||
admin_required,
|
||||
login_required,
|
||||
)
|
||||
from app.auth.models import Permission
|
||||
|
||||
from app.views import absences_bp as bp
|
||||
|
||||
context = ScoDoc7Context(globals())
|
||||
|
||||
|
||||
@bp.route("/")
|
||||
@scodoc7func(context)
|
||||
def index_html():
|
||||
"""Un exemple de fonction ScoDoc 7 dans ZAbsences"""
|
||||
return """<html>
|
||||
<body><h1>ScoDoc 8 ZAbsences !</h1>
|
||||
<p>ZAbsences ScoDoc 8</p>
|
||||
<p>g.scodoc_dept=%(scodoc_dept)s</p>
|
||||
</body>
|
||||
</html>
|
||||
""" % {
|
||||
"scodoc_dept": g.scodoc_dept,
|
||||
}
|
65
app/views/essais.py
Normal file
65
app/views/essais.py
Normal file
@ -0,0 +1,65 @@
|
||||
# -*- coding: UTF-8 -*
|
||||
"""
|
||||
Module Essais: divers essais pour la migration vers Flask
|
||||
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
|
||||
from flask import g
|
||||
from flask import current_app
|
||||
|
||||
from app.decorators import (
|
||||
scodoc7func,
|
||||
ScoDoc7Context,
|
||||
permission_required,
|
||||
admin_required,
|
||||
login_required,
|
||||
)
|
||||
from app.auth.models import Permission
|
||||
|
||||
from app.views import notes_bp as bp
|
||||
|
||||
# import sco_core deviendra:
|
||||
from app.ScoDoc import sco_core
|
||||
|
||||
context = ScoDoc7Context(globals())
|
||||
|
||||
|
||||
@bp.route("/<scodoc_dept>/Scolarite/sco_exemple")
|
||||
@scodoc7func(context)
|
||||
def sco_exemple(etudid="NON"):
|
||||
"""Un exemple de fonction ScoDoc 7"""
|
||||
return """<html>
|
||||
<body><h1>ScoDoc 7 rules !</h1>
|
||||
<p>etudid=%(etudid)s</p>
|
||||
<p>g.scodoc_dept=%(scodoc_dept)s</p>
|
||||
</body>
|
||||
</html>
|
||||
""" % {
|
||||
"etudid": etudid,
|
||||
"scodoc_dept": g.scodoc_dept,
|
||||
}
|
||||
|
||||
|
||||
# En ScoDoc 7, on a souvent des vues qui en appellent d'autres
|
||||
# avec context.sco_exemple( etudid="E12" )
|
||||
@bp.route("/<scodoc_dept>/Scolarite/sco_exemple2")
|
||||
@login_required
|
||||
@scodoc7func(context)
|
||||
def sco_exemple2():
|
||||
return "Exemple 2" + context.sco_exemple(etudid="deux")
|
||||
|
||||
|
||||
# Test avec un seul argument REQUEST positionnel
|
||||
@bp.route("/<scodoc_dept>/Scolarite/sco_get_version")
|
||||
@scodoc7func(context)
|
||||
def sco_get_version(REQUEST):
|
||||
return sco_core.sco_get_version(REQUEST)
|
||||
|
||||
|
||||
# Fonction ressemblant à une méthode Zope protégée
|
||||
@bp.route("/<scodoc_dept>/Scolarite/sco_test_view")
|
||||
@scodoc7func(context)
|
||||
@permission_required(Permission.ScoView)
|
||||
def sco_test_view(REQUEST=None):
|
||||
return """Vous avez vu sco_test_view !"""
|
3548
app/views/notes.py
3548
app/views/notes.py
File diff suppressed because it is too large
Load Diff
37
app/views/scolar.py
Normal file
37
app/views/scolar.py
Normal file
@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Module scolar: issu de ScoDoc7 / ZScolar.py
|
||||
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
|
||||
from flask import g
|
||||
from flask import current_app
|
||||
|
||||
from app.decorators import (
|
||||
scodoc7func,
|
||||
ScoDoc7Context,
|
||||
permission_required,
|
||||
admin_required,
|
||||
login_required,
|
||||
)
|
||||
from app.auth.models import Permission
|
||||
|
||||
from app.views import scolar_bp as bp
|
||||
|
||||
context = ScoDoc7Context(globals())
|
||||
|
||||
|
||||
@bp.route("/about")
|
||||
@scodoc7func(context)
|
||||
def about():
|
||||
"""Un exemple de fonction ScoDoc 7 dans ZScolar"""
|
||||
return """<html>
|
||||
<body><h1>ScoDoc 7 ZScolar !</h1>
|
||||
<p>ZScolar ScoDoc 8</p>
|
||||
<p>g.scodoc_dept=%(scodoc_dept)s</p>
|
||||
</body>
|
||||
</html>
|
||||
""" % {
|
||||
"scodoc_dept": g.scodoc_dept,
|
||||
}
|
18
config.py
Normal file → Executable file
18
config.py
Normal file → Executable file
@ -7,12 +7,13 @@ BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
||||
load_dotenv(os.path.join(BASEDIR, ".env"))
|
||||
|
||||
|
||||
class Config(object):
|
||||
"""General configution. Mostly loaded from environment via .env"""
|
||||
class ConfigClass(object):
|
||||
"""General configuration. Mostly loaded from environment via .env"""
|
||||
|
||||
SECRET_KEY = os.environ.get("SECRET_KEY") or "un-grand-secret-introuvable"
|
||||
SQLALCHEMY_DATABASE_URI = (
|
||||
os.environ.get("DATABASE_URL") or "postgresql://scodoc@localhost:5432/SCO8USERS"
|
||||
os.environ.get("USERS_DATABASE_URI")
|
||||
or "postgresql://scodoc@localhost:5432/SCO8USERS"
|
||||
)
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
LOG_TO_STDOUT = os.environ.get("LOG_TO_STDOUT")
|
||||
@ -29,3 +30,14 @@ class Config(object):
|
||||
BOOTSTRAP_SERVE_LOCAL = os.environ.get("BOOTSTRAP_SERVE_LOCAL")
|
||||
# for ScoDoc 7 compat (à changer)
|
||||
INSTANCE_HOME = os.environ.get("INSTANCE_HOME", "/opt/scodoc")
|
||||
|
||||
# For legacy ScoDoc7 installs: postgresql user
|
||||
SCODOC7_SQL_USER = os.environ.get("SCODOC7_SQL_USER", "www-data")
|
||||
DEFAULT_SQL_PORT = os.environ.get("DEFAULT_SQL_PORT", "5432")
|
||||
|
||||
def __init__(self):
|
||||
"""Used to build some config variable at startup time"""
|
||||
self.SCODOC_VAR_DIR = os.path.join(self.INSTANCE_HOME, "var", "scodoc")
|
||||
|
||||
|
||||
Config = ConfigClass()
|
||||
|
@ -15,6 +15,7 @@ Flask-Migrate==2.7.0
|
||||
Flask-Moment==0.11.0
|
||||
Flask-SQLAlchemy==2.4.4
|
||||
Flask-WTF==0.14.3
|
||||
icalendar==4.0.7
|
||||
idna==2.10
|
||||
itsdangerous==1.1.0
|
||||
jaxml==3.2
|
||||
@ -24,13 +25,17 @@ MarkupSafe==1.1.1
|
||||
Pillow==6.2.2
|
||||
pkg-resources==0.0.0
|
||||
psycopg2==2.8.6
|
||||
pyExcelerator==0.6.3a0
|
||||
PyJWT==1.7.1
|
||||
PyRSS2Gen==1.1
|
||||
python-dateutil==2.8.1
|
||||
python-dotenv==0.15.0
|
||||
python-editor==1.0.4
|
||||
pytz==2021.1
|
||||
reportlab==3.5.59
|
||||
six==1.15.0
|
||||
SQLAlchemy==1.3.23
|
||||
stripogram==1.5
|
||||
typing==3.7.4.3
|
||||
visitor==0.1.3
|
||||
Werkzeug==1.0.1
|
||||
|
70
scodoc_manager.py
Normal file
70
scodoc_manager.py
Normal file
@ -0,0 +1,70 @@
|
||||
# -*- coding: UTF-8 -*
|
||||
|
||||
"""
|
||||
Manage departments, databases.
|
||||
|
||||
Each departement `X` has its own database, `SCOX`
|
||||
and a small configuration file `.../config/depts/X.cfg`
|
||||
containing the database URI
|
||||
Old ScoDoc7 installs config files contained `dbname=SCOX`
|
||||
which translates as
|
||||
"postgresql://<user>@localhost:5432/<dbname>"
|
||||
<user> being given by `SCODOC7_SQL_USER` env variable.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
|
||||
from config import Config
|
||||
|
||||
from app.scodoc.sco_exceptions import ScoConfigurationError
|
||||
|
||||
|
||||
class ScoDeptDescription:
|
||||
def __init__(self, filename):
|
||||
"""Read dept description from dept file"""
|
||||
if os.path.split(filename)[1][-4:] != ".cfg":
|
||||
raise ScoConfigurationError("Invalid dept config filename: %s" % filename)
|
||||
self.dept_id = os.path.split(filename)[1][:-4]
|
||||
if not self.dept_id:
|
||||
raise ScoConfigurationError("Invalid dept config filename: %s" % filename)
|
||||
try:
|
||||
db_uri = open(filename).read().strip()
|
||||
except:
|
||||
raise ScoConfigurationError("Department config file missing: %s" % filename)
|
||||
m = re.match(r"dbname=SCO([a-zA-Z0-9]+$)", db_uri)
|
||||
if m:
|
||||
# ScoDoc7 backward compat
|
||||
dept = m.group(1) # unused in ScoDoc7
|
||||
db_name = "SCO" + self.dept_id.upper()
|
||||
db_uri = "postgresql://%(db_user)s@localhost:%(db_port)s/%(db_name)s" % {
|
||||
"db_user": Config.SCODOC7_SQL_USER,
|
||||
"db_name": db_name,
|
||||
"db_port": Config.DEFAULT_SQL_PORT,
|
||||
}
|
||||
self.db_uri = db_uri
|
||||
|
||||
|
||||
class ScoDocManager:
|
||||
def __init__(self):
|
||||
filenames = glob.glob(Config.SCODOC_VAR_DIR + "/config/depts/*.cfg")
|
||||
descr_list = [ScoDeptDescription(f) for f in filenames]
|
||||
self.dept_descriptions = {d.dept_id: d for d in descr_list}
|
||||
|
||||
def get_dept_db_uri(self, dept_id):
|
||||
"DB URI for this dept id"
|
||||
return self.dept_descriptions[dept_id].db_uri
|
||||
|
||||
def get_dept_ids(self):
|
||||
"get (unsorted) dept ids"
|
||||
return [d.dept_id for d in descr_list]
|
||||
|
||||
def get_db_uri(self):
|
||||
"""
|
||||
Returns DB URI for the "current" departement.
|
||||
Replaces ScoDoc7 GetDBConnexionString()
|
||||
"""
|
||||
return self.get_dept_db_uri(g.scodoc_dept)
|
||||
|
||||
|
||||
sco_mgr = ScoDocManager()
|
Loading…
Reference in New Issue
Block a user