This commit is contained in:
Emmanuel Viennet 2021-12-31 17:54:33 +01:00
commit 8f390cb415
18 changed files with 253 additions and 111 deletions

View File

@ -4,9 +4,6 @@
"""
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
class Absence(db.Model):

View File

@ -10,9 +10,7 @@ from flask import g, request
from app import db
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 notesdb as ndb

View File

@ -4,9 +4,6 @@
"""
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
from app.models import UniteEns
import app.scodoc.notesdb as ndb

View File

@ -4,9 +4,7 @@
"""
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
class Scolog(db.Model):

View File

@ -5,9 +5,7 @@
from typing import Any
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
from app.models import GROUPNAME_STR_LEN

View File

@ -4,7 +4,6 @@
"""
from app import db
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN

View File

@ -265,8 +265,11 @@ def DBUpdateArgs(cnx, table, vals, where=None, commit=False, convert_empty_to_nu
cursor.execute(req, vals)
# log('req=%s\n'%req)
# log('vals=%s\n'%vals)
except psycopg2.errors.StringDataRightTruncation:
cnx.rollback()
raise ScoValueError("champs de texte trop long !")
except:
cnx.commit() # get rid of this transaction
cnx.rollback() # get rid of this transaction
log('Exception in DBUpdateArgs:\n\treq="%s"\n\tvals="%s"\n' % (req, vals))
raise # and re-raise exception
if commit:

View File

@ -33,6 +33,7 @@ from flask import g, url_for, request
from app import db
from app import log
from app.models import SHORT_STR_LEN
from app.models.formations import Formation
from app.models.modules import Module
@ -209,6 +210,7 @@ def formation_edit(formation_id=None, create=False):
"size": 12,
"title": "Code formation",
"explanation": "code interne. Toutes les formations partageant le même code sont compatibles (compensation de semestres, capitalisation d'UE). Laisser vide si vous ne savez pas, ou entrer le code d'une formation existante.",
"validator": lambda val, _: len(val) < SHORT_STR_LEN,
},
),
(

View File

@ -32,6 +32,7 @@ import flask
from flask import url_for, render_template
from flask import g, request
from flask_login import current_user
from app.models import APO_CODE_STR_LEN
from app.models import Matiere, Module, UniteEns
import app.scodoc.notesdb as ndb
@ -397,21 +398,21 @@ def module_delete(module_id=None):
return flask.redirect(dest_url)
def do_module_edit(val):
def do_module_edit(vals: dict) -> None:
"edit a module"
from app.scodoc import sco_edit_formation
# check
mod = module_list({"module_id": val["module_id"]})[0]
mod = module_list({"module_id": vals["module_id"]})[0]
if module_is_locked(mod["module_id"]):
# formation verrouillée: empeche de modifier certains champs:
protected_fields = ("coefficient", "ue_id", "matiere_id", "semestre_id")
for f in protected_fields:
if f in val:
del val[f]
if f in vals:
del vals[f]
# edit
cnx = ndb.GetDBConnexion()
_moduleEditor.edit(cnx, val)
_moduleEditor.edit(cnx, vals)
Formation.query.get(mod["formation_id"]).invalidate_cached_sems()
@ -604,6 +605,7 @@ def module_edit(module_id=None):
"title": "Code Apogée",
"size": 25,
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
},
),
(

View File

@ -33,6 +33,7 @@ from flask import url_for, render_template
from flask import g, request
from flask_login import current_user
from app.models import APO_CODE_STR_LEN
from app.models import Formation, UniteEns, ModuleImpl, Module
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -324,6 +325,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
"title": "Code Apogée",
"size": 25,
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
},
),
(

View File

@ -33,6 +33,7 @@ from flask_login import current_user
from app import db
from app.auth.models import User
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
from app.models import ModuleImpl, Evaluation, EvaluationUEPoids
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@ -359,6 +360,7 @@ def do_formsemestre_createwithmodules(edit=False):
mf_manual = {
"size": 12,
"template": '<tr%(item_dom_attr)s><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s',
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
}
if etapes:
mf = {
@ -495,6 +497,7 @@ def do_formsemestre_createwithmodules(edit=False):
"size": 8,
"title": "Couleur fond des bulletins",
"explanation": "version web seulement (ex: #ffeeee)",
"validator": lambda val, _: len(val) < SHORT_STR_LEN,
},
),
(

View File

@ -45,6 +45,7 @@ from flask import g, request
from flask import url_for, make_response
from app import db
from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN
from app.models.groups import Partition
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
@ -870,10 +871,11 @@ def editPartitionForm(formsemestre_id=None):
page_title="Partitions...",
javascripts=["js/editPartitionForm.js"],
),
# limite à SHORT_STR_LEN
r"""<script type="text/javascript">
function checkname() {
var val = document.editpart.partition_name.value.replace(/^\s+/, "").replace(/\s+$/, "");
if (val.length > 0) {
if ((val.length > 0)&&(val.length < 32)) {
document.editpart.ok.disabled = false;
} else {
document.editpart.ok.disabled = true;
@ -1124,6 +1126,7 @@ def partition_rename(partition_id):
"default": partition["partition_name"],
"allow_null": False,
"size": 12,
"validator": lambda val, _: len(val) < SHORT_STR_LEN,
},
),
),
@ -1231,6 +1234,7 @@ def group_rename(group_id):
"default": group["group_name"],
"size": 12,
"allow_null": False,
"validator": lambda val, _: len(val) < GROUPNAME_STR_LEN,
},
),
),

View File

@ -256,6 +256,10 @@ function createGroup() {
alert("Nom de groupe vide !");
return false;
}
if (group_name.length >= 32) { // SHORT_STR_LEN
alert("Nom de groupe trop long !");
return false;
}
// check name:
for (var group_id in groupBoxes) {
if (group_id != 'extend') {

View File

@ -1,4 +1,4 @@
{# -*- mode: jinja-html -*- #}
{# -*- mode: jinja-html -*- #}
{{ sco_header|safe }}
<h2 class="formsemestre">Affectation aux groupes de {{ partition["partition_name"] }}</h2>

View File

@ -0,0 +1,56 @@
#!/usr/bin/env python3
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Exemple utilisation API ScoDoc 9 avec jeton obtenu par basic authentication
utilisation:
à faire fonctionner en environnment de test (FLASK_ENV=test dans le fichier .env)
pytest tests/api/test_api_logos.py
"""
import pytest
from scodoc import app
from tests.unit.config_test_logos import (
create_super_token,
create_admin_token,
create_lambda_token,
create_logos,
create_dept,
)
def test_super_access(create_super_token):
dept1, dept2, dept3, token = create_super_token
HEADERS = {"Authorization": f"Bearer {token}"}
with app.test_client() as client:
response = client.get("/ScoDoc/api/logos", headers=HEADERS)
assert response.status_code == 200
assert response.json is not None
def test_admin_access(create_admin_token):
dept1, dept2, dept3, token = create_admin_token
headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client:
response = client.get("/ScoDoc/api/logos", headers=headers)
assert response.status_code == 401
def test_lambda_access(create_lambda_token):
dept1, dept2, dept3, token = create_lambda_token
headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client:
response = client.get("/ScoDoc/api/logos", headers=headers)
assert response.status_code == 401
def test_initial_with_header_and_footer(create_super_token):
dept1, dept2, dept3, token = create_super_token
headers = {"Authorization": f"Bearer {token}"}
with app.test_client() as client:
response = client.get("/ScoDoc/api/logos", headers=headers)
assert response.status_code == 200
assert response.json is not None
assert len(response.json) == 7

