Merge pull request 'master' (#2) from ScoDoc/ScoDoc:master into master
Reviewed-on: https://scodoc.org/git/lehmann/ScoDoc-Front/pulls/2
This commit is contained in:
commit
e0edde3f46
@ -1,6 +1,7 @@
|
||||
# -*- coding: UTF-8 -*
|
||||
# Authentication code borrowed from Miguel Grinberg's Mega Tutorial
|
||||
# (see https://github.com/miguelgrinberg/microblog)
|
||||
# and modified for ScoDoc
|
||||
|
||||
# Under The MIT License (MIT)
|
||||
|
||||
@ -23,6 +24,7 @@
|
||||
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from flask import g
|
||||
from flask_httpauth import HTTPBasicAuth, HTTPTokenAuth
|
||||
from app.auth.models import User
|
||||
from app.api.errors import error_response
|
||||
@ -35,6 +37,7 @@ token_auth = HTTPTokenAuth()
|
||||
def verify_password(username, password):
|
||||
user = User.query.filter_by(user_name=username).first()
|
||||
if user and user.check_password(password):
|
||||
g.current_user = user
|
||||
return user
|
||||
|
||||
|
||||
@ -45,7 +48,9 @@ def basic_auth_error(status):
|
||||
|
||||
@token_auth.verify_token
|
||||
def verify_token(token):
|
||||
return User.check_token(token) if token else None
|
||||
user = User.check_token(token) if token else None
|
||||
g.current_user = user
|
||||
return user
|
||||
|
||||
|
||||
@token_auth.error_handler
|
||||
@ -53,15 +58,20 @@ def token_auth_error(status):
|
||||
return error_response(status)
|
||||
|
||||
|
||||
def token_permission_required(permission):
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
scodoc_dept = getattr(g, "scodoc_dept", None)
|
||||
if not current_user.has_permission(permission, scodoc_dept):
|
||||
abort(403)
|
||||
return f(*args, **kwargs)
|
||||
@token_auth.get_user_roles
|
||||
def get_user_roles(user):
|
||||
return user.roles
|
||||
|
||||
return login_required(decorated_function)
|
||||
|
||||
return decorator
|
||||
# def token_permission_required(permission):
|
||||
# def decorator(f):
|
||||
# @wraps(f)
|
||||
# def decorated_function(*args, **kwargs):
|
||||
# scodoc_dept = getattr(g, "scodoc_dept", None)
|
||||
# if not current_user.has_permission(permission, scodoc_dept):
|
||||
# abort(403)
|
||||
# return f(*args, **kwargs)
|
||||
|
||||
# return login_required(decorated_function)
|
||||
|
||||
# return decorator
|
||||
|
@ -39,13 +39,18 @@
|
||||
# Scolarite/Notes/moduleimpl_status
|
||||
# Scolarite/setGroups
|
||||
|
||||
from flask import jsonify, request, url_for, abort
|
||||
from app import db
|
||||
from flask import jsonify, request, url_for, abort, g
|
||||
from flask_login import current_user
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app import db, log
|
||||
from app.api import bp
|
||||
from app.api.auth import token_auth
|
||||
from app.api.errors import bad_request
|
||||
|
||||
from app.api.errors import bad_request, error_response
|
||||
from app.decorators import permission_required
|
||||
from app import models
|
||||
from app.models import FormSemestre, FormSemestreInscription, Identite
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("list_depts", methods=["GET"])
|
||||
@ -54,3 +59,23 @@ def list_depts():
|
||||
depts = models.Departement.query.filter_by(visible=True).all()
|
||||
data = [d.to_dict() for d in depts]
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@bp.route("/etudiants/courant", methods=["GET"])
|
||||
@token_auth.login_required
|
||||
def etudiants():
|
||||
"""Liste de tous les étudiants actuellement inscrits à un semestre
|
||||
en cours.
|
||||
"""
|
||||
# Vérification de l'accès: permission Observateir sur tous les départements
|
||||
# (c'est un exemple à compléter)
|
||||
if not g.current_user.has_permission(Permission.ScoObservateur, None):
|
||||
return error_response(401, message="accès interdit")
|
||||
|
||||
query = db.session.query(Identite).filter(
|
||||
FormSemestreInscription.formsemestre_id == FormSemestre.id,
|
||||
FormSemestreInscription.etudid == Identite.id,
|
||||
FormSemestre.date_debut <= func.now(),
|
||||
FormSemestre.date_fin >= func.now(),
|
||||
)
|
||||
return jsonify([e.to_dict_bul(include_photo=False) for e in query])
|
||||
|
@ -65,7 +65,7 @@ class User(UserMixin, db.Model):
|
||||
date_created = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
date_expiration = db.Column(db.DateTime, default=None)
|
||||
passwd_temp = db.Column(db.Boolean, default=False)
|
||||
token = db.Column(db.String(32), index=True, unique=True)
|
||||
token = db.Column(db.Text(), index=True, unique=True)
|
||||
token_expiration = db.Column(db.DateTime)
|
||||
|
||||
roles = db.relationship("Role", secondary="user_role", viewonly=True)
|
||||
@ -272,7 +272,7 @@ class User(UserMixin, db.Model):
|
||||
"""string repr. of user's roles (with depts)
|
||||
e.g. "Ens_RT, Ens_Info, Secr_CJ"
|
||||
"""
|
||||
return ",".join(f"{r.role.name}_{r.dept or ''}" for r in self.user_roles)
|
||||
return ",".join(f"{r.role.name or ''}_{r.dept or ''}" for r in self.user_roles)
|
||||
|
||||
def is_administrator(self):
|
||||
"True if i'm an active SuperAdmin"
|
||||
|
@ -25,7 +25,6 @@ class ResultatsSemestreBUT:
|
||||
"""Structure légère pour stocker les résultats du semestre et
|
||||
générer les bulletins.
|
||||
__init__ : charge depuis le cache ou calcule
|
||||
invalidate(): invalide données cachées
|
||||
"""
|
||||
|
||||
_cached_attrs = (
|
||||
|
@ -35,12 +35,12 @@ from app.models.modules import Module, ModuleUECoef, NotesTag, notes_modules_tag
|
||||
from app.models.ues import UniteEns
|
||||
from app.models.formsemestre import (
|
||||
FormSemestre,
|
||||
FormsemestreEtape,
|
||||
FormSemestreEtape,
|
||||
FormationModalite,
|
||||
FormsemestreUECoef,
|
||||
FormsemestreUEComputationExpr,
|
||||
FormsemestreCustomMenu,
|
||||
FormsemestreInscription,
|
||||
FormSemestreUECoef,
|
||||
FormSemestreUEComputationExpr,
|
||||
FormSemestreCustomMenu,
|
||||
FormSemestreInscription,
|
||||
notes_formsemestre_responsables,
|
||||
NotesSemSet,
|
||||
notes_semset_formsemestre,
|
||||
@ -57,7 +57,7 @@ from app.models.evaluations import (
|
||||
from app.models.groups import Partition, GroupDescr, group_membership
|
||||
from app.models.notes import (
|
||||
ScolarEvent,
|
||||
ScolarFormsemestreValidation,
|
||||
ScolarFormSemestreValidation,
|
||||
ScolarAutorisationInscription,
|
||||
BulAppreciations,
|
||||
NotesNotes,
|
||||
|
@ -9,7 +9,6 @@ from app import models
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models import CODE_STR_LEN
|
||||
from app.scodoc import sco_photos
|
||||
|
||||
|
||||
class Identite(db.Model):
|
||||
@ -71,12 +70,14 @@ class Identite(db.Model):
|
||||
"le mail associé à la première adrese de l'étudiant, ou None"
|
||||
return self.adresses[0].email or None if self.adresses.count() > 0 else None
|
||||
|
||||
def to_dict_bul(self):
|
||||
def to_dict_bul(self, include_photo=True):
|
||||
"""Infos exportées dans les bulletins"""
|
||||
return {
|
||||
from app.scodoc import sco_photos
|
||||
|
||||
d = {
|
||||
"civilite": self.civilite,
|
||||
"code_ine": self.code_nip,
|
||||
"code_nip": self.code_ine,
|
||||
"code_ine": self.code_ine,
|
||||
"code_nip": self.code_nip,
|
||||
"date_naissance": self.date_naissance.isoformat()
|
||||
if self.date_naissance
|
||||
else None,
|
||||
@ -84,9 +85,11 @@ class Identite(db.Model):
|
||||
"emailperso": self.get_first_email("emailperso"),
|
||||
"etudid": self.id,
|
||||
"nom": self.nom_disp(),
|
||||
"photo_url": sco_photos.get_etud_photo_url(self.id),
|
||||
"prenom": self.prenom,
|
||||
}
|
||||
if include_photo:
|
||||
d["photo_url"] = (sco_photos.get_etud_photo_url(self.id),)
|
||||
return d
|
||||
|
||||
def inscription_courante(self):
|
||||
"""La première inscription à un formsemestre _actuellement_ en cours.
|
||||
@ -104,7 +107,7 @@ class Identite(db.Model):
|
||||
False si pas inscrit, ou scu.INSCRIT, DEMISSION, DEF
|
||||
"""
|
||||
# voir si ce n'est pas trop lent:
|
||||
ins = models.FormsemestreInscription.query.filter_by(
|
||||
ins = models.FormSemestreInscription.query.filter_by(
|
||||
etudid=self.id, formsemestre_id=formsemestre_id
|
||||
).first()
|
||||
if ins:
|
||||
|
@ -82,7 +82,7 @@ class FormSemestre(db.Model):
|
||||
|
||||
# Relations:
|
||||
etapes = db.relationship(
|
||||
"FormsemestreEtape", cascade="all,delete", backref="formsemestre"
|
||||
"FormSemestreEtape", cascade="all,delete", backref="formsemestre"
|
||||
)
|
||||
modimpls = db.relationship("ModuleImpl", backref="formsemestre", lazy="dynamic")
|
||||
etuds = db.relationship(
|
||||
@ -119,7 +119,7 @@ class FormSemestre(db.Model):
|
||||
return d
|
||||
|
||||
def query_ues(self, with_sport=False) -> flask_sqlalchemy.BaseQuery:
|
||||
"""UE des modules de ce semestre.
|
||||
"""UE des modules de ce semestre, triées par numéro.
|
||||
- Formations classiques: les UEs auxquelles appartiennent
|
||||
les modules mis en place dans ce semestre.
|
||||
- Formations APC / BUT: les UEs de la formation qui ont
|
||||
@ -262,7 +262,7 @@ notes_formsemestre_responsables = db.Table(
|
||||
)
|
||||
|
||||
|
||||
class FormsemestreEtape(db.Model):
|
||||
class FormSemestreEtape(db.Model):
|
||||
"""Étape Apogée associées au semestre"""
|
||||
|
||||
__tablename__ = "notes_formsemestre_etapes"
|
||||
@ -331,7 +331,7 @@ class FormationModalite(db.Model):
|
||||
raise
|
||||
|
||||
|
||||
class FormsemestreUECoef(db.Model):
|
||||
class FormSemestreUECoef(db.Model):
|
||||
"""Coef des UE capitalisees arrivant dans ce semestre"""
|
||||
|
||||
__tablename__ = "notes_formsemestre_uecoef"
|
||||
@ -350,7 +350,7 @@ class FormsemestreUECoef(db.Model):
|
||||
coefficient = db.Column(db.Float, nullable=False)
|
||||
|
||||
|
||||
class FormsemestreUEComputationExpr(db.Model):
|
||||
class FormSemestreUEComputationExpr(db.Model):
|
||||
"""Formules utilisateurs pour calcul moyenne UE"""
|
||||
|
||||
__tablename__ = "notes_formsemestre_ue_computation_expr"
|
||||
@ -370,7 +370,7 @@ class FormsemestreUEComputationExpr(db.Model):
|
||||
computation_expr = db.Column(db.Text())
|
||||
|
||||
|
||||
class FormsemestreCustomMenu(db.Model):
|
||||
class FormSemestreCustomMenu(db.Model):
|
||||
"""Menu custom associe au semestre"""
|
||||
|
||||
__tablename__ = "notes_formsemestre_custommenu"
|
||||
@ -386,7 +386,7 @@ class FormsemestreCustomMenu(db.Model):
|
||||
idx = db.Column(db.Integer, default=0, server_default="0") # rang dans le menu
|
||||
|
||||
|
||||
class FormsemestreInscription(db.Model):
|
||||
class FormSemestreInscription(db.Model):
|
||||
"""Inscription à un semestre de formation"""
|
||||
|
||||
__tablename__ = "notes_formsemestre_inscription"
|
||||
@ -410,7 +410,7 @@ class FormsemestreInscription(db.Model):
|
||||
backref=db.backref(
|
||||
"inscriptions",
|
||||
cascade="all, delete-orphan",
|
||||
order_by="FormsemestreInscription.etudid",
|
||||
order_by="FormSemestreInscription.etudid",
|
||||
),
|
||||
)
|
||||
# I inscrit, D demission en cours de semestre, DEF si "defaillant"
|
||||
|
@ -40,7 +40,7 @@ class ScolarEvent(db.Model):
|
||||
)
|
||||
|
||||
|
||||
class ScolarFormsemestreValidation(db.Model):
|
||||
class ScolarFormSemestreValidation(db.Model):
|
||||
"""Décisions de jury"""
|
||||
|
||||
__tablename__ = "scolar_formsemestre_validation"
|
||||
|
@ -32,7 +32,7 @@ from flask_login import current_user
|
||||
|
||||
from app import db
|
||||
from app.models import Formation, UniteEns, Matiere, Module, FormSemestre, ModuleImpl
|
||||
from app.models.notes import ScolarFormsemestreValidation
|
||||
from app.models.notes import ScolarFormSemestreValidation
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
@ -152,7 +152,7 @@ def html_ue_infos(ue):
|
||||
)
|
||||
.all()
|
||||
)
|
||||
nb_etuds_valid_ue = ScolarFormsemestreValidation.query.filter_by(
|
||||
nb_etuds_valid_ue = ScolarFormSemestreValidation.query.filter_by(
|
||||
ue_id=ue.id
|
||||
).count()
|
||||
can_safely_be_suppressed = (
|
||||
|
@ -56,7 +56,7 @@ from app.scodoc.htmlutils import histogram_notes
|
||||
|
||||
def do_evaluation_listenotes(
|
||||
evaluation_id=None, moduleimpl_id=None, format="html"
|
||||
) -> str:
|
||||
) -> tuple[str, str]:
|
||||
"""
|
||||
Affichage des notes d'une évaluation (si evaluation_id)
|
||||
ou de toutes les évaluations d'un module (si moduleimpl_id)
|
||||
@ -71,7 +71,7 @@ def do_evaluation_listenotes(
|
||||
else:
|
||||
raise ValueError("missing argument: evaluation or module")
|
||||
if not evals:
|
||||
return "<p>Aucune évaluation !</p>"
|
||||
return "<p>Aucune évaluation !</p>", f"ScoDoc"
|
||||
|
||||
E = evals[0] # il y a au moins une evaluation
|
||||
modimpl = ModuleImpl.query.get(E["moduleimpl_id"])
|
||||
@ -189,9 +189,12 @@ def do_evaluation_listenotes(
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + "\n" + tf[1], page_title
|
||||
elif tf[0] == -1:
|
||||
return flask.redirect(
|
||||
"%s/Notes/moduleimpl_status?moduleimpl_id=%s"
|
||||
% (scu.ScoURL(), E["moduleimpl_id"])
|
||||
return (
|
||||
flask.redirect(
|
||||
"%s/Notes/moduleimpl_status?moduleimpl_id=%s"
|
||||
% (scu.ScoURL(), E["moduleimpl_id"])
|
||||
),
|
||||
"",
|
||||
)
|
||||
else:
|
||||
anonymous_listing = tf[2]["anonymous_listing"]
|
||||
|
@ -42,9 +42,6 @@ Les images sont servies par ScoDoc, via la méthode getphotofile?etudid=xxx
|
||||
- support for legacy ZODB removed in v1909.
|
||||
|
||||
"""
|
||||
|
||||
from flask.helpers import make_response, url_for
|
||||
from app.scodoc.sco_exceptions import ScoGenError
|
||||
import datetime
|
||||
import glob
|
||||
import io
|
||||
@ -52,24 +49,26 @@ import os
|
||||
import random
|
||||
import requests
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import PIL
|
||||
from PIL import Image as PILImage
|
||||
|
||||
from flask import request, g
|
||||
from flask.helpers import make_response, url_for
|
||||
|
||||
from config import Config
|
||||
|
||||
from app import log
|
||||
from app import db
|
||||
from app.models import Identite
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_portal_apogee
|
||||
from app.scodoc import sco_preferences
|
||||
from app import log
|
||||
from app.scodoc.sco_exceptions import ScoGenError
|
||||
from app.scodoc.scolog import logdb
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from config import Config
|
||||
|
||||
# Full paths on server's filesystem. Something like "/opt/scodoc/var/scodoc/photos"
|
||||
# Full paths on server's filesystem. Something like "/opt/scodoc-data/photos"
|
||||
PHOTO_DIR = os.path.join(Config.SCODOC_VAR_DIR, "photos")
|
||||
ICONS_DIR = os.path.join(Config.SCODOC_DIR, "app", "static", "icons")
|
||||
UNKNOWN_IMAGE_PATH = os.path.join(ICONS_DIR, "unknown.jpg")
|
||||
@ -97,14 +96,15 @@ def get_etud_photo_url(etudid, size="small"):
|
||||
)
|
||||
|
||||
|
||||
def etud_photo_url(etud, size="small", fast=False):
|
||||
def etud_photo_url(etud: dict, size="small", fast=False) -> str:
|
||||
"""url to the image of the student, in "small" size or "orig" size.
|
||||
If ScoDoc doesn't have an image and a portal is configured, link to it.
|
||||
|
||||
"""
|
||||
photo_url = get_etud_photo_url(etud["etudid"], size=size)
|
||||
if fast:
|
||||
return photo_url
|
||||
path = photo_pathname(etud, size=size)
|
||||
path = photo_pathname(etud["photo_filename"], size=size)
|
||||
if not path:
|
||||
# Portail ?
|
||||
ext_url = photo_portal_url(etud)
|
||||
@ -131,8 +131,8 @@ def get_photo_image(etudid=None, size="small"):
|
||||
if not etudid:
|
||||
filename = UNKNOWN_IMAGE_PATH
|
||||
else:
|
||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||
filename = photo_pathname(etud, size=size)
|
||||
etud = Identite.query.get_or_404(etudid)
|
||||
filename = photo_pathname(etud.photo_filename, size=size)
|
||||
if not filename:
|
||||
filename = UNKNOWN_IMAGE_PATH
|
||||
return _http_jpeg_file(filename)
|
||||
@ -171,8 +171,8 @@ def _http_jpeg_file(filename):
|
||||
return response
|
||||
|
||||
|
||||
def etud_photo_is_local(etud, size="small"):
|
||||
return photo_pathname(etud, size=size)
|
||||
def etud_photo_is_local(etud: dict, size="small"):
|
||||
return photo_pathname(etud["photo_filename"], size=size)
|
||||
|
||||
|
||||
def etud_photo_html(etud=None, etudid=None, title=None, size="small"):
|
||||
@ -215,9 +215,12 @@ def etud_photo_orig_html(etud=None, etudid=None, title=None):
|
||||
return etud_photo_html(etud=etud, etudid=etudid, title=title, size="orig")
|
||||
|
||||
|
||||
def photo_pathname(etud, size="orig"):
|
||||
"""Returns full path of image file if etud has a photo (in the filesystem), or False.
|
||||
def photo_pathname(photo_filename: str, size="orig"):
|
||||
"""Returns full path of image file if etud has a photo (in the filesystem),
|
||||
or False.
|
||||
Do not distinguish the cases: no photo, or file missing.
|
||||
Argument: photo_filename (Identite attribute)
|
||||
Resultat: False or str
|
||||
"""
|
||||
if size == "small":
|
||||
version = H90
|
||||
@ -225,9 +228,9 @@ def photo_pathname(etud, size="orig"):
|
||||
version = ""
|
||||
else:
|
||||
raise ValueError("invalid size parameter for photo")
|
||||
if not etud["photo_filename"]:
|
||||
if not photo_filename:
|
||||
return False
|
||||
path = os.path.join(PHOTO_DIR, etud["photo_filename"]) + version + IMAGE_EXT
|
||||
path = os.path.join(PHOTO_DIR, photo_filename) + version + IMAGE_EXT
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
else:
|
||||
@ -264,15 +267,14 @@ def store_photo(etud, data):
|
||||
return 1, "ok"
|
||||
|
||||
|
||||
def suppress_photo(etud):
|
||||
def suppress_photo(etud: Identite) -> None:
|
||||
"""Suppress a photo"""
|
||||
log("suppress_photo etudid=%s" % etud["etudid"])
|
||||
rel_path = photo_pathname(etud)
|
||||
log("suppress_photo etudid=%s" % etud.id)
|
||||
rel_path = photo_pathname(etud.photo_filename)
|
||||
# 1- remove ref. from database
|
||||
etud["photo_filename"] = None
|
||||
cnx = ndb.GetDBConnexion()
|
||||
sco_etud.identite_edit_nocheck(cnx, etud)
|
||||
cnx.commit()
|
||||
etud.photo_filename = None
|
||||
db.session.add(etud)
|
||||
|
||||
# 2- erase images files
|
||||
if rel_path:
|
||||
# remove extension and glob
|
||||
@ -281,8 +283,10 @@ def suppress_photo(etud):
|
||||
for filename in filenames:
|
||||
log("removing file %s" % filename)
|
||||
os.remove(filename)
|
||||
db.session.commit()
|
||||
# 3- log
|
||||
logdb(cnx, method="changePhoto", msg="suppression", etudid=etud["etudid"])
|
||||
cnx = ndb.GetDBConnexion()
|
||||
logdb(cnx, method="changePhoto", msg="suppression", etudid=etud.id)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@ -373,6 +377,9 @@ def copy_portal_photo_to_fs(etud):
|
||||
log("copy_portal_photo_to_fs: failure (exception in store_photo)!")
|
||||
if status == 1:
|
||||
log("copy_portal_photo_to_fs: copied %s" % url)
|
||||
return photo_pathname(etud), "%s: photo chargée" % etud["nomprenom"]
|
||||
return (
|
||||
photo_pathname(etud["photo_filename"]),
|
||||
f"{etud['nomprenom']}: photo chargée",
|
||||
)
|
||||
else:
|
||||
return None, "%s: <b>%s</b>" % (etud["nomprenom"], diag)
|
||||
|
@ -183,10 +183,11 @@ def trombino_html(groups_infos):
|
||||
|
||||
|
||||
def check_local_photos_availability(groups_infos, format=""):
|
||||
"""Verifie que toutes les photos (des gropupes indiqués) sont copiées localement
|
||||
dans ScoDoc (seules les photos dont nous disposons localement peuvent être exportées
|
||||
en pdf ou en zip).
|
||||
Si toutes ne sont pas dispo, retourne un dialogue d'avertissement pour l'utilisateur.
|
||||
"""Vérifie que toutes les photos (des groupes indiqués) sont copiées
|
||||
localement dans ScoDoc (seules les photos dont nous disposons localement
|
||||
peuvent être exportées en pdf ou en zip).
|
||||
Si toutes ne sont pas dispo, retourne un dialogue d'avertissement
|
||||
pour l'utilisateur.
|
||||
"""
|
||||
nb_missing = 0
|
||||
for t in groups_infos.members:
|
||||
@ -221,7 +222,7 @@ def _trombino_zip(groups_infos):
|
||||
# assume we have the photos (or the user acknowledged the fact)
|
||||
# Archive originals (not reduced) images, in JPEG
|
||||
for t in groups_infos.members:
|
||||
im_path = sco_photos.photo_pathname(t, size="orig")
|
||||
im_path = sco_photos.photo_pathname(t["photo_filename"], size="orig")
|
||||
if not im_path:
|
||||
continue
|
||||
img = open(im_path, "rb").read()
|
||||
@ -292,9 +293,9 @@ def trombino_copy_photos(group_ids=[], dialog_confirmed=False):
|
||||
|
||||
|
||||
def _get_etud_platypus_image(t, image_width=2 * cm):
|
||||
"""Returns aplatypus object for the photo of student t"""
|
||||
"""Returns a platypus object for the photo of student t"""
|
||||
try:
|
||||
path = sco_photos.photo_pathname(t, size="small")
|
||||
path = sco_photos.photo_pathname(t["photo_filename"], size="small")
|
||||
if not path:
|
||||
# log('> unknown')
|
||||
path = sco_photos.UNKNOWN_IMAGE_PATH
|
||||
|
@ -72,7 +72,7 @@ NOTES_SUPPRESS = -1001.0 # note a supprimer
|
||||
NOTES_ATTENTE = -1002.0 # note "en attente" (se calcule comme une note neutralisee)
|
||||
|
||||
# ---- CODES INSCRIPTION AUX SEMESTRES
|
||||
# (champ etat de FormsemestreInscription)
|
||||
# (champ etat de FormSemestreInscription)
|
||||
INSCRIT = "I"
|
||||
DEMISSION = "D"
|
||||
DEF = "DEF"
|
||||
|
@ -9,7 +9,7 @@
|
||||
<releve-but></releve-but>
|
||||
<script src="/ScoDoc/static/js/releve-but.js"></script>
|
||||
<script>
|
||||
let dataSrc = "{{bul_url|safe}}";
|
||||
let dataSrc = "{{bul_url|safe}}";
|
||||
fetch(dataSrc)
|
||||
.then(r => { return r.json() })
|
||||
.then(json => {
|
||||
@ -22,10 +22,9 @@
|
||||
.dateInscription,
|
||||
.numerosEtudiant,
|
||||
.dateNaissance{
|
||||
/*display: none;*/
|
||||
display: none;
|
||||
}`;
|
||||
releve.shadowRoot.appendChild(style);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
@ -1739,7 +1739,7 @@ def evaluation_listenotes():
|
||||
mode = "module"
|
||||
|
||||
format = vals.get("format", "html")
|
||||
B, page_title = sco_liste_notes.do_evaluation_listenotes(
|
||||
html_content, page_title = sco_liste_notes.do_evaluation_listenotes(
|
||||
evaluation_id=evaluation_id, moduleimpl_id=moduleimpl_id, format=format
|
||||
)
|
||||
if format == "html":
|
||||
@ -1750,9 +1750,9 @@ def evaluation_listenotes():
|
||||
init_qtip=True,
|
||||
)
|
||||
F = html_sco_header.sco_footer()
|
||||
return H + B + F
|
||||
return H + html_content + F
|
||||
else:
|
||||
return B
|
||||
return html_content
|
||||
|
||||
|
||||
sco_publish(
|
||||
|
@ -54,7 +54,7 @@ from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
|
||||
|
||||
import app
|
||||
from app.models import Departement, Identite
|
||||
from app.models import FormSemestre, FormsemestreInscription
|
||||
from app.models import FormSemestre, FormSemestreInscription
|
||||
from app.models import ScoDocSiteConfig
|
||||
import sco_version
|
||||
from app.scodoc import sco_logos, sco_config_form
|
||||
|
@ -49,6 +49,7 @@ from app.decorators import (
|
||||
admin_required,
|
||||
login_required,
|
||||
)
|
||||
from app.models.etudiants import Identite
|
||||
|
||||
from app.views import scolar_bp as bp
|
||||
|
||||
@ -944,21 +945,21 @@ def formChangePhoto(etudid=None):
|
||||
@scodoc7func
|
||||
def formSuppressPhoto(etudid=None, dialog_confirmed=False):
|
||||
"""Formulaire suppression photo étudiant"""
|
||||
etud = sco_etud.get_etud_info(filled=True)[0]
|
||||
etud = Identite.query.get_or_404(etudid)
|
||||
if not dialog_confirmed:
|
||||
return scu.confirm_dialog(
|
||||
"<p>Confirmer la suppression de la photo de %(nomprenom)s ?</p>" % etud,
|
||||
f"<p>Confirmer la suppression de la photo de {etud.nom_disp()} ?</p>",
|
||||
dest_url="",
|
||||
cancel_url=url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||
),
|
||||
parameters={"etudid": etudid},
|
||||
parameters={"etudid": etud.id},
|
||||
)
|
||||
|
||||
sco_photos.suppress_photo(etud)
|
||||
|
||||
return flask.redirect(
|
||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""index in FormsemestreInscription
|
||||
"""index in FormSemestreInscription
|
||||
|
||||
Revision ID: 4f98a8b02c89
|
||||
Revises: a57a6ee2e3cb
|
||||
@ -10,23 +10,47 @@ import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '4f98a8b02c89'
|
||||
down_revision = 'a57a6ee2e3cb'
|
||||
revision = "4f98a8b02c89"
|
||||
down_revision = "a57a6ee2e3cb"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_index(op.f('ix_notes_formsemestre_inscription_etat'), 'notes_formsemestre_inscription', ['etat'], unique=False)
|
||||
op.create_index(op.f('ix_notes_formsemestre_inscription_etudid'), 'notes_formsemestre_inscription', ['etudid'], unique=False)
|
||||
op.create_index(op.f('ix_notes_formsemestre_inscription_formsemestre_id'), 'notes_formsemestre_inscription', ['formsemestre_id'], unique=False)
|
||||
op.create_index(
|
||||
op.f("ix_notes_formsemestre_inscription_etat"),
|
||||
"notes_formsemestre_inscription",
|
||||
["etat"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_notes_formsemestre_inscription_etudid"),
|
||||
"notes_formsemestre_inscription",
|
||||
["etudid"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
op.f("ix_notes_formsemestre_inscription_formsemestre_id"),
|
||||
"notes_formsemestre_inscription",
|
||||
["formsemestre_id"],
|
||||
unique=False,
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_notes_formsemestre_inscription_formsemestre_id'), table_name='notes_formsemestre_inscription')
|
||||
op.drop_index(op.f('ix_notes_formsemestre_inscription_etudid'), table_name='notes_formsemestre_inscription')
|
||||
op.drop_index(op.f('ix_notes_formsemestre_inscription_etat'), table_name='notes_formsemestre_inscription')
|
||||
op.drop_index(
|
||||
op.f("ix_notes_formsemestre_inscription_formsemestre_id"),
|
||||
table_name="notes_formsemestre_inscription",
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_notes_formsemestre_inscription_etudid"),
|
||||
table_name="notes_formsemestre_inscription",
|
||||
)
|
||||
op.drop_index(
|
||||
op.f("ix_notes_formsemestre_inscription_etat"),
|
||||
table_name="notes_formsemestre_inscription",
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
40
migrations/versions/91be8a06d423_user_token_size_limit.py
Normal file
40
migrations/versions/91be8a06d423_user_token_size_limit.py
Normal file
@ -0,0 +1,40 @@
|
||||
"""user token size limit
|
||||
|
||||
Revision ID: 91be8a06d423
|
||||
Revises: 4f98a8b02c89
|
||||
Create Date: 2021-12-20 22:48:42.390743
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "91be8a06d423"
|
||||
down_revision = "4f98a8b02c89"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column(
|
||||
"user",
|
||||
"token",
|
||||
existing_type=sa.VARCHAR(length=32),
|
||||
type_=sa.Text(),
|
||||
existing_nullable=True,
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column(
|
||||
"user",
|
||||
"token",
|
||||
existing_type=sa.Text(),
|
||||
type_=sa.VARCHAR(length=32),
|
||||
existing_nullable=True,
|
||||
)
|
||||
# ### end Alembic commands ###
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.1.11"
|
||||
SCOVERSION = "9.1.12"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
33
scodoc.py
33
scodoc.py
@ -24,7 +24,7 @@ from app.auth.models import User, Role, UserRole
|
||||
from app.models import ScoPreference
|
||||
from app.scodoc.sco_logos import make_logo_local
|
||||
from app.models import Formation, UniteEns, Module
|
||||
from app.models import FormSemestre, FormsemestreInscription
|
||||
from app.models import FormSemestre, FormSemestreInscription
|
||||
from app.models import ModuleImpl, ModuleImplInscription
|
||||
from app.models import Identite
|
||||
from app.models.evaluations import Evaluation
|
||||
@ -57,7 +57,7 @@ def make_shell_context():
|
||||
"flask": flask,
|
||||
"Formation": Formation,
|
||||
"FormSemestre": FormSemestre,
|
||||
"FormsemestreInscription": FormsemestreInscription,
|
||||
"FormSemestreInscription": FormSemestreInscription,
|
||||
"Identite": Identite,
|
||||
"login_user": login_user,
|
||||
"logout_user": logout_user,
|
||||
@ -248,6 +248,35 @@ def edit_role(rolename, addpermissionname=None, removepermissionname=None): # e
|
||||
db.session.commit()
|
||||
|
||||
|
||||
@app.cli.command()
|
||||
@click.argument("username")
|
||||
@click.option("-d", "--dept", "dept_acronym")
|
||||
@click.option("-a", "--add", "add_role_name")
|
||||
@click.option("-r", "--remove", "remove_role_name")
|
||||
def user_role(username, dept_acronym=None, add_role_name=None, remove_role_name=None):
|
||||
"""Add or remove a role to the given user in the given dept"""
|
||||
user = User.query.filter_by(user_name=username).first()
|
||||
if not user:
|
||||
sys.stderr.write(f"user_role: user {username} does not exists\n")
|
||||
return 1
|
||||
if dept_acronym:
|
||||
dept = models.Departement.query.filter_by(acronym=dept_acronym).first()
|
||||
if dept is None:
|
||||
sys.stderr.write(f"Erreur: le departement {dept} n'existe pas !\n")
|
||||
return 2
|
||||
|
||||
if add_role_name:
|
||||
role = Role.query.filter_by(name=add_role_name).first()
|
||||
user.add_role(role, dept_acronym)
|
||||
if remove_role_name:
|
||||
role = Role.query.filter_by(name=remove_role_name).first()
|
||||
user_role = UserRole.query.filter(
|
||||
UserRole.role == role, UserRole.user == user, UserRole.dept == dept_acronym
|
||||
).first()
|
||||
db.session.delete(user_role)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
@app.cli.command()
|
||||
@click.argument("dept")
|
||||
def delete_dept(dept): # delete-dept
|
||||
|
@ -2,7 +2,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Exemple utilisation API ScoDoc 9 avec jeton obtenu par basic athentication
|
||||
"""Exemple utilisation API ScoDoc 9 avec jeton obtenu par basic authentication
|
||||
|
||||
|
||||
Utilisation: créer les variables d'environnement: (indiquer les valeurs
|
||||
@ -80,6 +80,15 @@ if r.status_code != 200:
|
||||
|
||||
pp(r.json())
|
||||
|
||||
# Liste des tous les étudiants en cours (de tous les depts)
|
||||
r = requests.get(
|
||||
SCODOC_URL + "/ScoDoc/api/etudiants/courant",
|
||||
headers=HEADERS,
|
||||
verify=CHECK_CERTIFICATE,
|
||||
)
|
||||
if r.status_code != 200:
|
||||
raise ScoError("erreur de connexion: vérifier adresse et identifiants")
|
||||
|
||||
|
||||
# # --- Recupere la liste de tous les semestres:
|
||||
# sems = GET(s, "Notes/formsemestre_list?format=json", "Aucun semestre !")
|
||||
|
Loading…
Reference in New Issue
Block a user