View File

@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
"""Test Logos
Mise en place de l'environnement de test pour logos
"""
from pathlib import Path
from shutil import copytree, rmtree, copy
import pytest
import app.scodoc.sco_utils as scu
from app import db, Departement
from app.auth.models import User, Role
from config import TestConfig
from scodoc import app
from tests.conftest import test_client
RESOURCES_DIR = "/opt/scodoc/tests/ressources/test_logos"
@pytest.fixture
def create_dept(test_client):
"""Crée 3 départements:
return departements object
"""
client = test_client
dept1 = Departement(acronym="RT")
dept2 = Departement(acronym="INFO")
dept3 = Departement(acronym="GEA")
db.session.add(dept1)
db.session.add(dept2)
db.session.add(dept3)
db.session.commit()
yield dept1, dept2, dept3
db.session.delete(dept1)
db.session.delete(dept2)
db.session.delete(dept3)
db.session.commit()
@pytest.fixture
def create_logos(create_dept):
"""Crée les logos:
...logos --+-- logo_A.jpg
+-- logo_C.jpg
+-- logo_D.png
+-- logo_E.jpg
+-- logo_F.jpeg
+-- logos_{d1} --+-- logo_A.jpg
| +-- logo_B.jpg
+-- logos_{d2} --+-- logo_A.jpg
"""
dept1, dept2, dept3 = create_dept
dept1_id = dept1.id
dept2_id = dept2.id
FILE_LIST = ["logo_A.jpg", "logo_C.jpg", "logo_D.png", "logo_E.jpg", "logo_F.jpeg"]
for filename in FILE_LIST:
from_path = Path(RESOURCES_DIR).joinpath(filename)
to_path = Path(scu.SCODOC_LOGOS_DIR).joinpath(filename)
copy(from_path.absolute(), to_path.absolute())
copytree(
f"{RESOURCES_DIR}/logos_1",
f"{scu.SCODOC_LOGOS_DIR}/logos_{dept1_id}",
)
copytree(
f"{RESOURCES_DIR}/logos_2",
f"{scu.SCODOC_LOGOS_DIR}/logos_{dept2_id}",
)
yield dept1, dept2, dept3
rmtree(f"{scu.SCODOC_LOGOS_DIR}/logos_{dept1_id}")
rmtree(f"{scu.SCODOC_LOGOS_DIR}/logos_{dept2_id}")
# rm files
for filename in FILE_LIST:
to_path = Path(scu.SCODOC_LOGOS_DIR).joinpath(filename)
to_path.unlink()
def get_token(user_name):
with app.test_client() as client:
response = client.post("/ScoDoc/api/tokens", auth=(user_name, user_name))
assert response.status_code == 200
token = response.json["token"]
user = User.check_token(token) if token else None
assert user.user_name == user_name
app.logger.info(f"{user.user_name}: token obtained: {token}")
return token
@pytest.fixture
def create_super_token(create_logos):
dept1, dept2, dept3 = create_logos
# change super_admin password
# utilisateur mozart -> super
user_name = "mozart"
u = User.query.filter_by(user_name=user_name).first()
if u is None:
u = User(user_name=user_name)
u.set_password(user_name)
if "SuperAdmin" not in {r.name for r in u.roles}:
super_role = Role.query.filter_by(name="SuperAdmin").first()
u.add_role(super_role, None)
db.session.add(u)
db.session.commit()
return dept1, dept2, dept3, get_token(u.user_name)
@pytest.fixture
def create_admin_token(create_logos):
dept1, dept2, dept3 = create_logos
# utilisateur bach -> admin
user_name = "bach"
u = User.query.filter_by(user_name=user_name).first()
if u is None:
u = User(user_name=user_name)
u.set_password(user_name)
if "Admin" not in {r.name for r in u.roles}:
admin_role = Role.query.filter_by(name=user_name).first()
u.add_role(admin_role, TestConfig.DEPT_TEST)
db.session.add(u)
db.session.commit()
return dept1, dept2, dept3, get_token(u.user_name)
@pytest.fixture
def create_lambda_token(create_logos):
dept1, dept2, dept3 = create_logos
# utilisateur vivaldi -> lambda
user_name = "vivaldi"
u = User.query.filter_by(user_name=user_name).first()
if u is None:
u = User(user_name=user_name)
u.set_password(user_name)
db.session.add(u)
db.session.commit()
return dept1, dept2, dept3, get_token(user_name)

View File

@ -6,6 +6,7 @@ from tests.unit import sco_fake_gen
from app import db
from app import models
import app.scodoc.sco_utils as scu
from app.scodoc import sco_codes_parcours

View File

@ -2,131 +2,69 @@
"""Test Logos
Utiliser comme:
pytest tests/unit/test_logos.py
"""
from pathlib import Path
from shutil import copytree, copy, rmtree
from shutil import copy
import pytest as pytest
from _pytest.python_api import approx
import app
from app import db
from app.models import Departement
import app.scodoc.sco_utils as scu
from app.scodoc.sco_logos import (
find_logo,
Logo,
list_logos,
GLOBAL,
write_logo,
delete_logo,
)
RESOURCES_DIR = "/opt/scodoc/tests/ressources/test_logos"
@pytest.fixture
def create_dept(test_client):
"""Crée 2 départements:
return departements object
"""
dept1 = Departement(acronym="RT")
dept2 = Departement(acronym="INFO")
dept3 = Departement(acronym="GEA")
db.session.add(dept1)
db.session.add(dept2)
db.session.add(dept3)
db.session.commit()
yield dept1, dept2, dept3
db.session.delete(dept1)
db.session.delete(dept2)
db.session.delete(dept3)
db.session.commit()
@pytest.fixture
def create_logos(create_dept):
"""Crée les logos:
...logos --+-- logo_A.jpg
+-- logo_C.jpg
+-- logo_D.png
+-- logo_E.jpg
+-- logo_F.jpeg
+-- logos_{d1} --+-- logo_A.jpg
| +-- logo_B.jpg
+-- logos_{d2} --+-- logo_A.jpg
"""
dept1, dept2, dept3 = create_dept
d1 = dept1.id
d2 = dept2.id
d3 = dept3.id
FILE_LIST = ["logo_A.jpg", "logo_C.jpg", "logo_D.png", "logo_E.jpg", "logo_F.jpeg"]
for fn in FILE_LIST:
from_path = Path(RESOURCES_DIR).joinpath(fn)
to_path = Path(scu.SCODOC_LOGOS_DIR).joinpath(fn)
copy(from_path.absolute(), to_path.absolute())
copytree(
f"{RESOURCES_DIR}/logos_1",
f"{scu.SCODOC_LOGOS_DIR}/logos_{d1}",
)
copytree(
f"{RESOURCES_DIR}/logos_2",
f"{scu.SCODOC_LOGOS_DIR}/logos_{d2}",
)
yield None
rmtree(f"{scu.SCODOC_LOGOS_DIR}/logos_{d1}")
rmtree(f"{scu.SCODOC_LOGOS_DIR}/logos_{d2}")
# rm files
for fn in FILE_LIST:
to_path = Path(scu.SCODOC_LOGOS_DIR).joinpath(fn)
to_path.unlink()
from tests.unit.config_test_logos import create_dept, create_logos, RESOURCES_DIR
def test_select_global_only(create_logos):
dept1, dept2, dept3 = create_logos
C_logo = app.scodoc.sco_logos.find_logo(logoname="C")
assert C_logo.filepath == f"{scu.SCODOC_LOGOS_DIR}/logo_C.jpg"
def test_select_local_only(create_dept, create_logos):
dept1, dept2, dept3 = create_dept
def test_select_local_only(create_logos):
dept1, dept2, dept3 = create_logos
B_logo = app.scodoc.sco_logos.find_logo(logoname="B", dept_id=dept1.id)
assert B_logo.filepath == f"{scu.SCODOC_LOGOS_DIR}/logos_{dept1.id}/logo_B.jpg"
def test_select_local_override_global(create_dept, create_logos):
dept1, dept2, dept3 = create_dept
def test_select_local_override_global(create_logos):
dept1, dept2, dept3 = create_logos
A1_logo = app.scodoc.sco_logos.find_logo(logoname="A", dept_id=dept1.id)
assert A1_logo.filepath == f"{scu.SCODOC_LOGOS_DIR}/logos_{dept1.id}/logo_A.jpg"
def test_select_global_with_strict(create_dept, create_logos):
dept1, dept2, dept3 = create_dept
def test_select_global_with_strict(create_logos):
dept1, dept2, dept3 = create_logos
A_logo = app.scodoc.sco_logos.find_logo(logoname="A", dept_id=dept1.id, strict=True)
assert A_logo.filepath == f"{scu.SCODOC_LOGOS_DIR}/logos_{dept1.id}/logo_A.jpg"
def test_looks_for_non_existant_should_give_none(create_dept, create_logos):
def test_looks_for_non_existant_should_give_none(create_logos):
# search for a local non-existant logo returns None
dept1, dept2, dept3 = create_dept
dept1, dept2, dept3 = create_logos
no_logo = app.scodoc.sco_logos.find_logo(logoname="Z", dept_id=dept1.id)
assert no_logo is None
def test_looks_localy_for_a_global_should_give_none(create_dept, create_logos):
def test_looks_localy_for_a_global_should_give_none(create_logos):
# search for a local non-existant logo returns None
dept1, dept2, dept3 = create_dept
dept1, dept2, dept3 = create_logos
no_logo = app.scodoc.sco_logos.find_logo(
logoname="C", dept_id=dept1.id, strict=True
)
assert no_logo is None
def test_get_jpg_data(create_dept, create_logos):
def test_get_jpg_data(create_logos):
dept1, dept2, dept3 = create_logos
logo = find_logo("A", dept_id=None)
assert logo is not None
logo.select()
@ -137,7 +75,8 @@ def test_get_jpg_data(create_dept, create_logos):
assert logo.mm == approx((9.38, 5.49), 0.1)
def test_get_png_without_data(create_dept, create_logos):
def test_get_png_without_data(create_logos):
dept1, dept2, dept3 = create_logos
logo = find_logo("D", dept_id=None)
assert logo is not None
logo.select()
@ -149,7 +88,8 @@ def test_get_png_without_data(create_dept, create_logos):
assert logo.mm is None
def test_delete_unique_global_jpg_logo(create_dept, create_logos):
def test_delete_unique_global_jpg_logo(create_logos):
dept1, dept2, dept3 = create_logos
from_path = Path(RESOURCES_DIR).joinpath("logo_A.jpg")
to_path = Path(scu.SCODOC_LOGOS_DIR).joinpath("logo_W.jpg")
copy(from_path.absolute(), to_path.absolute())
@ -158,8 +98,8 @@ def test_delete_unique_global_jpg_logo(create_dept, create_logos):
assert not to_path.exists()
def test_delete_unique_local_jpg_logo(create_dept, create_logos):
dept1, dept2, dept3 = create_dept
def test_delete_unique_local_jpg_logo(create_logos):
dept1, dept2, dept3 = create_logos
from_path = Path(RESOURCES_DIR).joinpath("logo_A.jpg")
to_path = Path(scu.SCODOC_LOGOS_DIR).joinpath(f"logos_{dept1.id}", "logo_W.jpg")
copy(from_path.absolute(), to_path.absolute())
@ -168,8 +108,8 @@ def test_delete_unique_local_jpg_logo(create_dept, create_logos):
assert not to_path.exists()
def test_delete_multiple_local_jpg_logo(create_dept, create_logos):
dept1, dept2, dept3 = create_dept
def test_delete_multiple_local_jpg_logo(create_logos):
dept1, dept2, dept3 = create_logos
from_path_A = Path(RESOURCES_DIR).joinpath("logo_A.jpg")
to_path_A = Path(scu.SCODOC_LOGOS_DIR).joinpath(f"logos_{dept1.id}", "logo_V.jpg")
from_path_B = Path(RESOURCES_DIR).joinpath("logo_D.png")
@ -183,7 +123,8 @@ def test_delete_multiple_local_jpg_logo(create_dept, create_logos):
assert not to_path_B.exists()
def test_create_global_jpg_logo(create_dept, create_logos):
def test_create_global_jpg_logo(create_logos):
dept1, dept2, dept3 = create_logos
path = Path(f"{RESOURCES_DIR}/logo_C.jpg")
stream = path.open("rb")
logo_path = Path(scu.SCODOC_LOGOS_DIR).joinpath("logo_X.jpg")
@ -193,8 +134,8 @@ def test_create_global_jpg_logo(create_dept, create_logos):
logo_path.unlink(missing_ok=True)
def test_create_locale_jpg_logo(create_dept, create_logos):
dept1, dept2, dept3 = create_dept
def test_create_locale_jpg_logo(create_logos):
dept1, dept2, dept3 = create_logos
path = Path(f"{RESOURCES_DIR}/logo_C.jpg")
stream = path.open("rb")
logo_path = Path(scu.SCODOC_LOGOS_DIR).joinpath(f"logos_{dept1.id}", "logo_Y.jpg")
@ -204,7 +145,8 @@ def test_create_locale_jpg_logo(create_dept, create_logos):
logo_path.unlink(missing_ok=True)
def test_create_jpg_instead_of_png_logo(create_dept, create_logos):
def test_create_jpg_instead_of_png_logo(create_logos):
dept1, dept2, dept3 = create_logos
# action
logo = Logo("D") # create global logo (replace logo_D.png)
path = Path(f"{RESOURCES_DIR}/logo_C.jpg")
@ -226,9 +168,9 @@ def test_create_jpg_instead_of_png_logo(create_dept, create_logos):
created.unlink(missing_ok=True)
def test_list_logo(create_dept, create_logos):
def test_list_logo(create_logos):
# test only existence of copied logos. We assumes that they are OK
dept1, dept2, dept3 = create_dept
dept1, dept2, dept3 = create_logos
logos = list_logos()
assert set(logos.keys()) == {dept1.id, dept2.id, None}
assert {"A", "C", "D", "E", "F", "header", "footer"}.issubset(