forked from ScoDoc/ScoDoc
Compare commits
112 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b0ae231725 | ||
|
74de287ad6 | ||
0509f1c923 | |||
659c790af2 | |||
88d9356209 | |||
84580a8a6f | |||
bf53dfa93a | |||
c1d8a34e34 | |||
dc53fe8395 | |||
464d678299 | |||
|
aa9d85f4bd | ||
6e6f76dc26 | |||
b9cf1e9efa | |||
|
871e85ae26 | ||
ef44a71e39 | |||
867ed6dde8 | |||
c1e6b67f20 | |||
f2f616d643 | |||
ce80b9f765 | |||
|
bdc6c90bfc | ||
6ab027dffe | |||
d8f6fe35e9 | |||
|
8fd1fa5a25 | ||
|
47f17157f1 | ||
|
10b7b876ea | ||
|
597f7ef85c | ||
d4e875a7bd | |||
430b378723 | |||
37c7e82994 | |||
ca11132503 | |||
478bf80c6b | |||
a904db9eee | |||
26dcc0db3b | |||
b448e32f8a | |||
20bb9cc9ed | |||
8a6b167f8c | |||
17d9b8daa9 | |||
6a48d5bbcf | |||
|
4ab7142488 | ||
|
07f09ddead | ||
|
4f40713787 | ||
|
71f88dfa97 | ||
|
9caa6bf75d | ||
|
3d74979237 | ||
|
e1a5ea31cd | ||
|
5624637f30 | ||
|
11c9ab332f | ||
|
514e9e4c83 | ||
f136b80c84 | |||
405533798e | |||
99b0f23bca | |||
1437c1bafa | |||
|
ebceb70f05 | ||
814d458beb | |||
47e40a9634 | |||
19a8e9650b | |||
376fa570f6 | |||
b16d519362 | |||
4bfd0858a8 | |||
|
24de3ac1b5 | ||
|
fb6b63bf0b | ||
|
0eb407f5e3 | ||
6b8667522b | |||
37dcdca65b | |||
7c7d128b57 | |||
646c30bb21 | |||
055839757b | |||
6eb3921774 | |||
b2443a2c69 | |||
0df77b20fb | |||
6d922adb7e | |||
bf6cad9d77 | |||
5a751cb6e7 | |||
cb0c9d8f53 | |||
e6f86d655b | |||
|
af7b5b01fb | ||
|
330036c1be | ||
513fb3d46d | |||
|
ae84ed26c3 | ||
3971145abd | |||
5edd7a8ba3 | |||
07c2f00d0d | |||
3d34f9330d | |||
525d0446cc | |||
fc036705e8 | |||
bee6d10c90 | |||
74401da853 | |||
450d503c39 | |||
d7f5c3e5e1 | |||
03ba057a87 | |||
0533ad59fd | |||
9302a173aa | |||
|
5b68adaf87 | ||
933968c99b | |||
|
00eb37e8ac | ||
|
7f08f84934 | ||
|
d77bf8f700 | ||
|
5cb7ade189 | ||
|
5e066d13f0 | ||
|
d9d8d8d7fe | ||
3cecfbb697 | |||
ad8bb5aace | |||
77adda5c90 | |||
f6897a2c12 | |||
7a77b5a81a | |||
7ff0fd39fb | |||
64038687b7 | |||
75c10a4917 | |||
4824b33358 | |||
f87ed3bb68 | |||
0a7bb35604 | |||
c9ca97df6e |
@ -28,6 +28,9 @@ from flask_migrate import Migrate
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
from jinja2 import select_autoescape
|
||||
import numpy as np
|
||||
import psycopg2
|
||||
from psycopg2.extensions import AsIs as psycopg2_AsIs
|
||||
import sqlalchemy as sa
|
||||
import werkzeug.debug
|
||||
from wtforms.fields import HiddenField
|
||||
@ -68,6 +71,19 @@ cache = Cache(
|
||||
)
|
||||
|
||||
|
||||
# NumPy & Psycopg2 (necessary with Numpy 2.0)
|
||||
# probablement à changer quand on passera à psycopg3.2
|
||||
def adapt_numpy_scalar(numpy_scalar):
|
||||
"""Adapt numeric types for psycopg2"""
|
||||
return psycopg2_AsIs(numpy_scalar if not np.isnan(numpy_scalar) else "'NaN'")
|
||||
|
||||
|
||||
psycopg2.extensions.register_adapter(np.float32, adapt_numpy_scalar)
|
||||
psycopg2.extensions.register_adapter(np.float64, adapt_numpy_scalar)
|
||||
psycopg2.extensions.register_adapter(np.int32, adapt_numpy_scalar)
|
||||
psycopg2.extensions.register_adapter(np.int64, adapt_numpy_scalar)
|
||||
|
||||
|
||||
def handle_sco_value_error(exc):
|
||||
"page d'erreur avec message"
|
||||
return render_template("sco_value_error.j2", exc=exc), 404
|
||||
@ -337,8 +353,7 @@ def create_app(config_class=DevConfig):
|
||||
app.jinja_env.autoescape = select_autoescape(default_for_string=True, default=True)
|
||||
app.jinja_env.trim_blocks = True
|
||||
app.jinja_env.lstrip_blocks = True
|
||||
# previously in Flask-Bootstrap:
|
||||
app.jinja_env.globals["bootstrap_is_hidden_field"] = lambda field: isinstance(
|
||||
app.jinja_env.globals["is_hidden_field"] = lambda field: isinstance(
|
||||
field, HiddenField
|
||||
)
|
||||
|
||||
|
@ -7,7 +7,7 @@ from flask_json import as_json
|
||||
from flask import Blueprint
|
||||
from flask import current_app, g, request
|
||||
from flask_login import current_user
|
||||
from app import db
|
||||
from app import db, log
|
||||
from app.decorators import permission_required
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoException
|
||||
@ -47,6 +47,7 @@ def api_permission_required(permission):
|
||||
@api_bp.errorhandler(404)
|
||||
def api_error_handler(e):
|
||||
"erreurs API => json"
|
||||
log(f"api_error_handler: {e}")
|
||||
return scu.json_error(404, message=str(e))
|
||||
|
||||
|
||||
|
@ -25,6 +25,7 @@ from app.models import BilletAbsence
|
||||
from app.models.etudiants import Identite
|
||||
from app.scodoc import sco_abs_billets
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
@bp.route("/billets_absence/etudiant/<int:etudid>")
|
||||
@ -59,13 +60,17 @@ def billets_absence_create():
|
||||
"justified" : bool
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/billets_absence/create;{""etudid"":""1"",""abs_begin"":""2023-10-27T10:00"",""abs_end"":""2023-10-28T10:00"",""description"":""grave malade"",""justified"":""1""}
|
||||
"""
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
etudid = data.get("etudid")
|
||||
abs_begin = data.get("abs_begin")
|
||||
abs_end = data.get("abs_end")
|
||||
description = data.get("description", "")
|
||||
justified = data.get("justified", False)
|
||||
justified = scu.to_bool(data.get("justified", False))
|
||||
if None in (etudid, abs_begin, abs_end):
|
||||
return json_error(
|
||||
404, message="Paramètre manquant: etudid, abs_begin, abs_end requis"
|
||||
|
@ -38,7 +38,13 @@ from app.scodoc.sco_utils import json_error
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def departements_list():
|
||||
"""Liste tous les départements."""
|
||||
"""Liste tous les départements.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departements;
|
||||
|
||||
"""
|
||||
return [dept.to_dict(with_dept_name=True) for dept in Departement.query]
|
||||
|
||||
|
||||
@ -48,7 +54,13 @@ def departements_list():
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def departements_ids():
|
||||
"""Liste des ids de tous les départements."""
|
||||
"""Liste des ids de tous les départements.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departements_ids;
|
||||
|
||||
"""
|
||||
return [dept.id for dept in Departement.query]
|
||||
|
||||
|
||||
@ -61,17 +73,10 @@ def departement_by_acronym(acronym: str):
|
||||
"""
|
||||
Info sur un département. Accès par acronyme.
|
||||
|
||||
Exemple de résultat :
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"acronym": "TAPI",
|
||||
"dept_name" : "TEST",
|
||||
"description": null,
|
||||
"visible": true,
|
||||
"date_creation": "Fri, 15 Apr 2022 12:19:28 GMT"
|
||||
}
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/TAPI;
|
||||
|
||||
"""
|
||||
dept = Departement.query.filter_by(acronym=acronym).first_or_404()
|
||||
return dept.to_dict(with_dept_name=True)
|
||||
@ -82,9 +87,14 @@ def departement_by_acronym(acronym: str):
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def departement_by_id(dept_id: int):
|
||||
def departement_get(dept_id: int):
|
||||
"""
|
||||
Info sur un département. Accès par id.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/id/1;
|
||||
|
||||
"""
|
||||
dept = Departement.query.get_or_404(dept_id)
|
||||
return dept.to_dict()
|
||||
@ -107,6 +117,10 @@ def departement_create():
|
||||
"visible": bool,
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/create;{""acronym"":""MYDEPT"",""visible"":""1""}
|
||||
"""
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
acronym = str(data.get("acronym", ""))
|
||||
@ -180,23 +194,10 @@ def departement_etudiants(acronym: str):
|
||||
------
|
||||
acronym : l'acronyme d'un département
|
||||
|
||||
Exemple de résultat :
|
||||
```json
|
||||
[
|
||||
{
|
||||
"civilite": "M",
|
||||
"code_ine": "7899X61616",
|
||||
"code_nip": "F6777H88",
|
||||
"date_naissance": null,
|
||||
"email": "toto@toto.fr",
|
||||
"emailperso": null,
|
||||
"etudid": 18,
|
||||
"nom": "MOREL",
|
||||
"prenom": "JACQUES"
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/TAPI/etudiants;
|
||||
|
||||
"""
|
||||
dept = Departement.query.filter_by(acronym=acronym).first_or_404()
|
||||
return [etud.to_dict_short() for etud in dept.etudiants]
|
||||
@ -221,7 +222,13 @@ def departement_etudiants_by_id(dept_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def departement_formsemestres_ids(acronym: str):
|
||||
"""Liste des ids de tous les formsemestres du département."""
|
||||
"""Liste des ids de tous les formsemestres du département.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/TAPI/formsemestres_ids;
|
||||
|
||||
"""
|
||||
dept = Departement.query.filter_by(acronym=acronym).first_or_404()
|
||||
return [formsemestre.id for formsemestre in dept.formsemestres]
|
||||
|
||||
@ -232,7 +239,13 @@ def departement_formsemestres_ids(acronym: str):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def departement_formsemestres_ids_by_id(dept_id: int):
|
||||
"""Liste des ids de tous les formsemestres du département."""
|
||||
"""Liste des ids de tous les formsemestres du département.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/id/1/formsemestres_ids;
|
||||
|
||||
"""
|
||||
dept = Departement.query.get_or_404(dept_id)
|
||||
return [formsemestre.id for formsemestre in dept.formsemestres]
|
||||
|
||||
@ -253,6 +266,9 @@ def departement_formsemestres_courants(acronym: str = "", dept_id: int | None =
|
||||
-----
|
||||
date_courante:<string:date_courante>
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/id/1/formsemestres_courants?date_courante=2022-01-01
|
||||
"""
|
||||
dept = (
|
||||
Departement.query.filter_by(acronym=acronym).first_or_404()
|
||||
|
@ -101,27 +101,16 @@ def etudiants_courants(long: bool = False):
|
||||
et les formsemestres contenant la date courante,
|
||||
ou à défaut celle indiquée en argument (au format ISO).
|
||||
|
||||
En format "long": voir l'exemple.
|
||||
|
||||
QUERY
|
||||
-----
|
||||
date_courante:<string:date_courante>
|
||||
|
||||
Exemple de résultat :
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1234,
|
||||
"code_nip": "12345678",
|
||||
"code_ine": null,
|
||||
"nom": "JOHN",
|
||||
"nom_usuel": None,
|
||||
"prenom": "DEUF",
|
||||
"civilite": "M",
|
||||
}
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
En format "long": voir documentation.
|
||||
SAMPLES
|
||||
-------
|
||||
/etudiants/courants?date_courante=2022-05-01;
|
||||
/etudiants/courants/long?date_courante=2022-05-01;
|
||||
|
||||
"""
|
||||
allowed_depts = current_user.get_depts_with_permission(Permission.ScoView)
|
||||
@ -436,6 +425,10 @@ def bulletin(
|
||||
version : type de bulletin (par défaut, "selectedevals"): short, long, selectedevals, butcourt
|
||||
pdf : si spécifié, bulletin au format PDF (et non JSON).
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/etudiant/etudid/1/formsemestre/1/bulletin
|
||||
|
||||
"""
|
||||
if version == "pdf":
|
||||
version = "long"
|
||||
@ -494,33 +487,9 @@ def etudiant_groups(formsemestre_id: int, etudid: int = None):
|
||||
formsemestre_id : l'id d'un formsemestre
|
||||
etudid : l'etudid d'un étudiant
|
||||
|
||||
Exemple de résultat :
|
||||
```json
|
||||
[
|
||||
{
|
||||
"partition_id": 1,
|
||||
"id": 1,
|
||||
"formsemestre_id": 1,
|
||||
"partition_name": null,
|
||||
"numero": 0,
|
||||
"bul_show_rank": false,
|
||||
"show_in_lists": true,
|
||||
"group_id": 1,
|
||||
"group_name": null
|
||||
},
|
||||
{
|
||||
"partition_id": 2,
|
||||
"id": 2,
|
||||
"formsemestre_id": 1,
|
||||
"partition_name": "TD",
|
||||
"numero": 1,
|
||||
"bul_show_rank": false,
|
||||
"show_in_lists": true,
|
||||
"group_id": 2,
|
||||
"group_name": "A"
|
||||
}
|
||||
]
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/etudiant/etudid/1/formsemestre/1/groups
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
@ -627,6 +596,10 @@ def etudiant_edit(
|
||||
------
|
||||
`code_type`: le type du code, `etudid`, `ine` ou `nip`.
|
||||
`code`: la valeur du code
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/etudiant/ine/INE1/edit;{""prenom"":""Nouveau Prénom"", ""adresses"":[{""email"":""nouvelle@adresse.fr""}]}
|
||||
"""
|
||||
ok, etud = _get_etud_by_code(code_type, code, g.scodoc_dept)
|
||||
if not ok:
|
||||
@ -682,6 +655,10 @@ def etudiant_annotation(
|
||||
"comment" : string
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/etudiant/etudid/1/annotation;{""comment"":""une annotation sur l'étudiant""}
|
||||
"""
|
||||
if not current_user.has_permission(Permission.ViewEtudData):
|
||||
return json_error(403, "non autorisé (manque ViewEtudData)")
|
||||
|
@ -84,7 +84,9 @@ def moduleimpl_evaluations(moduleimpl_id: int):
|
||||
------
|
||||
moduleimpl_id : l'id d'un moduleimpl
|
||||
|
||||
Exemple de résultat : voir `/evaluation`.
|
||||
SAMPLES
|
||||
-------
|
||||
/moduleimpl/1/evaluations
|
||||
"""
|
||||
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||
return [evaluation.to_dict_api() for evaluation in modimpl.evaluations]
|
||||
@ -104,30 +106,9 @@ def evaluation_notes(evaluation_id: int):
|
||||
------
|
||||
evaluation_id : l'id de l'évaluation
|
||||
|
||||
Exemple de résultat :
|
||||
```json
|
||||
{
|
||||
"11": {
|
||||
"etudid": 11,
|
||||
"evaluation_id": 1,
|
||||
"value": 15.0,
|
||||
"note_max" : 20.0,
|
||||
"comment": "",
|
||||
"date": "2024-07-19T19:08:44+02:00",
|
||||
"uid": 2
|
||||
},
|
||||
"12": {
|
||||
"etudid": 12,
|
||||
"evaluation_id": 1,
|
||||
"value": "ABS",
|
||||
"note_max" : 20.0,
|
||||
"comment": "",
|
||||
"date": "2024-07-19T19:08:44+02:00",
|
||||
"uid": 2
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/evaluation/2/notes;
|
||||
"""
|
||||
query = Evaluation.query.filter_by(id=evaluation_id)
|
||||
if g.scodoc_dept:
|
||||
@ -173,10 +154,14 @@ def evaluation_set_notes(evaluation_id: int): # evaluation-notes-set
|
||||
|
||||
Résultat:
|
||||
|
||||
- nb_changed: nombre de notes changées
|
||||
- nb_suppress: nombre de notes effacées
|
||||
- etudids_changed: étudiants dont la note est modifiée
|
||||
- etudids_with_decision: liste des etudiants dont la note a changé
|
||||
alors qu'ils ont une décision de jury enregistrée.
|
||||
- history_menu: un fragment de HTML expliquant l'historique de la note de chaque étudiant modifié.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/evaluation/1/notes/set;{""notes"": [[1, 17], [2, ""SUPR""]], ""comment"" : ""sample test""}
|
||||
"""
|
||||
query = Evaluation.query.filter_by(id=evaluation_id)
|
||||
if g.scodoc_dept:
|
||||
@ -224,6 +209,11 @@ def evaluation_create(moduleimpl_id: int):
|
||||
}
|
||||
|
||||
Résultat: l'évaluation créée.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/moduleimpl/1/evaluation/create;{""description"":""Exemple éval.""}
|
||||
|
||||
"""
|
||||
moduleimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
|
||||
if not moduleimpl.can_edit_evaluation(current_user):
|
||||
|
@ -44,6 +44,11 @@ def formations():
|
||||
"""
|
||||
Retourne la liste de toutes les formations (tous départements,
|
||||
sauf si route départementale).
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formations;
|
||||
|
||||
"""
|
||||
query = Formation.query
|
||||
if g.scodoc_dept:
|
||||
@ -64,6 +69,11 @@ def formations_ids():
|
||||
(tous départements, ou du département indiqué dans la route)
|
||||
|
||||
Exemple de résultat : `[ 17, 99, 32 ]`.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formations_ids;
|
||||
|
||||
"""
|
||||
query = Formation.query
|
||||
if g.scodoc_dept:
|
||||
@ -77,28 +87,14 @@ def formations_ids():
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def formation_by_id(formation_id: int):
|
||||
def formation_get(formation_id: int):
|
||||
"""
|
||||
La formation d'id donné.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formation/1;
|
||||
|
||||
Exemple de résultat :
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"acronyme": "BUT R&T",
|
||||
"titre_officiel": "Bachelor technologique réseaux et télécommunications",
|
||||
"formation_code": "V1RET",
|
||||
"code_specialite": null,
|
||||
"dept_id": 1,
|
||||
"titre": "BUT R&T",
|
||||
"version": 1,
|
||||
"type_parcours": 700,
|
||||
"referentiel_competence_id": null,
|
||||
"formation_id": 1
|
||||
}
|
||||
```
|
||||
"""
|
||||
query = Formation.query.filter_by(id=formation_id)
|
||||
if g.scodoc_dept:
|
||||
@ -135,97 +131,9 @@ def formation_export_by_formation_id(formation_id: int, export_ids=False):
|
||||
formation_id : l'id d'une formation
|
||||
export_with_ids : si présent, exporte aussi les ids des objets ScoDoc de la formation.
|
||||
|
||||
Exemple de résultat :
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"acronyme": "BUT R&T",
|
||||
"titre_officiel": "Bachelor technologique r\u00e9seaux et t\u00e9l\u00e9communications",
|
||||
"formation_code": "V1RET",
|
||||
"code_specialite": null,
|
||||
"dept_id": 1,
|
||||
"titre": "BUT R&T",
|
||||
"version": 1,
|
||||
"type_parcours": 700,
|
||||
"referentiel_competence_id": null,
|
||||
"formation_id": 1,
|
||||
"ue": [
|
||||
{
|
||||
"acronyme": "RT1.1",
|
||||
"numero": 1,
|
||||
"titre": "Administrer les r\u00e9seaux et l\u2019Internet",
|
||||
"type": 0,
|
||||
"ue_code": "UCOD11",
|
||||
"ects": 12.0,
|
||||
"is_external": false,
|
||||
"code_apogee": "",
|
||||
"coefficient": 0.0,
|
||||
"semestre_idx": 1,
|
||||
"color": "#B80004",
|
||||
"reference": 1,
|
||||
"matiere": [
|
||||
{
|
||||
"titre": "Administrer les r\u00e9seaux et l\u2019Internet",
|
||||
"numero": 1,
|
||||
"module": [
|
||||
{
|
||||
"titre": "Initiation aux r\u00e9seaux informatiques",
|
||||
"abbrev": "Init aux r\u00e9seaux informatiques",
|
||||
"code": "R101",
|
||||
"heures_cours": 0.0,
|
||||
"heures_td": 0.0,
|
||||
"heures_tp": 0.0,
|
||||
"coefficient": 1.0,
|
||||
"ects": "",
|
||||
"semestre_id": 1,
|
||||
"numero": 10,
|
||||
"code_apogee": "",
|
||||
"module_type": 2,
|
||||
"coefficients": [
|
||||
{
|
||||
"ue_reference": "1",
|
||||
"coef": "12.0"
|
||||
},
|
||||
{
|
||||
"ue_reference": "2",
|
||||
"coef": "4.0"
|
||||
},
|
||||
{
|
||||
"ue_reference": "3",
|
||||
"coef": "4.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"titre": "Se sensibiliser \u00e0 l'hygi\u00e8ne informatique...",
|
||||
"abbrev": "Hygi\u00e8ne informatique",
|
||||
"code": "SAE11",
|
||||
"heures_cours": 0.0,
|
||||
"heures_td": 0.0,
|
||||
"heures_tp": 0.0,
|
||||
"coefficient": 1.0,
|
||||
"ects": "",
|
||||
"semestre_id": 1,
|
||||
"numero": 10,
|
||||
"code_apogee": "",
|
||||
"module_type": 3,
|
||||
"coefficients": [
|
||||
{
|
||||
"ue_reference": "1",
|
||||
"coef": "16.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/formation/1/export
|
||||
"""
|
||||
query = Formation.query.filter_by(id=formation_id)
|
||||
if g.scodoc_dept:
|
||||
@ -250,6 +158,11 @@ def referentiel_competences(formation_id: int):
|
||||
"""
|
||||
Retourne le référentiel de compétences de la formation
|
||||
ou null si pas de référentiel associé.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formation/1/referentiel_competences;
|
||||
|
||||
"""
|
||||
query = Formation.query.filter_by(id=formation_id)
|
||||
if g.scodoc_dept:
|
||||
@ -360,7 +273,12 @@ def ue_desassoc_niveau(ue_id: int):
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def get_ue(ue_id: int):
|
||||
"""Renvoie l'UE."""
|
||||
"""Renvoie l'UE.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formation/ue/1;
|
||||
"""
|
||||
query = UniteEns.query.filter_by(id=ue_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
||||
@ -374,7 +292,12 @@ def get_ue(ue_id: int):
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def formation_module_get(module_id: int):
|
||||
"""Renvoie le module."""
|
||||
"""Renvoie le module.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formation/module/1;
|
||||
"""
|
||||
query = Module.query.filter_by(id=module_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
||||
|
@ -13,11 +13,14 @@
|
||||
FormSemestre
|
||||
|
||||
"""
|
||||
import base64
|
||||
import io
|
||||
from operator import attrgetter, itemgetter
|
||||
|
||||
from flask import g, make_response, request
|
||||
from flask_json import as_json
|
||||
from flask_login import current_user, login_required
|
||||
import PIL
|
||||
import sqlalchemy as sa
|
||||
import app
|
||||
from app import db, log
|
||||
@ -32,6 +35,7 @@ from app.models import (
|
||||
Departement,
|
||||
Evaluation,
|
||||
FormSemestre,
|
||||
FormSemestreDescription,
|
||||
FormSemestreEtape,
|
||||
FormSemestreInscription,
|
||||
Identite,
|
||||
@ -41,6 +45,10 @@ from app.models import (
|
||||
from app.models.formsemestre import GROUPS_AUTO_ASSIGNMENT_DATA_MAX
|
||||
from app.scodoc.sco_bulletins import get_formsemestre_bulletin_etud_json
|
||||
from app.scodoc import sco_edt_cal
|
||||
from app.scodoc.sco_formsemestre_inscriptions import (
|
||||
do_formsemestre_inscription_with_modules,
|
||||
do_formsemestre_desinscription,
|
||||
)
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
@ -54,49 +62,17 @@ from app.tables.recap import TableRecap, RowRecap
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def formsemestre_infos(formsemestre_id: int):
|
||||
def formsemestre_get(formsemestre_id: int):
|
||||
"""
|
||||
Information sur le formsemestre indiqué.
|
||||
|
||||
formsemestre_id : l'id du formsemestre
|
||||
|
||||
Exemple de résultat :
|
||||
```json
|
||||
{
|
||||
"block_moyennes": false,
|
||||
"bul_bgcolor": "white",
|
||||
"bul_hide_xml": false,
|
||||
"date_debut_iso": "2021-09-01",
|
||||
"date_debut": "01/09/2021",
|
||||
"date_fin_iso": "2022-08-31",
|
||||
"date_fin": "31/08/2022",
|
||||
"dept_id": 1,
|
||||
"elt_annee_apo": null,
|
||||
"elt_passage_apo" : null,
|
||||
"elt_sem_apo": null,
|
||||
"ens_can_edit_eval": false,
|
||||
"etat": true,
|
||||
"formation_id": 1,
|
||||
"formsemestre_id": 1,
|
||||
"gestion_compensation": false,
|
||||
"gestion_semestrielle": false,
|
||||
"id": 1,
|
||||
"modalite": "FI",
|
||||
"resp_can_change_ens": true,
|
||||
"resp_can_edit": false,
|
||||
"responsables": [1, 99], // uids
|
||||
"scodoc7_id": null,
|
||||
"semestre_id": 1,
|
||||
"titre_formation" : "BUT GEA",
|
||||
"titre_num": "BUT GEA semestre 1",
|
||||
"titre": "BUT GEA",
|
||||
}
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
return formsemestre.to_dict_api()
|
||||
|
||||
|
||||
@ -270,7 +246,7 @@ def formsemestre_set_apo_etapes():
|
||||
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
||||
par des virgules.
|
||||
|
||||
Ce changement peut être fait sur un semestre verrouillé
|
||||
Ce changement peut être fait sur un semestre verrouillé.
|
||||
|
||||
DATA
|
||||
----
|
||||
@ -378,7 +354,7 @@ def formsemestre_set_elt_annee_apo():
|
||||
@scodoc
|
||||
@permission_required(Permission.EditApogee)
|
||||
def formsemestre_set_elt_passage_apo():
|
||||
"""Change les codes apogée de passage du semestre indiqué (par le champ oid).
|
||||
"""Change les codes Apogée de passage du semestre indiqué (par le champ oid).
|
||||
|
||||
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
||||
par des virgules.
|
||||
@ -425,14 +401,11 @@ def bulletins(formsemestre_id: int, version: str = "long"):
|
||||
formsemestre_id : int
|
||||
version : string ("long", "short", "selectedevals")
|
||||
|
||||
Exemple de résultat : liste, voir https://scodoc.org/ScoDoc9API/#bulletin
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/bulletins
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
formsemestre: FormSemestre = query.first()
|
||||
if formsemestre is None:
|
||||
return json_error(404, "formsemestre non trouve")
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
app.set_sco_dept(formsemestre.departement.acronym)
|
||||
|
||||
data = []
|
||||
@ -455,72 +428,11 @@ def formsemestre_programme(formsemestre_id: int):
|
||||
"""
|
||||
Retourne la liste des UEs, ressources et SAEs d'un semestre
|
||||
|
||||
|
||||
Exemple de résultat :
|
||||
```json
|
||||
{
|
||||
"ues": [
|
||||
{
|
||||
"type": 0,
|
||||
"formation_id": 1,
|
||||
"ue_code": "UCOD11",
|
||||
"id": 1,
|
||||
"ects": 12.0,
|
||||
"acronyme": "RT1.1",
|
||||
"is_external": false,
|
||||
"numero": 1,
|
||||
"code_apogee": "",
|
||||
"titre": "Administrer les r\u00e9seaux et l\u2019Internet",
|
||||
"coefficient": 0.0,
|
||||
"semestre_idx": 1,
|
||||
"color": "#B80004",
|
||||
"ue_id": 1
|
||||
},
|
||||
...
|
||||
],
|
||||
"ressources": [
|
||||
{
|
||||
"ens": [ 10, 18 ],
|
||||
"formsemestre_id": 1,
|
||||
"id": 15,
|
||||
"module": {
|
||||
"abbrev": "Programmer",
|
||||
"code": "SAE15",
|
||||
"code_apogee": "V7GOP",
|
||||
"coefficient": 1.0,
|
||||
"formation_id": 1,
|
||||
"heures_cours": 0.0,
|
||||
"heures_td": 0.0,
|
||||
"heures_tp": 0.0,
|
||||
"id": 15,
|
||||
"matiere_id": 3,
|
||||
"module_id": 15,
|
||||
"module_type": 3,
|
||||
"numero": 50,
|
||||
"semestre_id": 1,
|
||||
"titre": "Programmer en Python",
|
||||
"ue_id": 3
|
||||
},
|
||||
"module_id": 15,
|
||||
"moduleimpl_id": 15,
|
||||
"responsable_id": 2
|
||||
},
|
||||
...
|
||||
],
|
||||
"saes": [
|
||||
{
|
||||
...
|
||||
},
|
||||
...
|
||||
],
|
||||
"modules" : [ ... les modules qui ne sont ni des SAEs ni des ressources ... ]
|
||||
}
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/programme
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
ues = formsemestre.get_ues()
|
||||
m_list = {
|
||||
ModuleType.RESSOURCE: [],
|
||||
@ -588,11 +500,12 @@ def formsemestre_etudiants(
|
||||
-----
|
||||
etat:<string:etat>
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/etudiants/query;
|
||||
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
if with_query:
|
||||
etat = request.args.get("etat")
|
||||
if etat is not None:
|
||||
@ -624,6 +537,63 @@ def formsemestre_etudiants(
|
||||
return sorted(etuds, key=itemgetter("sort_key"))
|
||||
|
||||
|
||||
@bp.post("/formsemestre/<int:formsemestre_id>/etudid/<int:etudid>/inscrit")
|
||||
@api_web_bp.post("/formsemestre/<int:formsemestre_id>/etudid/<int:etudid>/inscrit")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.EtudInscrit)
|
||||
@as_json
|
||||
def formsemestre_etud_inscrit(formsemestre_id: int, etudid: int):
|
||||
"""Inscrit l'étudiant à ce formsemestre et TOUS ses modules STANDARDS
|
||||
(donc sauf les modules bonus sport).
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"dept_id" : int, # le département
|
||||
"etape" : string, # optionnel: l'étape Apogée d'inscription
|
||||
"group_ids" : [int], # optionnel: liste des groupes où inscrire l'étudiant (doivent exister)
|
||||
}
|
||||
```
|
||||
"""
|
||||
data = request.get_json(force=True) if request.data else {}
|
||||
dept_id = data.get("dept_id", g.scodoc_dept_id)
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id, dept_id=dept_id)
|
||||
app.set_sco_dept(formsemestre.departement.acronym)
|
||||
etud = Identite.get_etud(etudid)
|
||||
|
||||
group_ids = data.get("group_ids", [])
|
||||
etape = data.get("etape", None)
|
||||
do_formsemestre_inscription_with_modules(
|
||||
formsemestre.id, etud.id, dept_id=dept_id, etape=etape, group_ids=group_ids
|
||||
)
|
||||
app.log(f"formsemestre_etud_inscrit: {etud} inscrit à {formsemestre}")
|
||||
return (
|
||||
FormSemestreInscription.query.filter_by(
|
||||
formsemestre_id=formsemestre.id, etudid=etud.id
|
||||
)
|
||||
.first()
|
||||
.to_dict()
|
||||
)
|
||||
|
||||
|
||||
@bp.post("/formsemestre/<int:formsemestre_id>/etudid/<int:etudid>/desinscrit")
|
||||
@api_web_bp.post("/formsemestre/<int:formsemestre_id>/etudid/<int:etudid>/desinscrit")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.EtudInscrit)
|
||||
@as_json
|
||||
def formsemestre_etud_desinscrit(formsemestre_id: int, etudid: int):
|
||||
"""Désinscrit l'étudiant de ce formsemestre et TOUS ses modules"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
app.set_sco_dept(formsemestre.departement.acronym)
|
||||
etud = Identite.get_etud(etudid)
|
||||
do_formsemestre_desinscription(etud.id, formsemestre.id)
|
||||
app.log(f"formsemestre_etud_desinscrit: {etud} désinscrit de {formsemestre}")
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
||||
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/etat_evals")
|
||||
@login_required
|
||||
@ -634,37 +604,9 @@ def formsemestre_etat_evaluations(formsemestre_id: int):
|
||||
"""
|
||||
Informations sur l'état des évaluations d'un formsemestre.
|
||||
|
||||
Exemple de résultat :
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1, // moduleimpl_id
|
||||
"titre": "Initiation aux réseaux informatiques",
|
||||
"evaluations": [
|
||||
{
|
||||
"id": 1,
|
||||
"description": null,
|
||||
"datetime_epreuve": null,
|
||||
"heure_fin": "09:00:00",
|
||||
"coefficient": "02.00"
|
||||
"is_complete": true,
|
||||
"nb_inscrits": 16,
|
||||
"nb_manquantes": 0,
|
||||
"ABS": 0,
|
||||
"ATT": 0,
|
||||
"EXC": 0,
|
||||
"saisie_notes": {
|
||||
"datetime_debut": "2021-09-11T00:00:00+02:00",
|
||||
"datetime_fin": "2022-08-25T00:00:00+02:00",
|
||||
"datetime_mediane": "2022-03-19T00:00:00+01:00"
|
||||
}
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
]
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/etat_evals
|
||||
"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
app.set_sco_dept(formsemestre.departement.acronym)
|
||||
@ -749,16 +691,16 @@ def formsemestre_resultat(formsemestre_id: int):
|
||||
-----
|
||||
format:<string:format>
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/resultats;
|
||||
"""
|
||||
format_spec = request.args.get("format", None)
|
||||
if format_spec is not None and format_spec != "raw":
|
||||
return json_error(API_CLIENT_ERROR, "invalid format specification")
|
||||
convert_values = format_spec != "raw"
|
||||
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
app.set_sco_dept(formsemestre.departement.acronym)
|
||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
# Ajoute le groupe de chaque partition,
|
||||
@ -796,10 +738,7 @@ def formsemestre_resultat(formsemestre_id: int):
|
||||
@as_json
|
||||
def groups_get_auto_assignment(formsemestre_id: int):
|
||||
"""Rend les données stockées par `groups_save_auto_assignment`."""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
response = make_response(formsemestre.groups_auto_assignment_data or b"")
|
||||
response.headers["Content-Type"] = scu.JSON_MIMETYPE
|
||||
return response
|
||||
@ -819,11 +758,7 @@ def groups_save_auto_assignment(formsemestre_id: int):
|
||||
"""Enregistre les données, associées à ce formsemestre.
|
||||
Usage réservé aux fonctions de gestion des groupes, ne pas utiliser ailleurs.
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
if not formsemestre.can_change_groups():
|
||||
return json_error(403, "non autorisé (can_change_groups)")
|
||||
|
||||
@ -832,6 +767,7 @@ def groups_save_auto_assignment(formsemestre_id: int):
|
||||
formsemestre.groups_auto_assignment_data = request.data
|
||||
db.session.add(formsemestre)
|
||||
db.session.commit()
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>/edt")
|
||||
@ -852,12 +788,101 @@ def formsemestre_edt(formsemestre_id: int):
|
||||
group_ids : string (optionnel) filtre sur les groupes ScoDoc.
|
||||
show_modules_titles: show_modules_titles affiche le titre complet du module (défaut), sinon juste le code.
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
group_ids = request.args.getlist("group_ids", int)
|
||||
show_modules_titles = scu.to_bool(request.args.get("show_modules_titles", False))
|
||||
return sco_edt_cal.formsemestre_edt_dict(
|
||||
formsemestre, group_ids=group_ids, show_modules_titles=show_modules_titles
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>/description")
|
||||
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/description")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def formsemestre_get_description(formsemestre_id: int):
|
||||
"""Description externe du formsemestre. Peut être vide.
|
||||
|
||||
formsemestre_id : l'id du formsemestre
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/description
|
||||
"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
return formsemestre.description.to_dict() if formsemestre.description else {}
|
||||
|
||||
|
||||
@bp.post("/formsemestre/<int:formsemestre_id>/description/edit")
|
||||
@api_web_bp.post("/formsemestre/<int:formsemestre_id>/description/edit")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def formsemestre_edit_description(formsemestre_id: int):
|
||||
"""Modifie description externe du formsemestre.
|
||||
Les images peuvent êtres passées dans el json, encodées en base64.
|
||||
formsemestre_id : l'id du formsemestre
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/<int:formsemestre_id>/description/edit;{""description"":""descriptif du semestre"", ""dispositif"":1}
|
||||
"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||
if not formsemestre.description:
|
||||
formsemestre.description = FormSemestreDescription()
|
||||
# Decode images (base64)
|
||||
for key in ["image", "photo_ens"]:
|
||||
if key in args:
|
||||
args[key] = base64.b64decode(args[key])
|
||||
formsemestre.description.from_dict(args)
|
||||
db.session.commit()
|
||||
return formsemestre.description.to_dict()
|
||||
|
||||
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>/description/image")
|
||||
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/description/image")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def formsemestre_get_description_image(formsemestre_id: int):
|
||||
"""Image de la description externe du formsemestre. Peut être vide.
|
||||
|
||||
formsemestre_id : l'id du formsemestre
|
||||
"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
if not formsemestre.description or not formsemestre.description.image:
|
||||
return make_response("", 204) # 204 No Content status
|
||||
|
||||
return _image_response(formsemestre.description.image)
|
||||
|
||||
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>/description/photo_ens")
|
||||
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/description/photo_ens")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def formsemestre_get_photo_ens(formsemestre_id: int):
|
||||
"""Photo du responsable, ou illustration du formsemestre. Peut être vide."""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
if not formsemestre.description or not formsemestre.description.photo_ens:
|
||||
return make_response("", 204) # 204 No Content status
|
||||
|
||||
return _image_response(formsemestre.description.photo_ens)
|
||||
|
||||
|
||||
def _image_response(image_data: bytes):
|
||||
# Guess the mimetype based on the image data
|
||||
try:
|
||||
image = PIL.Image.open(io.BytesIO(image_data))
|
||||
mimetype = image.get_format_mimetype()
|
||||
except PIL.UnidentifiedImageError:
|
||||
# Default to binary stream if mimetype cannot be determined
|
||||
mimetype = "application/octet-stream"
|
||||
|
||||
response = make_response(image_data)
|
||||
response.headers["Content-Type"] = mimetype
|
||||
return response
|
||||
|
@ -53,7 +53,12 @@ from app.scodoc.sco_utils import json_error
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def decisions_jury(formsemestre_id: int):
|
||||
"""Décisions du jury des étudiants du formsemestre."""
|
||||
"""Décisions du jury des étudiants du formsemestre.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/decisions_jury
|
||||
"""
|
||||
# APC, pair:
|
||||
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
||||
if formsemestre is None:
|
||||
|
@ -165,7 +165,7 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
|
||||
def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
||||
"""
|
||||
Renvoie tous les justificatifs d'un département
|
||||
(en ajoutant un champ "formsemestre" si possible)
|
||||
(en ajoutant un champ "`formsemestre`" si possible).
|
||||
|
||||
QUERY
|
||||
-----
|
||||
@ -195,7 +195,7 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
||||
"""
|
||||
|
||||
# Récupération du département et des étudiants du département
|
||||
dept: Departement = Departement.query.get(dept_id)
|
||||
dept: Departement = db.session.get(Departement, dept_id)
|
||||
if dept is None:
|
||||
return json_error(404, "Assiduité non existante")
|
||||
etuds: list[int] = [etud.id for etud in dept.etudiants]
|
||||
@ -220,9 +220,9 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
||||
|
||||
def _set_sems(justi: Justificatif, restrict: bool) -> dict:
|
||||
"""
|
||||
_set_sems Ajoute le formsemestre associé au justificatif s'il existe
|
||||
_set_sems Ajoute le formsemestre associé au justificatif s'il existe.
|
||||
|
||||
Si le formsemestre n'existe pas, renvoie la simple représentation du justificatif
|
||||
Si le formsemestre n'existe pas, renvoie la simple représentation du justificatif.
|
||||
|
||||
Args:
|
||||
justi (Justificatif): Le justificatif
|
||||
@ -263,7 +263,7 @@ def _set_sems(justi: Justificatif, restrict: bool) -> dict:
|
||||
@as_json
|
||||
@permission_required(Permission.ScoView)
|
||||
def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False):
|
||||
"""Retourne tous les justificatifs du formsemestre
|
||||
"""Retourne tous les justificatifs du formsemestre.
|
||||
|
||||
QUERY
|
||||
-----
|
||||
@ -337,7 +337,7 @@ def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False):
|
||||
@permission_required(Permission.AbsChange)
|
||||
def justif_create(etudid: int = None, nip=None, ine=None):
|
||||
"""
|
||||
Création d'un justificatif pour l'étudiant (etudid)
|
||||
Création d'un justificatif pour l'étudiant.
|
||||
|
||||
DATA
|
||||
----
|
||||
@ -489,7 +489,7 @@ def _create_one(
|
||||
@permission_required(Permission.AbsChange)
|
||||
def justif_edit(justif_id: int):
|
||||
"""
|
||||
Edition d'un justificatif à partir de son id
|
||||
Édition d'un justificatif à partir de son id.
|
||||
|
||||
DATA
|
||||
----
|
||||
@ -611,7 +611,7 @@ def justif_edit(justif_id: int):
|
||||
@permission_required(Permission.AbsChange)
|
||||
def justif_delete():
|
||||
"""
|
||||
Suppression d'un justificatif à partir de son id
|
||||
Suppression d'un justificatif à partir de son id.
|
||||
|
||||
DATA
|
||||
----
|
||||
@ -699,7 +699,7 @@ def _delete_one(justif_id: int) -> tuple[int, str]:
|
||||
@permission_required(Permission.AbsChange)
|
||||
def justif_import(justif_id: int = None):
|
||||
"""
|
||||
Importation d'un fichier (création d'archive)
|
||||
Importation d'un fichier (création d'archive).
|
||||
|
||||
> Procédure d'importation de fichier : [importer un justificatif](FichiersJustificatifs.md#importer-un-fichier)
|
||||
"""
|
||||
@ -752,7 +752,8 @@ def justif_import(justif_id: int = None):
|
||||
def justif_export(justif_id: int | None = None, filename: str | None = None):
|
||||
"""
|
||||
Retourne un fichier d'une archive d'un justificatif.
|
||||
La permission est ScoView + (AbsJustifView ou être l'auteur du justifcatif)
|
||||
|
||||
La permission est `ScoView` + (`AbsJustifView` ou être l'auteur du justificatif).
|
||||
|
||||
> Procédure de téléchargement de fichier : [télécharger un justificatif](FichiersJustificatifs.md#télécharger-un-fichier)
|
||||
"""
|
||||
@ -764,7 +765,7 @@ def justif_export(justif_id: int | None = None, filename: str | None = None):
|
||||
current_user.has_permission(Permission.AbsJustifView)
|
||||
or justificatif_unique.user_id == current_user.id
|
||||
):
|
||||
return json_error(401, "non autorisé à voir ce fichier")
|
||||
return json_error(403, "non autorisé à voir ce fichier")
|
||||
|
||||
# On récupère l'archive concernée
|
||||
archive_name: str = justificatif_unique.fichier
|
||||
@ -791,7 +792,7 @@ def justif_export(justif_id: int | None = None, filename: str | None = None):
|
||||
@permission_required(Permission.AbsChange)
|
||||
def justif_remove(justif_id: int = None):
|
||||
"""
|
||||
Supression d'un fichier ou d'une archive
|
||||
Supression d'un fichier ou d'une archive.
|
||||
|
||||
> Procédure de suppression de fichier : [supprimer un justificatif](FichiersJustificatifs.md#supprimer-un-fichier)
|
||||
|
||||
@ -870,7 +871,7 @@ def justif_remove(justif_id: int = None):
|
||||
@permission_required(Permission.ScoView)
|
||||
def justif_list(justif_id: int = None):
|
||||
"""
|
||||
Liste les fichiers du justificatif
|
||||
Liste les fichiers du justificatif.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
@ -917,7 +918,7 @@ def justif_list(justif_id: int = None):
|
||||
@permission_required(Permission.AbsChange)
|
||||
def justif_justifies(justif_id: int = None):
|
||||
"""
|
||||
Liste assiduite_id justifiées par le justificatif
|
||||
Liste `assiduite_id` justifiées par le justificatif.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
|
@ -50,7 +50,12 @@ from app.scodoc.sco_utils import json_error
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
@as_json
|
||||
def logo_list_globals():
|
||||
"""Liste des noms des logos définis pour le site ScoDoc."""
|
||||
"""Liste des noms des logos définis pour le site ScoDoc.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/logos
|
||||
"""
|
||||
logos = list_logos()[None]
|
||||
return list(logos.keys())
|
||||
|
||||
@ -63,6 +68,10 @@ def logo_get_global(logoname):
|
||||
|
||||
L'image est au format png ou jpg; le format retourné dépend du format sous lequel
|
||||
l'image a été initialement enregistrée.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/logo/B
|
||||
"""
|
||||
logo = find_logo(logoname=logoname)
|
||||
if logo is None:
|
||||
@ -80,15 +89,19 @@ def _core_get_logos(dept_id) -> list:
|
||||
return list(logos.keys())
|
||||
|
||||
|
||||
@bp.route("/departement/<string:departement>/logos")
|
||||
@bp.route("/departement/<string:dept_acronym>/logos")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
@as_json
|
||||
def logo_get_local_by_acronym(departement):
|
||||
def departement_logos(dept_acronym: str):
|
||||
"""Liste des noms des logos définis pour le département
|
||||
désigné par son acronyme.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/departement/TAPI/logos
|
||||
"""
|
||||
dept_id = Departement.from_acronym(departement).id
|
||||
dept_id = Departement.from_acronym(dept_acronym).id
|
||||
return _core_get_logos(dept_id)
|
||||
|
||||
|
||||
@ -96,7 +109,7 @@ def logo_get_local_by_acronym(departement):
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
@as_json
|
||||
def logo_get_local_by_id(dept_id):
|
||||
def departement_logos_by_id(dept_id):
|
||||
"""Liste des noms des logos définis pour le département
|
||||
désigné par son id.
|
||||
"""
|
||||
|
@ -16,12 +16,15 @@ from flask_json import as_json
|
||||
from flask_login import login_required
|
||||
|
||||
import app
|
||||
from app import db
|
||||
from app.api import api_bp as bp, api_web_bp
|
||||
from app.api import api_permission_required as permission_required
|
||||
from app.decorators import scodoc
|
||||
from app.models import ModuleImpl
|
||||
from app.scodoc import sco_liste_notes
|
||||
from app.models import Identite, ModuleImpl, ModuleImplInscription
|
||||
from app.scodoc import sco_cache, sco_liste_notes
|
||||
from app.scodoc.sco_moduleimpl import do_moduleimpl_inscrit_etuds
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_utils import json_error
|
||||
|
||||
|
||||
@bp.route("/moduleimpl/<int:moduleimpl_id>")
|
||||
@ -38,37 +41,9 @@ def moduleimpl(moduleimpl_id: int):
|
||||
------
|
||||
moduleimpl_id : l'id d'un moduleimpl
|
||||
|
||||
Exemple de résultat :
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"formsemestre_id": 1,
|
||||
"module_id": 1,
|
||||
"responsable_id": 2,
|
||||
"moduleimpl_id": 1,
|
||||
"ens": [],
|
||||
"module": {
|
||||
"heures_tp": 0,
|
||||
"code_apogee": "",
|
||||
"titre": "Initiation aux réseaux informatiques",
|
||||
"coefficient": 1,
|
||||
"module_type": 2,
|
||||
"id": 1,
|
||||
"ects": null,
|
||||
"abbrev": "Init aux réseaux informatiques",
|
||||
"ue_id": 1,
|
||||
"code": "R101",
|
||||
"formation_id": 1,
|
||||
"heures_cours": 0,
|
||||
"matiere_id": 1,
|
||||
"heures_td": 0,
|
||||
"semestre_id": 1,
|
||||
"numero": 10,
|
||||
"module_id": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/moduleimpl/1
|
||||
"""
|
||||
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||
return modimpl.to_dict(convert_objects=True)
|
||||
@ -83,23 +58,68 @@ def moduleimpl(moduleimpl_id: int):
|
||||
def moduleimpl_inscriptions(moduleimpl_id: int):
|
||||
"""Liste des inscriptions à ce moduleimpl.
|
||||
|
||||
Exemple de résultat :
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"etudid": 666,
|
||||
"moduleimpl_id": 1234,
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/moduleimpl/1/inscriptions
|
||||
"""
|
||||
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||
return [i.to_dict() for i in modimpl.inscriptions]
|
||||
|
||||
|
||||
@bp.post("/moduleimpl/<int:moduleimpl_id>/etudid/<int:etudid>/inscrit")
|
||||
@api_web_bp.post("/moduleimpl/<int:moduleimpl_id>/etudid/<int:etudid>/inscrit")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def moduleimpl_etud_inscrit(moduleimpl_id: int, etudid: int):
|
||||
"""Inscrit l'étudiant à ce moduleimpl.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/moduleimpl/1/etudid/2/inscrit
|
||||
"""
|
||||
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||
if not modimpl.can_change_inscriptions():
|
||||
return json_error(403, "opération non autorisée")
|
||||
etud = Identite.get_etud(etudid)
|
||||
do_moduleimpl_inscrit_etuds(modimpl.id, modimpl.formsemestre_id, [etud.id])
|
||||
app.log(f"moduleimpl_etud_inscrit: {etud} inscrit à {modimpl}")
|
||||
return (
|
||||
ModuleImplInscription.query.filter_by(moduleimpl_id=modimpl.id, etudid=etud.id)
|
||||
.first()
|
||||
.to_dict()
|
||||
)
|
||||
|
||||
|
||||
@bp.post("/moduleimpl/<int:moduleimpl_id>/etudid/<int:etudid>/desinscrit")
|
||||
@api_web_bp.post("/moduleimpl/<int:moduleimpl_id>/etudid/<int:etudid>/desinscrit")
|
||||
@login_required
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def moduleimpl_etud_desinscrit(moduleimpl_id: int, etudid: int):
|
||||
"""Désinscrit l'étudiant de ce moduleimpl.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/moduleimpl/1/etudid/2/desinscrit
|
||||
"""
|
||||
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||
if not modimpl.can_change_inscriptions():
|
||||
return json_error(403, "opération non autorisée")
|
||||
etud = Identite.get_etud(etudid)
|
||||
inscription = ModuleImplInscription.query.filter_by(
|
||||
etudid=etud.id, moduleimpl_id=modimpl.id
|
||||
).first()
|
||||
if inscription:
|
||||
db.session.delete(inscription)
|
||||
db.session.commit()
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=modimpl.formsemestre_id)
|
||||
app.log(f"moduleimpl_etud_desinscrit: {etud} inscrit à {modimpl}")
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@bp.route("/moduleimpl/<int:moduleimpl_id>/notes")
|
||||
@api_web_bp.route("/moduleimpl/<int:moduleimpl_id>/notes")
|
||||
@login_required
|
||||
@ -108,24 +128,9 @@ def moduleimpl_inscriptions(moduleimpl_id: int):
|
||||
def moduleimpl_notes(moduleimpl_id: int):
|
||||
"""Liste des notes dans ce moduleimpl.
|
||||
|
||||
Exemple de résultat :
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"etudid": 17776, // code de l'étudiant
|
||||
"nom": "DUPONT",
|
||||
"prenom": "Luz",
|
||||
"38411": 16.0, // Note dans l'évaluation d'id 38411
|
||||
"38410": 15.0,
|
||||
"moymod": 15.5, // Moyenne INDICATIVE module
|
||||
"moy_ue_2875": 15.5, // Moyenne vers l'UE 2875
|
||||
"moy_ue_2876": 15.5, // Moyenne vers l'UE 2876
|
||||
"moy_ue_2877": 15.5 // Moyenne vers l'UE 2877
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/moduleimpl/1/notes
|
||||
"""
|
||||
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||
app.set_sco_dept(modimpl.formsemestre.departement.acronym)
|
||||
|
@ -45,23 +45,9 @@ from app.scodoc import sco_utils as scu
|
||||
def partition_info(partition_id: int):
|
||||
"""Info sur une partition.
|
||||
|
||||
Exemple de résultat :
|
||||
|
||||
```json
|
||||
{
|
||||
'bul_show_rank': False,
|
||||
'formsemestre_id': 39,
|
||||
'groups': [
|
||||
{'id': 268, 'name': 'A', 'partition_id': 100},
|
||||
{'id': 269, 'name': 'B', 'partition_id': 100}
|
||||
],
|
||||
'groups_editable': True,
|
||||
'id': 100,
|
||||
'numero': 100,
|
||||
'partition_name': 'TD',
|
||||
'show_in_lists': True
|
||||
}
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/partition/1
|
||||
"""
|
||||
query = Partition.query.filter_by(id=partition_id)
|
||||
if g.scodoc_dept:
|
||||
@ -79,23 +65,9 @@ def partition_info(partition_id: int):
|
||||
def formsemestre_partitions(formsemestre_id: int):
|
||||
"""Liste de toutes les partitions d'un formsemestre.
|
||||
|
||||
Exemple de résultat :
|
||||
|
||||
```json
|
||||
{
|
||||
partition_id : {
|
||||
"bul_show_rank": False,
|
||||
"formsemestre_id": 1063,
|
||||
"groups" :
|
||||
group_id : {
|
||||
"id" : 12,
|
||||
"name" : "A",
|
||||
"partition_id" : partition_id,
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/formsemestre/1/partitions
|
||||
"""
|
||||
query = FormSemestre.query.filter_by(id=formsemestre_id)
|
||||
if g.scodoc_dept:
|
||||
@ -124,22 +96,9 @@ def group_etudiants(group_id: int):
|
||||
------
|
||||
group_id : l'id d'un groupe
|
||||
|
||||
Exemple de résultat :
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
'civilite': 'M',
|
||||
'id': 123456,
|
||||
'ine': None,
|
||||
'nip': '987654321',
|
||||
'nom': 'MARTIN',
|
||||
'nom_usuel': null,
|
||||
'prenom': 'JEAN'}
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
SAMPLES
|
||||
-------
|
||||
/group/1/etudiants
|
||||
"""
|
||||
query = GroupDescr.query.filter_by(id=group_id)
|
||||
if g.scodoc_dept:
|
||||
@ -210,7 +169,7 @@ def group_set_etudiant(group_id: int, etudid: int):
|
||||
if not group.partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
if not group.partition.formsemestre.can_change_groups():
|
||||
return json_error(401, "opération non autorisée")
|
||||
return json_error(403, "opération non autorisée")
|
||||
if etud.id not in {e.id for e in group.partition.formsemestre.etuds}:
|
||||
return json_error(404, "etud non inscrit au formsemestre du groupe")
|
||||
|
||||
@ -243,7 +202,7 @@ def group_remove_etud(group_id: int, etudid: int):
|
||||
if not group.partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
if not group.partition.formsemestre.can_change_groups():
|
||||
return json_error(401, "opération non autorisée")
|
||||
return json_error(403, "opération non autorisée")
|
||||
|
||||
group.remove_etud(etud)
|
||||
|
||||
@ -273,7 +232,7 @@ def partition_remove_etud(partition_id: int, etudid: int):
|
||||
if not partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
if not partition.formsemestre.can_change_groups():
|
||||
return json_error(401, "opération non autorisée")
|
||||
return json_error(403, "opération non autorisée")
|
||||
db.session.execute(
|
||||
sa.text(
|
||||
"""DELETE FROM group_membership
|
||||
@ -316,6 +275,10 @@ def group_create(partition_id: int): # partition-group-create
|
||||
"group_name" : nom_du_groupe,
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/partition/1/group/create;{""group_name"" : ""Nouveau Groupe""}
|
||||
"""
|
||||
query = Partition.query.filter_by(id=partition_id)
|
||||
if g.scodoc_dept:
|
||||
@ -326,7 +289,7 @@ def group_create(partition_id: int): # partition-group-create
|
||||
if not partition.groups_editable:
|
||||
return json_error(403, "partition non editable")
|
||||
if not partition.formsemestre.can_change_groups():
|
||||
return json_error(401, "opération non autorisée")
|
||||
return json_error(403, "opération non autorisée")
|
||||
|
||||
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||
group_name = args.get("group_name")
|
||||
@ -374,7 +337,7 @@ def group_delete(group_id: int):
|
||||
if not group.partition.groups_editable:
|
||||
return json_error(403, "partition non editable")
|
||||
if not group.partition.formsemestre.can_change_groups():
|
||||
return json_error(401, "opération non autorisée")
|
||||
return json_error(403, "opération non autorisée")
|
||||
formsemestre_id = group.partition.formsemestre_id
|
||||
log(f"deleting {group}")
|
||||
db.session.delete(group)
|
||||
@ -391,7 +354,19 @@ def group_delete(group_id: int):
|
||||
@permission_required(Permission.ScoView)
|
||||
@as_json
|
||||
def group_edit(group_id: int):
|
||||
"""Édition d'un groupe."""
|
||||
"""Édition d'un groupe.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"group_name" : "A1"
|
||||
}
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/group/1/edit;{""group_name"":""A1""}
|
||||
"""
|
||||
query = GroupDescr.query.filter_by(id=group_id)
|
||||
if g.scodoc_dept:
|
||||
query = (
|
||||
@ -403,7 +378,7 @@ def group_edit(group_id: int):
|
||||
if not group.partition.groups_editable:
|
||||
return json_error(403, "partition non editable")
|
||||
if not group.partition.formsemestre.can_change_groups():
|
||||
return json_error(401, "opération non autorisée")
|
||||
return json_error(403, "opération non autorisée")
|
||||
|
||||
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||
if "group_name" in args:
|
||||
@ -436,6 +411,10 @@ def group_set_edt_id(group_id: int, edt_id: str):
|
||||
|
||||
Contrairement à `/edit`, peut-être changé pour toute partition
|
||||
d'un formsemestre non verrouillé.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/group/1/set_edt_id/EDT_GR1
|
||||
"""
|
||||
query = GroupDescr.query.filter_by(id=group_id)
|
||||
if g.scodoc_dept:
|
||||
@ -444,7 +423,7 @@ def group_set_edt_id(group_id: int, edt_id: str):
|
||||
)
|
||||
group: GroupDescr = query.first_or_404()
|
||||
if not group.partition.formsemestre.can_change_groups():
|
||||
return json_error(401, "opération non autorisée")
|
||||
return json_error(403, "opération non autorisée")
|
||||
log(f"group_set_edt_id( {group_id}, '{edt_id}' )")
|
||||
group.edt_id = edt_id
|
||||
db.session.add(group)
|
||||
@ -482,7 +461,7 @@ def partition_create(formsemestre_id: int):
|
||||
if not formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
if not formsemestre.can_change_groups():
|
||||
return json_error(401, "opération non autorisée")
|
||||
return json_error(403, "opération non autorisée")
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
partition_name = data.get("partition_name")
|
||||
if partition_name is None:
|
||||
@ -544,7 +523,7 @@ def formsemestre_set_partitions_order(formsemestre_id: int):
|
||||
if not formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
if not formsemestre.can_change_groups():
|
||||
return json_error(401, "opération non autorisée")
|
||||
return json_error(403, "opération non autorisée")
|
||||
partition_ids = request.get_json(force=True) # may raise 400 Bad Request
|
||||
if not isinstance(partition_ids, list) and not all(
|
||||
isinstance(x, int) for x in partition_ids
|
||||
@ -590,7 +569,7 @@ def partition_order_groups(partition_id: int):
|
||||
if not partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
if not partition.formsemestre.can_change_groups():
|
||||
return json_error(401, "opération non autorisée")
|
||||
return json_error(403, "opération non autorisée")
|
||||
group_ids = request.get_json(force=True) # may raise 400 Bad Request
|
||||
if not isinstance(group_ids, list) and not all(
|
||||
isinstance(x, int) for x in group_ids
|
||||
@ -632,6 +611,10 @@ def partition_edit(partition_id: int):
|
||||
"groups_editable":bool
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/partition/1/edit;{""bul_show_rank"":1}
|
||||
"""
|
||||
query = Partition.query.filter_by(id=partition_id)
|
||||
if g.scodoc_dept:
|
||||
@ -640,7 +623,7 @@ def partition_edit(partition_id: int):
|
||||
if not partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
if not partition.formsemestre.can_change_groups():
|
||||
return json_error(401, "opération non autorisée")
|
||||
return json_error(403, "opération non autorisée")
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
modified = False
|
||||
partition_name = data.get("partition_name")
|
||||
@ -666,9 +649,8 @@ def partition_edit(partition_id: int):
|
||||
|
||||
for boolean_field in ("bul_show_rank", "show_in_lists", "groups_editable"):
|
||||
value = data.get(boolean_field)
|
||||
value = scu.to_bool(value) if value is not None else None
|
||||
if value is not None and value != getattr(partition, boolean_field):
|
||||
if not isinstance(value, bool):
|
||||
return json_error(API_CLIENT_ERROR, f"invalid type for {boolean_field}")
|
||||
if boolean_field == "groups_editable" and partition.is_parcours():
|
||||
return json_error(
|
||||
API_CLIENT_ERROR, f"can't change {scu.PARTITION_PARCOURS}"
|
||||
@ -707,7 +689,7 @@ def partition_delete(partition_id: int):
|
||||
if not partition.formsemestre.etat:
|
||||
return json_error(403, "formsemestre verrouillé")
|
||||
if not partition.formsemestre.can_change_groups():
|
||||
return json_error(401, "opération non autorisée")
|
||||
return json_error(403, "opération non autorisée")
|
||||
if not partition.partition_name:
|
||||
return json_error(
|
||||
API_CLIENT_ERROR, "ne peut pas supprimer la partition par défaut"
|
||||
|
@ -37,6 +37,10 @@ from app.scodoc.sco_utils import json_error
|
||||
def user_info(uid: int):
|
||||
"""
|
||||
Info sur un compte utilisateur ScoDoc.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/user/2
|
||||
"""
|
||||
user: User = db.session.get(User, uid)
|
||||
if user is None:
|
||||
@ -222,14 +226,19 @@ def user_edit(uid: int):
|
||||
def user_password(uid: int):
|
||||
"""Modification du mot de passe d'un utilisateur.
|
||||
|
||||
Champs modifiables:
|
||||
Si le mot de passe ne convient pas, erreur 400.
|
||||
|
||||
DATA
|
||||
----
|
||||
```json
|
||||
{
|
||||
"password": str
|
||||
}
|
||||
```.
|
||||
```
|
||||
|
||||
Si le mot de passe ne convient pas, erreur 400.
|
||||
SAMPLES
|
||||
-------
|
||||
/user/3/password;{""password"" : ""rePlaCemeNT456averylongandcomplicated""}
|
||||
"""
|
||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||
user: User = User.query.get_or_404(uid)
|
||||
@ -318,7 +327,12 @@ def user_role_remove(uid: int, role_name: str, dept: str = None):
|
||||
@permission_required(Permission.UsersView)
|
||||
@as_json
|
||||
def permissions_list():
|
||||
"""Liste des noms de permissions définies."""
|
||||
"""Liste des noms de permissions définies.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/permissions
|
||||
"""
|
||||
return list(Permission.permission_by_name.keys())
|
||||
|
||||
|
||||
@ -329,7 +343,12 @@ def permissions_list():
|
||||
@permission_required(Permission.UsersView)
|
||||
@as_json
|
||||
def role_get(role_name: str):
|
||||
"""Un rôle"""
|
||||
"""Un rôle.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/role/Ens
|
||||
"""
|
||||
return Role.query.filter_by(name=role_name).first_or_404().to_dict()
|
||||
|
||||
|
||||
@ -340,7 +359,12 @@ def role_get(role_name: str):
|
||||
@permission_required(Permission.UsersView)
|
||||
@as_json
|
||||
def roles_list():
|
||||
"""Tous les rôles définis."""
|
||||
"""Tous les rôles définis.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/roles
|
||||
"""
|
||||
return [role.to_dict() for role in Role.query]
|
||||
|
||||
|
||||
@ -410,6 +434,10 @@ def role_create(role_name: str):
|
||||
"permissions" : [ 'ScoView', ... ]
|
||||
}
|
||||
```
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/role/create/customRole;{""permissions"": [""ScoView"", ""UsersView""]}
|
||||
"""
|
||||
role: Role = Role.query.filter_by(name=role_name).first()
|
||||
if role:
|
||||
@ -471,7 +499,12 @@ def role_edit(role_name: str):
|
||||
@permission_required(Permission.ScoSuperAdmin)
|
||||
@as_json
|
||||
def role_delete(role_name: str):
|
||||
"""Suprression d'un rôle."""
|
||||
"""Suppression d'un rôle.
|
||||
|
||||
SAMPLES
|
||||
-------
|
||||
/role/customRole/delete
|
||||
"""
|
||||
role: Role = Role.query.filter_by(name=role_name).first_or_404()
|
||||
db.session.delete(role)
|
||||
db.session.commit()
|
||||
|
@ -445,9 +445,10 @@ class User(UserMixin, ScoDocModel):
|
||||
|
||||
def set_roles(self, roles, dept):
|
||||
"set roles in the given dept"
|
||||
self.user_roles = [
|
||||
UserRole(user=self, role=r, dept=dept) for r in roles if isinstance(r, Role)
|
||||
]
|
||||
self.user_roles = []
|
||||
for r in roles:
|
||||
if isinstance(r, Role):
|
||||
self.add_role(r, dept)
|
||||
|
||||
def get_roles(self):
|
||||
"iterator on my roles"
|
||||
|
@ -44,7 +44,9 @@ def formation_change_referentiel(
|
||||
ue.niveau_competence_id = niveaux_map[ue.niveau_competence_id]
|
||||
db.session.add(ue)
|
||||
if ue.parcours:
|
||||
new_list = [ApcParcours.query.get(parcours_map[p.id]) for p in ue.parcours]
|
||||
new_list = [
|
||||
db.session.get(ApcParcours, parcours_map[p.id]) for p in ue.parcours
|
||||
]
|
||||
ue.parcours.clear()
|
||||
ue.parcours.extend(new_list)
|
||||
db.session.add(ue)
|
||||
@ -52,7 +54,7 @@ def formation_change_referentiel(
|
||||
for module in formation.modules:
|
||||
if module.parcours:
|
||||
new_list = [
|
||||
ApcParcours.query.get(parcours_map[p.id]) for p in module.parcours
|
||||
db.session.get(ApcParcours, parcours_map[p.id]) for p in module.parcours
|
||||
]
|
||||
module.parcours.clear()
|
||||
module.parcours.extend(new_list)
|
||||
@ -76,7 +78,8 @@ def formation_change_referentiel(
|
||||
# FormSemestre / parcours_formsemestre
|
||||
for formsemestre in formation.formsemestres:
|
||||
new_list = [
|
||||
ApcParcours.query.get(parcours_map[p.id]) for p in formsemestre.parcours
|
||||
db.session.get(ApcParcours, parcours_map[p.id])
|
||||
for p in formsemestre.parcours
|
||||
]
|
||||
formsemestre.parcours.clear()
|
||||
formsemestre.parcours.extend(new_list)
|
||||
|
@ -632,17 +632,23 @@ def formation_semestre_niveaux_warning(formation: Formation, semestre_idx: int)
|
||||
|
||||
def ue_associee_au_niveau_du_parcours(
|
||||
ues_possibles: list[UniteEns], niveau: ApcNiveau, sem_name: str = "S"
|
||||
) -> UniteEns:
|
||||
"L'UE associée à ce niveau, ou None"
|
||||
) -> tuple[UniteEns, str]:
|
||||
"""L'UE associée à ce niveau, ou None.
|
||||
Renvoie aussi un message d'avertissement en cas d'associations multiples
|
||||
(en principe un niveau ne doit être associé qu'à une seule UE)
|
||||
"""
|
||||
ues = [ue for ue in ues_possibles if ue.niveau_competence_id == niveau.id]
|
||||
msg = ""
|
||||
if len(ues) > 1:
|
||||
msg = f"""{' et '.join(ue.acronyme for ue in ues)}
|
||||
associées au niveau {niveau} / {sem_name}. Utilisez le cas échéant l'item "Désassocier"."""
|
||||
# plusieurs UEs associées à ce niveau: élimine celles sans parcours
|
||||
ues_pair_avec_parcours = [ue for ue in ues if ue.parcours]
|
||||
if ues_pair_avec_parcours:
|
||||
ues = ues_pair_avec_parcours
|
||||
ues_avec_parcours = [ue for ue in ues if ue.parcours]
|
||||
if ues_avec_parcours:
|
||||
ues = ues_avec_parcours
|
||||
if len(ues) > 1:
|
||||
log(f"_niveau_ues: {len(ues)} associées au niveau {niveau} / {sem_name}")
|
||||
return ues[0] if ues else None
|
||||
return ues[0] if ues else None, msg
|
||||
|
||||
|
||||
def parcour_formation_competences(
|
||||
@ -700,6 +706,7 @@ def parcour_formation_competences(
|
||||
"ue_impair": None,
|
||||
"ues_pair": [],
|
||||
"ues_impair": [],
|
||||
"warning": "",
|
||||
}
|
||||
# Toutes les UEs de la formation dans ce parcours ou tronc commun
|
||||
ues = [
|
||||
@ -715,10 +722,10 @@ def parcour_formation_competences(
|
||||
ues_impair_possibles = [ue for ue in ues if ue.semestre_idx == (2 * annee - 1)]
|
||||
|
||||
# UE associée au niveau dans ce parcours
|
||||
ue_pair = ue_associee_au_niveau_du_parcours(
|
||||
ue_pair, warning_pair = ue_associee_au_niveau_du_parcours(
|
||||
ues_pair_possibles, niveau, f"S{2*annee}"
|
||||
)
|
||||
ue_impair = ue_associee_au_niveau_du_parcours(
|
||||
ue_impair, warning_impair = ue_associee_au_niveau_du_parcours(
|
||||
ues_impair_possibles, niveau, f"S{2*annee-1}"
|
||||
)
|
||||
|
||||
@ -736,6 +743,7 @@ def parcour_formation_competences(
|
||||
for ue in ues_impair_possibles
|
||||
if (not ue.niveau_competence) or ue.niveau_competence.id == niveau.id
|
||||
],
|
||||
"warning": ", ".join(filter(None, [warning_pair, warning_impair])),
|
||||
}
|
||||
|
||||
competences = [
|
||||
|
@ -1557,8 +1557,8 @@ class DecisionsProposeesUE(DecisionsProposees):
|
||||
res: ResultatsSemestreBUT = (
|
||||
self.rcue.res_pair if paire else self.rcue.res_impair
|
||||
)
|
||||
self.moy_ue = np.NaN
|
||||
self.moy_ue_with_cap = np.NaN
|
||||
self.moy_ue = np.nan
|
||||
self.moy_ue_with_cap = np.nan
|
||||
self.ue_status = {}
|
||||
|
||||
if self.ue.type != sco_codes.UE_STANDARD:
|
||||
|
@ -105,7 +105,7 @@ def pvjury_page_but(formsemestre_id: int, fmt="html"):
|
||||
},
|
||||
xls_style_base=xls_style_base,
|
||||
)
|
||||
return tab.make_page(fmt=fmt, javascripts=["js/etud_info.js"], init_qtip=True)
|
||||
return tab.make_page(fmt=fmt)
|
||||
|
||||
|
||||
def pvjury_table_but(
|
||||
|
@ -541,17 +541,16 @@ def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
||||
ue_ids = [ue.id for ue in ues]
|
||||
evaluation_ids = [evaluation.id for evaluation in modimpl.evaluations]
|
||||
evals_poids = pd.DataFrame(columns=ue_ids, index=evaluation_ids, dtype=float)
|
||||
if (
|
||||
modimpl.module.module_type == ModuleType.RESSOURCE
|
||||
or modimpl.module.module_type == ModuleType.SAE
|
||||
):
|
||||
if modimpl.module.module_type in (ModuleType.RESSOURCE, ModuleType.SAE):
|
||||
for ue_poids in EvaluationUEPoids.query.join(
|
||||
EvaluationUEPoids.evaluation
|
||||
).filter_by(moduleimpl_id=moduleimpl_id):
|
||||
try:
|
||||
evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
|
||||
except KeyError:
|
||||
pass # poids vers des UE qui n'existent plus ou sont dans un autre semestre...
|
||||
if (
|
||||
ue_poids.evaluation_id in evals_poids.index
|
||||
and ue_poids.ue_id in evals_poids.columns
|
||||
):
|
||||
evals_poids.at[ue_poids.evaluation_id, ue_poids.ue_id] = ue_poids.poids
|
||||
# ignore poids vers des UEs qui n'existent plus ou sont dans un autre semestre...
|
||||
|
||||
# Initialise poids non enregistrés:
|
||||
default_poids = (
|
||||
@ -564,7 +563,7 @@ def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
||||
if np.isnan(evals_poids.values.flat).any():
|
||||
ue_coefs = modimpl.module.get_ue_coef_dict()
|
||||
for ue in ues:
|
||||
evals_poids[ue.id][evals_poids[ue.id].isna()] = (
|
||||
evals_poids.loc[evals_poids[ue.id].isna(), ue.id] = (
|
||||
1 if ue_coefs.get(ue.id, default_poids) > 0 else 0
|
||||
)
|
||||
|
||||
|
@ -82,7 +82,7 @@ def compute_sem_moys_apc_using_ects(
|
||||
moy_gen = (etud_moy_ue_df * ects).sum(axis=1) / ects.sum(axis=1)
|
||||
except ZeroDivisionError:
|
||||
# peut arriver si aucun module... on ignore
|
||||
moy_gen = pd.Series(np.NaN, index=etud_moy_ue_df.index)
|
||||
moy_gen = pd.Series(np.nan, index=etud_moy_ue_df.index)
|
||||
except TypeError:
|
||||
if None in ects:
|
||||
formation = db.session.get(Formation, formation_id)
|
||||
@ -93,7 +93,7 @@ def compute_sem_moys_apc_using_ects(
|
||||
scodoc_dept=g.scodoc_dept, formation_id=formation_id)}">{formation.get_titre_version()}</a>)"""
|
||||
)
|
||||
)
|
||||
moy_gen = pd.Series(np.NaN, index=etud_moy_ue_df.index)
|
||||
moy_gen = pd.Series(np.nan, index=etud_moy_ue_df.index)
|
||||
else:
|
||||
raise
|
||||
return moy_gen
|
||||
|
@ -92,7 +92,7 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
|
||||
|
||||
for mod_coef in query:
|
||||
if mod_coef.module_id in module_coefs_df:
|
||||
module_coefs_df[mod_coef.module_id][mod_coef.ue_id] = mod_coef.coef
|
||||
module_coefs_df.at[mod_coef.ue_id, mod_coef.module_id] = mod_coef.coef
|
||||
# silently ignore coefs associated to other modules (ie when module_type is changed)
|
||||
|
||||
# Initialisation des poids non fixés:
|
||||
@ -138,14 +138,16 @@ def df_load_modimpl_coefs(
|
||||
)
|
||||
|
||||
for mod_coef in mod_coefs:
|
||||
try:
|
||||
modimpl_coefs_df[mod2impl[mod_coef.module_id]][
|
||||
mod_coef.ue_id
|
||||
] = mod_coef.coef
|
||||
except IndexError:
|
||||
if (
|
||||
mod_coef.ue_id in modimpl_coefs_df.index
|
||||
and mod2impl[mod_coef.module_id] in modimpl_coefs_df.columns
|
||||
):
|
||||
modimpl_coefs_df.at[mod_coef.ue_id, mod2impl[mod_coef.module_id]] = (
|
||||
mod_coef.coef
|
||||
)
|
||||
# il peut y avoir en base des coefs sur des modules ou UE
|
||||
# qui ont depuis été retirés de la formation
|
||||
pass
|
||||
# qui ont depuis été retirés de la formation : on ignore ces coefs
|
||||
|
||||
# Initialisation des poids non fixés:
|
||||
# 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
|
||||
# sur toutes les UE)
|
||||
@ -178,7 +180,7 @@ def notes_sem_assemble_cube(modimpls_notes: list[pd.DataFrame]) -> np.ndarray:
|
||||
except ValueError:
|
||||
app.critical_error(
|
||||
f"""notes_sem_assemble_cube: shapes {
|
||||
", ".join([x.shape for x in modimpls_notes_arr])}"""
|
||||
", ".join([str(x.shape) for x in modimpls_notes_arr])}"""
|
||||
)
|
||||
return modimpls_notes.swapaxes(0, 1)
|
||||
|
||||
@ -299,7 +301,11 @@ def compute_ue_moys_apc(
|
||||
)
|
||||
# Les "dispenses" sont très peu nombreuses et traitées en python:
|
||||
for dispense_ue in dispense_ues:
|
||||
etud_moy_ue_df[dispense_ue[1]][dispense_ue[0]] = 0.0
|
||||
if (
|
||||
dispense_ue[0] in etud_moy_ue_df.columns
|
||||
and dispense_ue[1] in etud_moy_ue_df.index
|
||||
):
|
||||
etud_moy_ue_df.at[dispense_ue[1], dispense_ue[0]] = 0.0
|
||||
|
||||
return etud_moy_ue_df
|
||||
|
||||
|
@ -1 +1,23 @@
|
||||
# empty but required for pylint
|
||||
"""WTF Forms for ScoDoc
|
||||
"""
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
|
||||
|
||||
class ScoDocForm(FlaskForm):
|
||||
"""Super class for ScoDoc forms
|
||||
(inspired by @iziram)
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"Init form, adding a filed for our error messages"
|
||||
super().__init__(*args, **kwargs)
|
||||
self.ok = True
|
||||
self.error_messages: list[str] = [] # used to report our errors
|
||||
|
||||
def set_error(self, err_msg, field=None):
|
||||
"Set error message both in form and field"
|
||||
self.ok = False
|
||||
self.error_messages.append(err_msg)
|
||||
if field:
|
||||
field.errors.append(err_msg)
|
||||
|
112
app/forms/formsemestre/edit_description.py
Normal file
112
app/forms/formsemestre/edit_description.py
Normal file
@ -0,0 +1,112 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""Formulaire édition description formsemestre
|
||||
"""
|
||||
from wtforms import (
|
||||
BooleanField,
|
||||
FileField,
|
||||
SelectField,
|
||||
StringField,
|
||||
TextAreaField,
|
||||
SubmitField,
|
||||
)
|
||||
from wtforms.validators import AnyOf, Optional
|
||||
|
||||
from app.forms import ScoDocForm
|
||||
from app.models import FORMSEMESTRE_DISPOSITIFS
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
class DateDMYField(StringField):
|
||||
"Champ date JJ/MM/AAAA"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
render_kw = kwargs.pop("render_kw", {})
|
||||
render_kw.update({"class": "datepicker", "size": 10})
|
||||
super().__init__(*args, render_kw=render_kw, **kwargs)
|
||||
|
||||
# note: process_formdata(self, valuelist) ne fonctionne pas
|
||||
# en cas d'erreur de saisie les valeurs ne sont pas ré-affichées.
|
||||
# On vérifie donc les valeurs dans le code de la vue.
|
||||
|
||||
def process_data(self, value):
|
||||
"Process data from model to form"
|
||||
if value:
|
||||
self.data = value.strftime(scu.DATE_FMT)
|
||||
else:
|
||||
self.data = ""
|
||||
|
||||
|
||||
class FormSemestreDescriptionForm(ScoDocForm):
|
||||
"Formulaire édition description formsemestre"
|
||||
description = TextAreaField(
|
||||
"Description",
|
||||
validators=[Optional()],
|
||||
description="""texte libre : informations
|
||||
sur le contenu, les objectifs, les modalités d'évaluation, etc.""",
|
||||
)
|
||||
horaire = StringField(
|
||||
"Horaire", validators=[Optional()], description="ex: les lundis 9h-12h"
|
||||
)
|
||||
date_debut_inscriptions = DateDMYField(
|
||||
"Date de début des inscriptions",
|
||||
description="""date d'ouverture des inscriptions
|
||||
(laisser vide pour autoriser tout le temps)""",
|
||||
render_kw={
|
||||
"id": "date_debut_inscriptions",
|
||||
},
|
||||
)
|
||||
date_fin_inscriptions = DateDMYField(
|
||||
"Date de fin des inscriptions",
|
||||
render_kw={
|
||||
"id": "date_fin_inscriptions",
|
||||
},
|
||||
)
|
||||
image = FileField(
|
||||
"Image", validators=[Optional()], description="Image illustrant cette formation"
|
||||
)
|
||||
campus = StringField(
|
||||
"Campus", validators=[Optional()], description="ex: Villetaneuse"
|
||||
)
|
||||
salle = StringField("Salle", validators=[Optional()], description="ex: salle 123")
|
||||
dispositif = SelectField(
|
||||
"Dispositif",
|
||||
choices=FORMSEMESTRE_DISPOSITIFS.items(),
|
||||
coerce=int,
|
||||
description="modalité de formation",
|
||||
validators=[AnyOf(FORMSEMESTRE_DISPOSITIFS.keys())],
|
||||
)
|
||||
modalites_mcc = TextAreaField(
|
||||
"Modalités de contrôle des connaissances",
|
||||
validators=[Optional()],
|
||||
description="texte libre",
|
||||
)
|
||||
photo_ens = FileField(
|
||||
"Photo de l'enseignant(e)",
|
||||
validators=[Optional()],
|
||||
description="ou autre illustration",
|
||||
)
|
||||
public = StringField(
|
||||
"Public visé", validators=[Optional()], description="ex: débutants"
|
||||
)
|
||||
prerequis = TextAreaField(
|
||||
"Prérequis", validators=[Optional()], description="texte libre"
|
||||
)
|
||||
responsable = StringField(
|
||||
"Responsable",
|
||||
validators=[Optional()],
|
||||
description="""nom de l'enseignant de la formation, ou personne
|
||||
chargée de l'organisation du semestre.""",
|
||||
)
|
||||
|
||||
wip = BooleanField(
|
||||
"Travaux en cours",
|
||||
description="work in progress: si coché, affichera juste le titre du semestre",
|
||||
)
|
||||
|
||||
submit = SubmitField("Enregistrer")
|
||||
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
@ -14,12 +14,13 @@ class _EditModimplsCodesForm(FlaskForm):
|
||||
# construit dynamiquement ci-dessous
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def EditModimplsCodesForm(formsemestre: FormSemestre) -> _EditModimplsCodesForm:
|
||||
"Création d'un formulaire pour éditer les codes"
|
||||
|
||||
# Formulaire dynamique, on créé une classe ad-hoc
|
||||
class F(_EditModimplsCodesForm):
|
||||
pass
|
||||
"class factory"
|
||||
|
||||
def _gen_mod_form(modimpl: ModuleImpl):
|
||||
field = StringField(
|
||||
|
@ -39,7 +39,7 @@ from wtforms import ValidationError
|
||||
from wtforms.fields.simple import StringField, HiddenField
|
||||
|
||||
from app.models import Departement
|
||||
from app.scodoc import sco_logos, html_sco_header
|
||||
from app.scodoc import sco_logos
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
from app.scodoc.sco_config_actions import LogoInsert
|
||||
@ -47,10 +47,6 @@ from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_logos import find_logo
|
||||
|
||||
|
||||
JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + []
|
||||
|
||||
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
||||
|
||||
# class ItemForm(FlaskForm):
|
||||
# """Unused Generic class to document common behavior for classes
|
||||
# * ScoConfigurationForm
|
||||
@ -369,6 +365,7 @@ def _make_data(modele):
|
||||
|
||||
class LogosConfigurationForm(FlaskForm):
|
||||
"Panneau de configuration des logos"
|
||||
|
||||
depts = FieldList(FormField(DeptForm))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
118
app/forms/multiselect.py
Normal file
118
app/forms/multiselect.py
Normal file
@ -0,0 +1,118 @@
|
||||
"""
|
||||
Simplification des multiselect HTML/JS
|
||||
"""
|
||||
|
||||
|
||||
class MultiSelect:
|
||||
"""
|
||||
Classe pour faciliter l'utilisation du multi-select HTML/JS
|
||||
|
||||
Les values sont représentées en dict {
|
||||
value: "...",
|
||||
label:"...",
|
||||
selected: True/False (default to False),
|
||||
single: True/False (default to False)
|
||||
}
|
||||
|
||||
Args:
|
||||
values (dict[str, list[dict]]): Dictionnaire des valeurs
|
||||
génère des <optgroup> pour chaque clef du dictionnaire
|
||||
génère des <option> pour chaque valeur du dictionnaire
|
||||
name (str, optional): Nom du multi-select. Defaults to "multi-select".
|
||||
html_id (str, optional): Id HTML du multi-select. Defaults to "multi-select".
|
||||
classname (str, optional): Classe CSS du multi-select. Defaults to "".
|
||||
label (str, optional): Label du multi-select. Defaults to "".
|
||||
export (str, optional): Format du multi-select (HTML/JS). Defaults to "js".
|
||||
HTML : group_ids="val1"&group_ids="val2"...
|
||||
JS : ["val1","val2", ...]
|
||||
|
||||
**kwargs: Arguments supplémentaires (appliqué au multiselect en HTML <multi-select key="value" ...>)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
values: dict[str, list[dict]],
|
||||
name="multi-select",
|
||||
html_id="multi-select",
|
||||
label="",
|
||||
classname="",
|
||||
**kwargs,
|
||||
) -> None:
|
||||
self.values: dict[str, list[dict]] = values
|
||||
self._on = ""
|
||||
|
||||
self.name: str = name
|
||||
self.html_id: str = html_id
|
||||
self.classname: str = classname
|
||||
self.label: str = label or name
|
||||
|
||||
self.args: dict = kwargs
|
||||
self.js: str = ""
|
||||
self.export: str = "return values"
|
||||
|
||||
def html(self) -> str:
|
||||
"""
|
||||
Génère l'HTML correspondant au multi-select
|
||||
"""
|
||||
opts: list[str] = []
|
||||
|
||||
for key, values in self.values.items():
|
||||
optgroup = f"<optgroup label='{key}'>"
|
||||
for value in values:
|
||||
selected = "selected" if value.get("selected", False) else ""
|
||||
single = "single" if value.get("single", False) else ""
|
||||
opt = f"<option value='{value.get('value')}' {selected} {single} >{value.get('label')}</option>"
|
||||
optgroup += opt
|
||||
optgroup += "</optgroup>"
|
||||
opts.append(optgroup)
|
||||
|
||||
args: list[str] = [f'{key}="{value}"' for key, value in self.args.items()]
|
||||
js: str = "{" + self.js + "}"
|
||||
export: str = "{" + self.export + "}"
|
||||
return f"""
|
||||
<multi-select
|
||||
label="{self.label}"
|
||||
id="{self.html_id}"
|
||||
name="{self.name}"
|
||||
class="{self.classname}"
|
||||
{" ".join(args)}
|
||||
>
|
||||
{"".join(opts)}
|
||||
</multi-select>
|
||||
<script>
|
||||
window.addEventListener('load', () => {{document.getElementById("{self.html_id}").on((values)=>{js});
|
||||
document.getElementById("{self.html_id}").format((values)=>{export});}} );
|
||||
</script>
|
||||
"""
|
||||
|
||||
def change_event(self, js: str) -> None:
|
||||
"""
|
||||
Ajoute un évènement de changement au multi-select
|
||||
|
||||
CallBack JS : (event) => {/*actions à effectuer*/}
|
||||
|
||||
Sera retranscrit dans l'HTML comme :
|
||||
|
||||
document.getElementById(%self.id%).on((event)=>{%self.js%})
|
||||
|
||||
Exemple d'utilisation :
|
||||
|
||||
js : "console.log(event.target.value)"
|
||||
"""
|
||||
self.js: str = js
|
||||
|
||||
def export_format(self, js: str) -> None:
|
||||
"""
|
||||
Met à jour le format de retour de valeur du multi-select
|
||||
|
||||
CallBack JS : (values) => {/*actions à effectuer*/}
|
||||
|
||||
Sera retranscrit dans l'HTML comme :
|
||||
|
||||
document.getElementById(%self.id%).format((values)=>{%self.js%})
|
||||
|
||||
Exemple d'utilisation :
|
||||
|
||||
js : "return values.map(v=> 'val:'+v)"
|
||||
"""
|
||||
self.export: str = js
|
@ -53,4 +53,6 @@ class FeuilleAppelPreForm(FlaskForm):
|
||||
},
|
||||
)
|
||||
|
||||
submit = SubmitField("Télécharger la liste d'émargement")
|
||||
submit = SubmitField(
|
||||
"Télécharger la liste d'émargement", id="btn-submit", name="btn-submit"
|
||||
)
|
||||
|
@ -38,7 +38,7 @@ class ScoDocModel(db.Model):
|
||||
__abstract__ = True # declare an abstract class for SQLAlchemy
|
||||
|
||||
def clone(self, not_copying=()):
|
||||
"""Clone, not copying the given attrs
|
||||
"""Clone, not copying the given attrs, and add to session.
|
||||
Attention: la copie n'a pas d'id avant le prochain flush ou commit.
|
||||
"""
|
||||
d = dict(self.__dict__)
|
||||
@ -111,6 +111,12 @@ class ScoDocModel(db.Model):
|
||||
db.session.add(self)
|
||||
return modified
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"dict"
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
return d
|
||||
|
||||
def edit_from_form(self, form) -> bool:
|
||||
"""Generic edit method for updating model instance.
|
||||
True if modification.
|
||||
@ -182,6 +188,10 @@ from app.models.formsemestre import (
|
||||
NotesSemSet,
|
||||
notes_semset_formsemestre,
|
||||
)
|
||||
from app.models.formsemestre_descr import (
|
||||
FormSemestreDescription,
|
||||
FORMSEMESTRE_DISPOSITIFS,
|
||||
)
|
||||
from app.models.moduleimpls import (
|
||||
ModuleImpl,
|
||||
notes_modules_enseignants,
|
||||
|
@ -297,7 +297,7 @@ class Assiduite(ScoDocModel):
|
||||
moduleimpl_id = int(moduleimpl_id)
|
||||
except ValueError as exc:
|
||||
raise ScoValueError("Module non reconnu") from exc
|
||||
moduleimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
|
||||
moduleimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id)
|
||||
|
||||
# ici moduleimpl est None si non spécifié
|
||||
|
||||
@ -352,8 +352,8 @@ class Assiduite(ScoDocModel):
|
||||
"""
|
||||
|
||||
if self.moduleimpl_id is not None:
|
||||
modimpl: ModuleImpl = ModuleImpl.query.get(self.moduleimpl_id)
|
||||
mod: Module = Module.query.get(modimpl.module_id)
|
||||
modimpl: ModuleImpl = db.session.get(ModuleImpl, self.moduleimpl_id)
|
||||
mod: Module = db.session.get(Module, modimpl.module_id)
|
||||
if traduire:
|
||||
return f"{mod.code} {mod.titre}"
|
||||
return mod
|
||||
|
@ -260,8 +260,8 @@ class Identite(models.ScoDocModel):
|
||||
Add to session but don't commit.
|
||||
True if modification.
|
||||
"""
|
||||
check_etud_duplicate_code(args, "code_nip")
|
||||
check_etud_duplicate_code(args, "code_ine")
|
||||
check_etud_duplicate_code(args, "code_nip", etudid=self.id)
|
||||
check_etud_duplicate_code(args, "code_ine", etudid=self.id)
|
||||
return super().from_dict(args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
@ -796,11 +796,11 @@ class Identite(models.ScoDocModel):
|
||||
)
|
||||
|
||||
|
||||
def check_etud_duplicate_code(args, code_name, edit=True):
|
||||
def check_etud_duplicate_code(args, code_name, edit=True, etudid: int | None = None):
|
||||
"""Vérifie que le code n'est pas dupliqué.
|
||||
Raises ScoGenError si problème.
|
||||
"""
|
||||
etudid = args.get("etudid", None)
|
||||
etudid = etudid or args.get("etudid", None)
|
||||
if not args.get(code_name, None):
|
||||
return
|
||||
etuds = Identite.query.filter_by(
|
||||
|
@ -109,7 +109,7 @@ class ScolarNews(db.Model):
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
"'Chargement notes dans Stage (S3 FI) par Aurélie Dupont'"
|
||||
"exemple: 'Notes dans Stage (S3 FI) par Aurélie Dupont'"
|
||||
formsemestre = self.get_news_formsemestre()
|
||||
user = User.query.filter_by(user_name=self.authenticated_user).first()
|
||||
|
||||
@ -271,7 +271,7 @@ class ScolarNews(db.Model):
|
||||
return ""
|
||||
dept_news_url = url_for("scolar.dept_news", scodoc_dept=g.scodoc_dept)
|
||||
H = [
|
||||
f"""<div class="scobox news"><div class="scobox-title"><a href="{
|
||||
f"""<div class="scobox news" desktop="true"><div class="scobox-title" desktop="true"><a href="{
|
||||
dept_news_url
|
||||
}">Dernières opérations</a>
|
||||
</div><ul class="newslist">"""
|
||||
@ -286,14 +286,18 @@ class ScolarNews(db.Model):
|
||||
f"""<li class="newslist">
|
||||
<span class="newstext"><a href="{dept_news_url}" class="stdlink">...</a>
|
||||
</span>
|
||||
</li>"""
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="newslist" mobile="true" style="margin-bottom: 0px;">
|
||||
<li><a href="{dept_news_url}" class="stdlink">Dernières opérations</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
||||
H.append("</ul></div>")
|
||||
|
||||
# Informations générales
|
||||
H.append(
|
||||
f"""<div>
|
||||
f"""<div desktop="true">
|
||||
Pour en savoir plus sur ScoDoc voir
|
||||
<a class="stdlink" href="{scu.SCO_ANNONCES_WEBSITE}">scodoc.org</a>
|
||||
</div>
|
||||
|
@ -23,7 +23,7 @@ from sqlalchemy.sql import text
|
||||
from sqlalchemy import func
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import db, log
|
||||
from app import db, email, log
|
||||
from app.auth.models import User
|
||||
from app import models
|
||||
from app.models import APO_CODE_STR_LEN, CODE_STR_LEN, SHORT_STR_LEN
|
||||
@ -36,7 +36,7 @@ from app.models.config import ScoDocSiteConfig
|
||||
from app.models.departements import Departement
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.evaluations import Evaluation
|
||||
from app.models.events import ScolarNews
|
||||
from app.models.events import Scolog, ScolarNews
|
||||
from app.models.formations import Formation
|
||||
from app.models.groups import GroupDescr, Partition
|
||||
from app.models.moduleimpls import (
|
||||
@ -45,9 +45,10 @@ from app.models.moduleimpls import (
|
||||
notes_modules_enseignants,
|
||||
)
|
||||
from app.models.modules import Module
|
||||
from app.models.scolar_event import ScolarEvent
|
||||
from app.models.ues import UniteEns
|
||||
from app.models.validations import ScolarFormSemestreValidation
|
||||
from app.scodoc import codes_cursus, sco_preferences
|
||||
from app.scodoc import codes_cursus, sco_cache, sco_preferences
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_utils import MONTH_NAMES_ABBREV, translate_assiduites_metric
|
||||
@ -69,6 +70,8 @@ class FormSemestre(models.ScoDocModel):
|
||||
formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
|
||||
semestre_id = db.Column(db.Integer, nullable=False, default=1, server_default="1")
|
||||
titre = db.Column(db.Text(), nullable=False)
|
||||
# nb max d'inscriptions (non DEM), null si illimité:
|
||||
capacite_accueil = db.Column(db.Integer, nullable=True)
|
||||
date_debut = db.Column(db.Date(), nullable=False)
|
||||
date_fin = db.Column(db.Date(), nullable=False) # jour inclus
|
||||
edt_id: str | None = db.Column(db.Text(), index=True, nullable=True)
|
||||
@ -143,6 +146,12 @@ class FormSemestre(models.ScoDocModel):
|
||||
lazy="dynamic",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
description = db.relationship(
|
||||
"FormSemestreDescription",
|
||||
back_populates="formsemestre",
|
||||
cascade="all, delete-orphan",
|
||||
uselist=False,
|
||||
)
|
||||
etuds = db.relationship(
|
||||
"Identite",
|
||||
secondary="notes_formsemestre_inscription",
|
||||
@ -1013,20 +1022,129 @@ class FormSemestre(models.ScoDocModel):
|
||||
codes |= {x.strip() for x in self.elt_passage_apo.split(",") if x}
|
||||
return codes
|
||||
|
||||
def get_inscrits(self, include_demdef=False, order=False) -> list[Identite]:
|
||||
def get_inscrits(
|
||||
self, include_demdef=False, order=False, etats: set | None = None
|
||||
) -> list[Identite]:
|
||||
"""Liste des étudiants inscrits à ce semestre
|
||||
Si include_demdef, tous les étudiants, avec les démissionnaires
|
||||
et défaillants.
|
||||
Si etats, seuls les étudiants dans l'un des états indiqués.
|
||||
Si order, tri par clé sort_key
|
||||
"""
|
||||
if include_demdef:
|
||||
etuds = [ins.etud for ins in self.inscriptions]
|
||||
else:
|
||||
elif not etats:
|
||||
etuds = [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT]
|
||||
else:
|
||||
etuds = [ins.etud for ins in self.inscriptions if ins.etat in etats]
|
||||
if order:
|
||||
etuds.sort(key=lambda e: e.sort_key)
|
||||
return etuds
|
||||
|
||||
def inscrit_etudiant(
|
||||
self,
|
||||
etud: "Identite",
|
||||
etat: str = scu.INSCRIT,
|
||||
etape: str | None = None,
|
||||
method: str | None = None,
|
||||
) -> "FormSemestreInscription":
|
||||
"""Inscrit l'étudiant au semestre, ou renvoie son inscription s'il l'est déjà.
|
||||
Vérifie la capacité d'accueil si indiquée (non null): si le semestre est plein,
|
||||
lève une exception. Génère un évènement et un log étudiant.
|
||||
method: indique origine de l'inscription pour le log étudiant.
|
||||
"""
|
||||
# remplace ancien do_formsemestre_inscription_create()
|
||||
if not self.etat: # check lock
|
||||
raise ScoValueError("inscrit_etudiant: semestre verrouille")
|
||||
inscr = FormSemestreInscription.query.filter_by(
|
||||
formsemestre_id=self.id, etudid=etud.id
|
||||
).first()
|
||||
if inscr is not None:
|
||||
return inscr
|
||||
|
||||
if self.capacite_accueil is not None:
|
||||
# tous sauf démissionnaires:
|
||||
inscriptions = self.get_inscrits(etats={scu.INSCRIT, scu.DEF})
|
||||
if len(inscriptions) >= self.capacite_accueil:
|
||||
raise ScoValueError(
|
||||
f"Semestre {self.titre} complet : {len(self.inscriptions)} inscrits",
|
||||
dest_url=url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=self.id,
|
||||
),
|
||||
)
|
||||
|
||||
inscr = FormSemestreInscription(
|
||||
formsemestre_id=self.id, etudid=etud.id, etat=etat, etape=etape
|
||||
)
|
||||
db.session.add(inscr)
|
||||
# Évènement
|
||||
event = ScolarEvent(
|
||||
etudid=etud.id,
|
||||
formsemestre_id=self.id,
|
||||
event_type="INSCRIPTION",
|
||||
)
|
||||
db.session.add(event)
|
||||
# Log etudiant
|
||||
Scolog.logdb(
|
||||
method=method,
|
||||
etudid=etud.id,
|
||||
msg=f"inscription en semestre {self.titre_annee()}",
|
||||
commit=True,
|
||||
)
|
||||
log(
|
||||
f"inscrit_etudiant: {etud.nomprenom} ({etud.id}) au semestre {self.titre_annee()}"
|
||||
)
|
||||
# Notification mail
|
||||
self._notify_inscription(etud)
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=self.id)
|
||||
return inscr
|
||||
|
||||
def desinscrit_etudiant(self, etud: Identite):
|
||||
"Désinscrit l'étudiant du semestre (et notifie le cas échéant)"
|
||||
inscr_sem = FormSemestreInscription.query.filter_by(
|
||||
etudid=etud.id, formsemestre_id=self.id
|
||||
).first()
|
||||
if not inscr_sem:
|
||||
raise ScoValueError(
|
||||
f"{etud.nomprenom} ({etud.id}) n'est pas inscrit au semestre !"
|
||||
)
|
||||
db.session.delete(inscr_sem)
|
||||
Scolog.logdb(
|
||||
method="desinscrit_etudiant",
|
||||
etudid=etud.id,
|
||||
msg=f"désinscription semestre {self.titre_annee()}",
|
||||
commit=True,
|
||||
)
|
||||
log(
|
||||
f"desinscrit_etudiant: {etud.nomprenom} ({etud.id}) au semestre {self.titre_annee()}"
|
||||
)
|
||||
self._notify_inscription(etud, action="désinscrit")
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=self.id)
|
||||
|
||||
def _notify_inscription(self, etud: Identite, action="inscrit") -> None:
|
||||
"Notifie inscription d'un étudiant: envoie un mail selon paramétrage"
|
||||
destinations = (
|
||||
sco_preferences.get_preference("emails_notifications_inscriptions", self.id)
|
||||
or ""
|
||||
)
|
||||
destinations = [x.strip() for x in destinations.split(",")]
|
||||
destinations = [x for x in destinations if x]
|
||||
if not destinations:
|
||||
return
|
||||
txt = f"""{etud.nom_prenom()}
|
||||
s'est {action}{etud.e}
|
||||
en {self.titre_annee()}"""
|
||||
subject = f"""Inscription de {etud.nom_prenom()} en {self.titre_annee()}"""
|
||||
# build mail
|
||||
log(f"_notify_inscription: sending notification to {destinations}")
|
||||
log(f"_notify_inscription: subject: {subject}")
|
||||
log(txt)
|
||||
email.send_email(
|
||||
"[ScoDoc] " + subject, email.get_from_addr(), destinations, txt
|
||||
)
|
||||
|
||||
def get_partitions_list(
|
||||
self, with_default=True, only_listed=False
|
||||
) -> list[Partition]:
|
||||
@ -1318,7 +1436,7 @@ notes_formsemestre_responsables = db.Table(
|
||||
)
|
||||
|
||||
|
||||
class FormSemestreEtape(db.Model):
|
||||
class FormSemestreEtape(models.ScoDocModel):
|
||||
"""Étape Apogée associée au semestre"""
|
||||
|
||||
__tablename__ = "notes_formsemestre_etapes"
|
||||
@ -1349,7 +1467,7 @@ class FormSemestreEtape(db.Model):
|
||||
return ApoEtapeVDI(self.etape_apo)
|
||||
|
||||
|
||||
class FormationModalite(db.Model):
|
||||
class FormationModalite(models.ScoDocModel):
|
||||
"""Modalités de formation, utilisées pour la présentation
|
||||
(grouper les semestres, générer des codes, etc.)
|
||||
"""
|
||||
@ -1400,7 +1518,7 @@ class FormationModalite(db.Model):
|
||||
raise
|
||||
|
||||
|
||||
class FormSemestreUECoef(db.Model):
|
||||
class FormSemestreUECoef(models.ScoDocModel):
|
||||
"""Coef des UE capitalisees arrivant dans ce semestre"""
|
||||
|
||||
__tablename__ = "notes_formsemestre_uecoef"
|
||||
@ -1441,7 +1559,7 @@ class FormSemestreUEComputationExpr(db.Model):
|
||||
computation_expr = db.Column(db.Text())
|
||||
|
||||
|
||||
class FormSemestreCustomMenu(db.Model):
|
||||
class FormSemestreCustomMenu(models.ScoDocModel):
|
||||
"""Menu custom associe au semestre"""
|
||||
|
||||
__tablename__ = "notes_formsemestre_custommenu"
|
||||
@ -1457,7 +1575,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(models.ScoDocModel):
|
||||
"""Inscription à un semestre de formation"""
|
||||
|
||||
__tablename__ = "notes_formsemestre_inscription"
|
||||
@ -1503,7 +1621,7 @@ class FormSemestreInscription(db.Model):
|
||||
} {('etape="'+self.etape+'"') if self.etape else ''}>"""
|
||||
|
||||
|
||||
class NotesSemSet(db.Model):
|
||||
class NotesSemSet(models.ScoDocModel):
|
||||
"""semsets: ensemble de formsemestres pour exports Apogée"""
|
||||
|
||||
__tablename__ = "notes_semset"
|
||||
|
82
app/models/formsemestre_descr.py
Normal file
82
app/models/formsemestre_descr.py
Normal file
@ -0,0 +1,82 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""Description d'un formsemestre pour applications tierces.
|
||||
|
||||
Ces informations sont éditables dans ScoDoc et publiés sur l'API
|
||||
pour affichage dans l'application tierce.
|
||||
"""
|
||||
|
||||
from app import db
|
||||
from app import models
|
||||
|
||||
|
||||
class FormSemestreDescription(models.ScoDocModel):
|
||||
"""Informations décrivant un "semestre" (session) de formation
|
||||
pour un apprenant.
|
||||
"""
|
||||
|
||||
__tablename__ = "notes_formsemestre_description"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
description = db.Column(db.Text(), nullable=False, default="", server_default="")
|
||||
"description du cours, html autorisé"
|
||||
horaire = db.Column(db.Text(), nullable=False, default="", server_default="")
|
||||
"indication sur l'horaire, texte libre"
|
||||
date_debut_inscriptions = db.Column(db.DateTime(timezone=True), nullable=True)
|
||||
date_fin_inscriptions = db.Column(db.DateTime(timezone=True), nullable=True)
|
||||
|
||||
wip = db.Column(db.Boolean, nullable=False, default=False, server_default="false")
|
||||
"work in progress: si vrai, affichera juste le titre du semestre"
|
||||
|
||||
# Store image data directly in the database:
|
||||
image = db.Column(db.LargeBinary(), nullable=True)
|
||||
campus = db.Column(db.Text(), nullable=False, default="", server_default="")
|
||||
salle = db.Column(db.Text(), nullable=False, default="", server_default="")
|
||||
|
||||
dispositif = db.Column(db.Integer, nullable=False, default=0, server_default="0")
|
||||
"0 présentiel, 1 online, 2 hybride"
|
||||
modalites_mcc = db.Column(db.Text(), nullable=False, default="", server_default="")
|
||||
"modalités de contrôle des connaissances"
|
||||
photo_ens = db.Column(db.LargeBinary(), nullable=True)
|
||||
"photo de l'enseignant(e)"
|
||||
public = db.Column(db.Text(), nullable=False, default="", server_default="")
|
||||
"public visé"
|
||||
prerequis = db.Column(db.Text(), nullable=False, default="", server_default="")
|
||||
"prérequis (texte libre, html autorisé)"
|
||||
responsable = db.Column(db.Text(), nullable=False, default="", server_default="")
|
||||
"responsable du cours (texte libre, html autorisé)"
|
||||
formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
)
|
||||
formsemestre = db.relationship(
|
||||
"FormSemestre", back_populates="description", uselist=False
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<FormSemestreDescription {self.id} {self.formsemestre}>"
|
||||
|
||||
def clone(self, not_copying=()) -> "FormSemestreDescription":
|
||||
"""clone instance"""
|
||||
return super().clone(not_copying=not_copying + ("formsemestre_id",))
|
||||
|
||||
def to_dict(self, exclude_images=True) -> dict:
|
||||
"dict, tous les attributs sauf les images"
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
if exclude_images:
|
||||
d.pop("image", None)
|
||||
d.pop("photo_ens", None)
|
||||
return d
|
||||
|
||||
|
||||
FORMSEMESTRE_DISPOSITIFS = {
|
||||
0: "présentiel",
|
||||
1: "en ligne",
|
||||
2: "hybride",
|
||||
}
|
@ -242,6 +242,16 @@ class GroupDescr(ScoDocModel):
|
||||
f"""<{self.__class__.__name__} {self.id} "{self.group_name or '(tous)'}">"""
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def filter_model_attributes(cls, data: dict, excluded: set[str] = None) -> dict:
|
||||
"""Returns a copy of dict with only the keys belonging to the Model and not in excluded.
|
||||
Exclude `partition_id` : a group cannot be moved from a partition to another.
|
||||
"""
|
||||
return super().filter_model_attributes(
|
||||
data,
|
||||
excluded=(excluded or set()) | {"partition_id"},
|
||||
)
|
||||
|
||||
def get_nom_with_part(self, default="-") -> str:
|
||||
"""Nom avec partition: 'TD A'
|
||||
Si groupe par défaut (tous), utilise default ou "-"
|
||||
|
@ -325,7 +325,7 @@ notes_modules_enseignants = db.Table(
|
||||
# XXX il manque probablement une relation pour gérer cela
|
||||
|
||||
|
||||
class ModuleImplInscription(db.Model):
|
||||
class ModuleImplInscription(ScoDocModel):
|
||||
"""Inscription à un module (etudiants,moduleimpl)"""
|
||||
|
||||
__tablename__ = "notes_moduleimpl_inscription"
|
||||
|
@ -56,7 +56,7 @@ class BulAppreciations(models.ScoDocModel):
|
||||
return safehtml.html_to_safe_html(self.comment or "")
|
||||
|
||||
|
||||
class NotesNotes(db.Model):
|
||||
class NotesNotes(models.ScoDocModel):
|
||||
"""Une note"""
|
||||
|
||||
__tablename__ = "notes_notes"
|
||||
@ -75,12 +75,6 @@ class NotesNotes(db.Model):
|
||||
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
uid = db.Column(db.Integer, db.ForeignKey("user.id"))
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"dict"
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
return d
|
||||
|
||||
def __repr__(self):
|
||||
"pour debug"
|
||||
from app.models.evaluations import Evaluation
|
||||
|
@ -333,7 +333,7 @@ class SxTag(pe_tabletags.TableTag):
|
||||
etud_moy = np.max(set_cube_no_nan, axis=2)
|
||||
|
||||
# Fix les max non calculé -1 -> NaN
|
||||
etud_moy[etud_moy < 0] = np.NaN
|
||||
etud_moy[etud_moy < 0] = np.nan
|
||||
|
||||
# Le dataFrame
|
||||
etud_moy_tag_df = pd.DataFrame(
|
||||
|
@ -95,7 +95,6 @@ def TrivialFormulator(
|
||||
To use text_suggest elements, one must:
|
||||
- specify options in text_suggest_options (a dict)
|
||||
- HTML page must load JS AutoSuggest.js and CSS autosuggest_inquisitor.css
|
||||
- bodyOnLoad must call JS function init_tf_form(formid)
|
||||
"""
|
||||
method = method.lower()
|
||||
if method == "get":
|
||||
@ -776,9 +775,12 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
||||
# => only one form with text_suggest field on a page.
|
||||
R.append(
|
||||
"""<script type="text/javascript">
|
||||
function init_tf_form(formid) {
|
||||
function init_tf_form() {
|
||||
%s
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
init_tf_form();
|
||||
});
|
||||
</script>"""
|
||||
% "\n".join(suggest_js)
|
||||
)
|
||||
|
@ -57,7 +57,6 @@ from reportlab.lib.units import cm
|
||||
|
||||
from flask import render_template
|
||||
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc import sco_excel
|
||||
from app.scodoc import sco_pdf
|
||||
@ -686,6 +685,7 @@ class GenTable:
|
||||
javascripts=(),
|
||||
with_html_headers=True,
|
||||
publish=True,
|
||||
template="sco_page.j2",
|
||||
):
|
||||
"""
|
||||
Build page at given format
|
||||
@ -704,7 +704,7 @@ class GenTable:
|
||||
H.append(self.html())
|
||||
if with_html_headers:
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
template,
|
||||
content="\n".join(H),
|
||||
title=page_title,
|
||||
javascripts=javascripts,
|
||||
|
@ -27,10 +27,7 @@
|
||||
|
||||
"""HTML Header/Footer for ScoDoc pages"""
|
||||
|
||||
import html
|
||||
|
||||
from flask import g, render_template, url_for
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
@ -39,22 +36,6 @@ from app.scodoc import html_sidebar
|
||||
import sco_version
|
||||
|
||||
|
||||
# Some constants:
|
||||
|
||||
# Multiselect menus are used on a few pages and not loaded by default
|
||||
BOOTSTRAP_MULTISELECT_JS = [
|
||||
"libjs/bootstrap/js/bootstrap.min.js",
|
||||
"libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.js",
|
||||
"libjs/purl.js",
|
||||
]
|
||||
|
||||
BOOTSTRAP_MULTISELECT_CSS = [
|
||||
"libjs/bootstrap/css/bootstrap.min.css",
|
||||
"libjs/bootstrap/css/bootstrap-theme.min.css",
|
||||
"libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.css",
|
||||
]
|
||||
|
||||
|
||||
def standard_html_header():
|
||||
"""Standard HTML header for pages outside depts"""
|
||||
# not used in ZScolar, see sco_header
|
||||
@ -85,7 +66,6 @@ _HTML_BEGIN = f"""<!DOCTYPE html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=%(encoding)s" />
|
||||
<meta http-equiv="Content-Style-Type" content="text/css" />
|
||||
@ -98,21 +78,13 @@ _HTML_BEGIN = f"""<!DOCTYPE html>
|
||||
<link href="{scu.STATIC_DIR}/css/scodoc.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{scu.STATIC_DIR}/css/menu.css" rel="stylesheet" type="text/css" />
|
||||
<link rel="stylesheet" type="text/css" href="{scu.STATIC_DIR}/DataTables/datatables.min.css" />
|
||||
<link href="{scu.STATIC_DIR}/css/gt_table.css" rel="stylesheet" type="text/css" />
|
||||
<script src="{scu.STATIC_DIR}/libjs/menu.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/libjs/bubble.js"></script>
|
||||
<script>
|
||||
window.onload=function(){{
|
||||
if (document.getElementById('gtrcontent')) {{
|
||||
enableTooltips("gtrcontent");
|
||||
}}
|
||||
if (document.getElementById('sidebar')) {{
|
||||
enableTooltips("sidebar");
|
||||
}}
|
||||
}};
|
||||
</script>
|
||||
|
||||
|
||||
<script src="{scu.STATIC_DIR}/jQuery/jquery.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/jQuery/jquery-migrate-3.5.2.min.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/libjs/jquery.field.min.js"></script>
|
||||
|
||||
<script src="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||
@ -121,12 +93,30 @@ _HTML_BEGIN = f"""<!DOCTYPE html>
|
||||
<script src="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.css" />
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/libjs/timepicker-1.3.5/jquery.timepicker.min.css" />
|
||||
<script src="{scu.STATIC_DIR}/libjs/timepicker-1.3.5/jquery.timepicker.min.js"></script>
|
||||
|
||||
<script src="{scu.STATIC_DIR}/js/scodoc.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/js/etud_info.js"></script>
|
||||
|
||||
<script src="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.css" />
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {{
|
||||
if (document.getElementById('gtrcontent')) {{
|
||||
enableTooltips("gtrcontent");
|
||||
}}
|
||||
if (document.getElementById('sidebar')) {{
|
||||
enableTooltips("sidebar");
|
||||
}}
|
||||
}});
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
def scodoc_top_html_header(page_title="ScoDoc: bienvenue"):
|
||||
"""HTML header for top level pages"""
|
||||
H = [
|
||||
_HTML_BEGIN % {"page_title": page_title, "encoding": scu.SCO_ENCODING},
|
||||
"""</head><body id="gtrcontent">""",
|
||||
@ -139,17 +129,12 @@ def scodoc_top_html_header(page_title="ScoDoc: bienvenue"):
|
||||
def sco_header(
|
||||
# optional args
|
||||
page_title="", # page title
|
||||
no_side_bar=False, # hide sidebar
|
||||
no_sidebar=False, # hide sidebar
|
||||
cssstyles=(), # additionals CSS sheets
|
||||
javascripts=(), # additionals JS filenames to load
|
||||
scripts=(), # script to put in page header
|
||||
bodyOnLoad="", # JS
|
||||
init_qtip=False, # include qTip
|
||||
init_google_maps=False, # Google maps
|
||||
init_datatables=True,
|
||||
titrebandeau="", # titre dans bandeau superieur
|
||||
head_message="", # message action (petit cadre jaune en haut) DEPRECATED
|
||||
user_check=True, # verifie passwords temporaires
|
||||
etudid=None,
|
||||
formsemestre_id=None,
|
||||
):
|
||||
@ -162,51 +147,21 @@ def sco_header(
|
||||
g.current_etudid = etudid
|
||||
scodoc_flash_status_messages()
|
||||
|
||||
# Get head message from http request:
|
||||
if not head_message:
|
||||
if request.method == "POST":
|
||||
head_message = request.form.get("head_message", "")
|
||||
elif request.method == "GET":
|
||||
head_message = request.args.get("head_message", "")
|
||||
params = {
|
||||
"page_title": page_title or sco_version.SCONAME,
|
||||
"no_side_bar": no_side_bar,
|
||||
"no_sidebar": no_sidebar,
|
||||
"ScoURL": url_for("scolar.index_html", scodoc_dept=g.scodoc_dept),
|
||||
"encoding": scu.SCO_ENCODING,
|
||||
"titrebandeau_mkup": "<td>" + titrebandeau + "</td>",
|
||||
"authuser": current_user.user_name,
|
||||
}
|
||||
if bodyOnLoad:
|
||||
params["bodyOnLoad_mkup"] = """onload="%s" """ % bodyOnLoad
|
||||
else:
|
||||
params["bodyOnLoad_mkup"] = ""
|
||||
if no_side_bar:
|
||||
if no_sidebar:
|
||||
params["margin_left"] = "1em"
|
||||
else:
|
||||
params["margin_left"] = "140px"
|
||||
|
||||
H = [
|
||||
"""<!DOCTYPE html><html lang="fr">
|
||||
<!-- ScoDoc legacy -->
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>%(page_title)s</title>
|
||||
<meta name="LANG" content="fr" />
|
||||
<meta name="DESCRIPTION" content="ScoDoc" />
|
||||
H = [_HTML_BEGIN % params]
|
||||
|
||||
"""
|
||||
% params
|
||||
]
|
||||
# jQuery UI
|
||||
# can modify loaded theme here
|
||||
H.append(
|
||||
f"""
|
||||
<link type="text/css" rel="stylesheet"
|
||||
href="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/css/smoothness/jquery-ui-1.10.4.custom.min.css" />
|
||||
<link type="text/css" rel="stylesheet"
|
||||
href="{scu.STATIC_DIR}/libjs/timepicker-1.3.5/jquery.timepicker.min.css" />
|
||||
"""
|
||||
)
|
||||
if init_google_maps:
|
||||
# It may be necessary to add an API key:
|
||||
H.append('<script src="https://maps.google.com/maps/api/js"></script>')
|
||||
@ -219,61 +174,17 @@ def sco_header(
|
||||
|
||||
H.append(
|
||||
f"""
|
||||
<link href="{scu.STATIC_DIR}/css/scodoc.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{scu.STATIC_DIR}/css/menu.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{scu.STATIC_DIR}/css/gt_table.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<script src="{scu.STATIC_DIR}/libjs/menu.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/libjs/bubble.js"></script>
|
||||
<script>
|
||||
window.onload=function(){{
|
||||
if (document.getElementById('gtrcontent')) {{
|
||||
enableTooltips("gtrcontent");
|
||||
}}
|
||||
if (document.getElementById('sidebar')) {{
|
||||
enableTooltips("sidebar");
|
||||
}}
|
||||
}};
|
||||
const SCO_URL="{url_for("scolar.index_html", scodoc_dept=g.scodoc_dept)}";
|
||||
const SCO_TIMEZONE="{scu.TIME_ZONE}";
|
||||
</script>"""
|
||||
)
|
||||
|
||||
# jQuery
|
||||
H.append(
|
||||
f"""
|
||||
<script src="{scu.STATIC_DIR}/jQuery/jquery.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/libjs/jquery.field.min.js"></script>
|
||||
"""
|
||||
)
|
||||
# qTip
|
||||
if init_qtip:
|
||||
H.append(
|
||||
f"""<script src="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet"
|
||||
href="{scu.STATIC_DIR}/libjs/qtip/jquery.qtip-3.0.3.min.css" />
|
||||
"""
|
||||
)
|
||||
|
||||
H.append(
|
||||
f"""<script
|
||||
src="{scu.STATIC_DIR}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/libjs/timepicker-1.3.5/jquery.timepicker.min.js"></script>
|
||||
<script src="{scu.STATIC_DIR}/js/scodoc.js"></script>
|
||||
"""
|
||||
)
|
||||
if init_google_maps:
|
||||
if init_google_maps: # utilisé uniquement pour carte lycées
|
||||
H.append(
|
||||
f'<script src="{scu.STATIC_DIR}/libjs/jquery.ui.map.full.min.js"></script>'
|
||||
)
|
||||
if init_datatables:
|
||||
H.append(
|
||||
f"""<link rel="stylesheet" type="text/css" href="{scu.STATIC_DIR}/DataTables/datatables.min.css"/>
|
||||
<script src="{scu.STATIC_DIR}/DataTables/datatables.min.js"></script>"""
|
||||
)
|
||||
# H.append(
|
||||
# f'<link href="{scu.STATIC_DIR}/css/tooltip.css" rel="stylesheet" type="text/css" />'
|
||||
# )
|
||||
|
||||
# JS additionels
|
||||
for js in javascripts:
|
||||
H.append(f"""<script src="{scu.STATIC_DIR}/{js}"></script>\n""")
|
||||
@ -295,15 +206,18 @@ def sco_header(
|
||||
H.append(script)
|
||||
H.append("""</script>""")
|
||||
|
||||
H.append("</head>")
|
||||
# Fin head, Body et bandeau haut:
|
||||
H.append(
|
||||
f"""</head>
|
||||
<!-- Legacy ScoDoc header -->
|
||||
<body>
|
||||
{scu.CUSTOM_HTML_HEADER}
|
||||
{'' if no_sidebar else html_sidebar.sidebar(etudid)}
|
||||
<div id="mobileNav" mobile="true" style="display:none;"></div>
|
||||
|
||||
# Body et bandeau haut:
|
||||
H.append("""<body %(bodyOnLoad_mkup)s>""" % params)
|
||||
H.append(scu.CUSTOM_HTML_HEADER)
|
||||
#
|
||||
if not no_side_bar:
|
||||
H.append(html_sidebar.sidebar(etudid))
|
||||
H.append("""<div id="gtrcontent">""")
|
||||
<div id="gtrcontent">
|
||||
"""
|
||||
)
|
||||
# En attendant le replacement complet de cette fonction,
|
||||
# inclusion ici des messages flask
|
||||
H.append(render_template("flashed_messages.j2"))
|
||||
@ -311,10 +225,6 @@ def sco_header(
|
||||
# Barre menu semestre:
|
||||
H.append(formsemestre_page_title(formsemestre_id))
|
||||
|
||||
#
|
||||
if head_message:
|
||||
H.append('<div class="head_message">' + html.escape(head_message) + "</div>")
|
||||
#
|
||||
# div pour affichage messages temporaires
|
||||
H.append('<div id="sco_msg" class="head_message"></div>')
|
||||
#
|
||||
@ -329,18 +239,3 @@ def sco_footer():
|
||||
+ scu.CUSTOM_HTML_FOOTER
|
||||
+ """</body></html>"""
|
||||
)
|
||||
|
||||
|
||||
def html_sem_header(
|
||||
title, with_page_header=True, with_h2=True, page_title=None, **args
|
||||
):
|
||||
"Titre d'une page semestre avec lien vers tableau de bord"
|
||||
# sem now unused and thus optional...
|
||||
if with_page_header:
|
||||
h = sco_header(page_title="%s" % (page_title or title), **args)
|
||||
else:
|
||||
h = ""
|
||||
if with_h2:
|
||||
return h + f"""<h2 class="formsemestre">{title}</h2>"""
|
||||
else:
|
||||
return h
|
||||
|
@ -41,10 +41,8 @@ from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_trombino
|
||||
from app.scodoc import sco_archives
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc.sco_exceptions import AccessDenied
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_etud
|
||||
|
||||
|
||||
class EtudsArchiver(sco_archives.BaseArchiver):
|
||||
@ -142,9 +140,6 @@ def etud_upload_file_form(etudid):
|
||||
etud = Identite.get_etud(etudid)
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"Chargement d'un document associé à {etud.nomprenom}",
|
||||
),
|
||||
f"""<h2>Chargement d'un document associé à {etud.nomprenom}</h2>
|
||||
|
||||
<p>Le fichier ne doit pas dépasser {
|
||||
@ -171,8 +166,12 @@ def etud_upload_file_form(etudid):
|
||||
cancelbutton="Annuler",
|
||||
)
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
||||
elif tf[0] == -1:
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title=f"Chargement d'un document associé à {etud.nomprenom}",
|
||||
content="\n".join(H) + tf[1],
|
||||
)
|
||||
if tf[0] == -1:
|
||||
return flask.redirect(etud.url_fiche())
|
||||
data = tf[2]["datafile"].read()
|
||||
descr = tf[2]["description"]
|
||||
@ -217,7 +216,6 @@ def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
|
||||
"scolar.fiche_etud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etudid,
|
||||
head_message="annulation",
|
||||
),
|
||||
parameters={"etudid": etudid, "archive_name": archive_name},
|
||||
)
|
||||
@ -264,9 +262,6 @@ def etudarchive_generate_excel_sample(group_id=None):
|
||||
def etudarchive_import_files_form(group_id):
|
||||
"""Formulaire pour importation fichiers d'un groupe"""
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Import de fichiers associés aux étudiants"
|
||||
),
|
||||
"""<h2 class="formsemestre">Téléchargement de fichier associés aux étudiants</h2>
|
||||
<p>Les fichiers associés (dossiers d'admission, certificats, ...), de
|
||||
types quelconques (pdf, doc, images) sont accessibles aux utilisateurs via
|
||||
@ -293,7 +288,6 @@ def etudarchive_import_files_form(group_id):
|
||||
"""
|
||||
% group_id,
|
||||
]
|
||||
F = html_sco_header.sco_footer()
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
scu.get_request_args(),
|
||||
@ -314,7 +308,11 @@ def etudarchive_import_files_form(group_id):
|
||||
)
|
||||
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + tf[1] + "</li></ol>" + F
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title="Import de fichiers associés aux étudiants",
|
||||
content="\n".join(H) + tf[1] + "</li></ol>",
|
||||
)
|
||||
# retrouve le semestre à partir du groupe:
|
||||
group = sco_groups.get_group(group_id)
|
||||
if tf[0] == -1:
|
||||
@ -360,7 +358,7 @@ def etudarchive_import_files(
|
||||
unmatched_files=unmatched_files,
|
||||
stored_etud_filename=stored_etud_filename,
|
||||
next_page=url_for(
|
||||
"scolar.groups_view",
|
||||
"scolar.groups_feuilles",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
),
|
||||
|
@ -27,7 +27,7 @@
|
||||
import json
|
||||
|
||||
import flask
|
||||
from flask import flash, g, request, url_for
|
||||
from flask import flash, g, render_template, request, url_for
|
||||
|
||||
from app import ScoDocJSONEncoder
|
||||
from app.but import jury_but_pv
|
||||
@ -36,7 +36,6 @@ from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc.sco_exceptions import ScoPermissionDenied
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_groups_view
|
||||
@ -123,18 +122,18 @@ def do_formsemestre_archive(
|
||||
)
|
||||
if table_html:
|
||||
flash(f"Moyennes archivées le {date}", category="info")
|
||||
data = "\n".join(
|
||||
[
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"Moyennes archivées le {date}",
|
||||
no_side_bar=True,
|
||||
),
|
||||
f'<h2 class="fontorange">Valeurs archivées le {date}</h2>',
|
||||
"""<style type="text/css">table.notes_recapcomplet tr { color: rgb(185,70,0); }
|
||||
data = render_template(
|
||||
"sco_page.j2",
|
||||
no_sidebar=True,
|
||||
title=f"Moyennes archivées le {date}",
|
||||
content="\n".join(
|
||||
[
|
||||
f'<h2 class="fontorange">Valeurs archivées le {date}</h2>',
|
||||
"""<style type="text/css">table.notes_recapcomplet tr { color: rgb(185,70,0); }
|
||||
</style>""",
|
||||
table_html,
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
table_html,
|
||||
]
|
||||
),
|
||||
)
|
||||
PV_ARCHIVER.store(
|
||||
archive_id, "Tableau_moyennes.html", data, dept_id=formsemestre.dept_id
|
||||
@ -238,12 +237,6 @@ def formsemestre_archive(formsemestre_id, group_ids: list[int] = None):
|
||||
)
|
||||
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Archiver les PV et résultats du semestre",
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
init_qtip=True,
|
||||
),
|
||||
"""<p class="help">Cette page permet de générer et d'archiver tous
|
||||
les documents résultant de ce semestre: PV de jury, lettres individuelles,
|
||||
tableaux récapitulatifs.</p><p class="help">Les documents archivés sont
|
||||
@ -260,7 +253,6 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
||||
}">Paramétrage</a>"
|
||||
(accessible à l'administrateur du département).</em>
|
||||
</p>""",
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
|
||||
descr = [
|
||||
@ -312,7 +304,17 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
|
||||
html_foot_markup=menu_choix_groupe,
|
||||
)
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + "\n" + tf[1] + "\n".join(F)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title="Archiver les PV et résultats",
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
content="<h2>Archiver les PV et résultats du semestre</h2>"
|
||||
+ "\n".join(H)
|
||||
+ "\n"
|
||||
+ tf[1]
|
||||
+ "\n".join(F),
|
||||
)
|
||||
elif tf[0] == -1:
|
||||
msg = "Opération annulée"
|
||||
else:
|
||||
@ -372,7 +374,7 @@ def formsemestre_list_archives(formsemestre_id):
|
||||
}
|
||||
archives_descr.append(a)
|
||||
|
||||
H = [html_sco_header.html_sem_header("Archive des PV et résultats ")]
|
||||
H = []
|
||||
if not archives_descr:
|
||||
H.append("<p>aucune archive enregistrée</p>")
|
||||
else:
|
||||
@ -400,7 +402,9 @@ def formsemestre_list_archives(formsemestre_id):
|
||||
H.append("</ul></li>")
|
||||
H.append("</ul>")
|
||||
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
return render_template(
|
||||
"sco_page.j2", title="Archive des PV et résultats", content="\n".join(H)
|
||||
)
|
||||
|
||||
|
||||
def formsemestre_get_archived_file(formsemestre_id, archive_name, filename):
|
||||
|
@ -56,7 +56,6 @@ from app.models import (
|
||||
)
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError, ScoTemporaryError
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import htmlutils
|
||||
from app.scodoc import sco_assiduites
|
||||
from app.scodoc import sco_bulletins_generator
|
||||
@ -960,7 +959,21 @@ def formsemestre_bulletinetud(
|
||||
elif fmt == "pdfmail":
|
||||
return ""
|
||||
H = [
|
||||
_formsemestre_bulletinetud_header_html(etud, formsemestre, fmt, version),
|
||||
render_template(
|
||||
"bul_head.j2",
|
||||
etud=etud,
|
||||
fmt=fmt,
|
||||
formsemestre=formsemestre,
|
||||
menu_autres_operations=make_menu_autres_operations(
|
||||
etud=etud,
|
||||
formsemestre=formsemestre,
|
||||
endpoint="notes.formsemestre_bulletinetud",
|
||||
version=version,
|
||||
),
|
||||
scu=scu,
|
||||
time=time,
|
||||
version=version,
|
||||
),
|
||||
bulletin,
|
||||
render_template(
|
||||
"bul_foot.j2",
|
||||
@ -971,10 +984,18 @@ def formsemestre_bulletinetud(
|
||||
inscription_courante=etud.inscription_courante(),
|
||||
inscription_str=etud.inscription_descr()["inscription_str"],
|
||||
),
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
|
||||
return "".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title=f"Bulletin de {etud.nomprenom}",
|
||||
content="".join(H),
|
||||
javascripts=[
|
||||
"js/bulletin.js",
|
||||
"libjs/d3.v3.min.js",
|
||||
"js/radar_bulletin.js",
|
||||
],
|
||||
cssstyles=["css/radar_bulletin.css"],
|
||||
)
|
||||
|
||||
|
||||
def can_send_bulletin_by_mail(formsemestre_id):
|
||||
@ -1312,38 +1333,3 @@ def make_menu_autres_operations(
|
||||
},
|
||||
]
|
||||
return htmlutils.make_menu("Autres opérations", menu_items, alone=True)
|
||||
|
||||
|
||||
def _formsemestre_bulletinetud_header_html(
|
||||
etud,
|
||||
formsemestre: FormSemestre,
|
||||
fmt=None,
|
||||
version=None,
|
||||
):
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"Bulletin de {etud.nomprenom}",
|
||||
javascripts=[
|
||||
"js/bulletin.js",
|
||||
"libjs/d3.v3.min.js",
|
||||
"js/radar_bulletin.js",
|
||||
],
|
||||
cssstyles=["css/radar_bulletin.css"],
|
||||
),
|
||||
render_template(
|
||||
"bul_head.j2",
|
||||
etud=etud,
|
||||
fmt=fmt,
|
||||
formsemestre=formsemestre,
|
||||
menu_autres_operations=make_menu_autres_operations(
|
||||
etud=etud,
|
||||
formsemestre=formsemestre,
|
||||
endpoint="notes.formsemestre_bulletinetud",
|
||||
version=version,
|
||||
),
|
||||
scu=scu,
|
||||
time=time,
|
||||
version=version,
|
||||
),
|
||||
]
|
||||
return "\n".join(H)
|
||||
|
@ -29,7 +29,7 @@
|
||||
Rapport (table) avec dernier semestre fréquenté et débouché de chaque étudiant
|
||||
"""
|
||||
import http
|
||||
from flask import url_for, g, request
|
||||
from flask import g, render_template, request, url_for
|
||||
|
||||
from app import log
|
||||
from app.comp import res_sem
|
||||
@ -40,7 +40,6 @@ import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import safehtml
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_permissions_check
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_tag_module
|
||||
@ -76,9 +75,9 @@ def report_debouche_date(start_year=None, fmt="html"):
|
||||
tab.base_url = f"{request.base_url}?start_year={start_year}"
|
||||
return tab.make_page(
|
||||
title="""<h2 class="formsemestre">Débouchés étudiants </h2>""",
|
||||
javascripts=["js/etud_info.js"],
|
||||
fmt=fmt,
|
||||
with_html_headers=True,
|
||||
template="sco_page_dept.j2",
|
||||
)
|
||||
|
||||
|
||||
@ -227,14 +226,16 @@ def table_debouche_etudids(etudids, keep_numeric=True):
|
||||
|
||||
def report_debouche_ask_date(msg: str) -> str:
|
||||
"""Formulaire demande date départ"""
|
||||
return f"""{html_sco_header.sco_header()}
|
||||
return render_template(
|
||||
"sco_page_dept.j2",
|
||||
content=f"""
|
||||
<h2>Table des débouchés des étudiants</h2>
|
||||
<form method="GET">
|
||||
{msg}
|
||||
<input type="text" name="start_year" value="" size=10/>
|
||||
</form>
|
||||
{html_sco_header.sco_footer()}
|
||||
"""
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
@ -324,11 +325,11 @@ def itemsuivi_set_date(itemsuivi_id, item_date):
|
||||
return ("", 204)
|
||||
|
||||
|
||||
def itemsuivi_set_situation(object, value):
|
||||
def itemsuivi_set_situation(obj, value):
|
||||
"""set situation"""
|
||||
if not sco_permissions_check.can_edit_suivi():
|
||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||
itemsuivi_id = object
|
||||
itemsuivi_id = obj
|
||||
situation = value.strip("-_ \t")
|
||||
# log('itemsuivi_set_situation %s : %s' % (itemsuivi_id, situation))
|
||||
cnx = ndb.GetDBConnexion()
|
||||
|
@ -29,7 +29,7 @@
|
||||
(portage from DTML)
|
||||
"""
|
||||
import flask
|
||||
from flask import flash, g, url_for, request
|
||||
from flask import flash, g, url_for, render_template, request
|
||||
import sqlalchemy
|
||||
|
||||
from app import db
|
||||
@ -54,7 +54,6 @@ def formation_delete(formation_id=None, dialog_confirmed=False):
|
||||
formation: Formation = Formation.query.get_or_404(formation_id)
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Suppression d'une formation"),
|
||||
f"""<h2>Suppression de la formation {formation.titre} ({formation.acronyme})</h2>""",
|
||||
]
|
||||
formsemestres = formation.formsemestres.all()
|
||||
@ -85,6 +84,7 @@ def formation_delete(formation_id=None, dialog_confirmed=False):
|
||||
OK="Supprimer cette formation",
|
||||
cancel_url=url_for("notes.index_html", scodoc_dept=g.scodoc_dept),
|
||||
parameters={"formation_id": formation_id},
|
||||
template="sco_page_dept.j2",
|
||||
)
|
||||
else:
|
||||
do_formation_delete(formation_id)
|
||||
@ -95,8 +95,9 @@ def formation_delete(formation_id=None, dialog_confirmed=False):
|
||||
}">continuer</a></p>"""
|
||||
)
|
||||
|
||||
H.append(html_sco_header.sco_footer())
|
||||
return "\n".join(H)
|
||||
return render_template(
|
||||
"sco_page-dept.j2", content="\n".join(H), title="Suppression d'une formation"
|
||||
)
|
||||
|
||||
|
||||
def do_formation_delete(formation_id):
|
||||
|
@ -51,7 +51,6 @@ from app.scodoc.sco_exceptions import (
|
||||
ScoGenError,
|
||||
ScoNonEmptyFormationObject,
|
||||
)
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_edit_matiere
|
||||
from app.scodoc import sco_moduleimpl
|
||||
@ -208,8 +207,8 @@ def module_delete(module_id=None):
|
||||
)
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Suppression d'un module"),
|
||||
f"""<h2>Suppression du module {module.titre or "<em>sans titre</em>"} ({module.code})</h2>""",
|
||||
f"""<h2>Suppression du module {module.titre or "<em>sans titre</em>"}
|
||||
({module.code})</h2>""",
|
||||
]
|
||||
|
||||
dest_url = url_for(
|
||||
@ -227,7 +226,11 @@ def module_delete(module_id=None):
|
||||
cancelbutton="Annuler",
|
||||
)
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
||||
return render_template(
|
||||
"sco_page_dept.j2",
|
||||
title="Suppression d'un module",
|
||||
content="\n".join(H) + tf[1],
|
||||
)
|
||||
elif tf[0] == -1:
|
||||
return flask.redirect(dest_url)
|
||||
else:
|
||||
@ -372,16 +375,6 @@ def module_edit(
|
||||
"""
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=page_title,
|
||||
cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css"],
|
||||
javascripts=[
|
||||
"libjs/jQuery-tagEditor/jquery.tag-editor.min.js",
|
||||
"libjs/jQuery-tagEditor/jquery.caret.min.js",
|
||||
"js/module_tag_editor.js",
|
||||
"js/module_edit.js",
|
||||
],
|
||||
),
|
||||
f"""<h2>{title}</h2>""",
|
||||
render_template(
|
||||
"scodoc/help/modules.j2",
|
||||
@ -780,7 +773,7 @@ def module_edit(
|
||||
scu.get_request_args(),
|
||||
descr,
|
||||
html_foot_markup=(
|
||||
f"""<div class="sco_tag_module_edit"><span
|
||||
f"""<div class="scobox sco_tag_module_edit"><span
|
||||
class="sco_tag_edit"><textarea data-module_id="{module_id}" class="module_tag_editor"
|
||||
>{','.join(sco_tag_module.module_tag_list(module_id))}</textarea></span></div>
|
||||
"""
|
||||
@ -793,8 +786,17 @@ def module_edit(
|
||||
)
|
||||
#
|
||||
if tf[0] == 0:
|
||||
return (
|
||||
"\n".join(H)
|
||||
return render_template(
|
||||
"sco_page_dept.j2",
|
||||
title=page_title,
|
||||
cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css"],
|
||||
javascripts=[
|
||||
"libjs/jQuery-tagEditor/jquery.tag-editor.min.js",
|
||||
"libjs/jQuery-tagEditor/jquery.caret.min.js",
|
||||
"js/module_tag_editor.js",
|
||||
"js/module_edit.js",
|
||||
],
|
||||
content="\n".join(H)
|
||||
+ tf[1]
|
||||
+ (
|
||||
f"""
|
||||
@ -805,8 +807,7 @@ def module_edit(
|
||||
"""
|
||||
if not create
|
||||
else ""
|
||||
)
|
||||
+ html_sco_header.sco_footer()
|
||||
),
|
||||
)
|
||||
elif tf[0] == -1:
|
||||
return flask.redirect(
|
||||
@ -904,9 +905,6 @@ def module_table(formation_id):
|
||||
raise ScoValueError("invalid formation !")
|
||||
formation: Formation = Formation.query.get_or_404(formation_id)
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"Liste des modules de {formation.titre}"
|
||||
),
|
||||
f"""<h2>Listes des modules dans la formation {formation.titre} ({formation.acronyme}</h2>
|
||||
<ul class="notes_module_list">
|
||||
""",
|
||||
@ -926,8 +924,11 @@ def module_table(formation_id):
|
||||
)
|
||||
H.append("</li>")
|
||||
H.append("</ul>")
|
||||
H.append(html_sco_header.sco_footer())
|
||||
return "\n".join(H)
|
||||
return render_template(
|
||||
"sco_page_dept.j2",
|
||||
title=f"Liste des modules de {formation.titre}",
|
||||
content="\n".join(H),
|
||||
)
|
||||
|
||||
|
||||
def module_is_locked(module_id):
|
||||
|
@ -25,9 +25,8 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Ajout/Modification/Suppression UE
|
||||
"""Ajout/Modification/Suppression UE"""
|
||||
|
||||
"""
|
||||
import re
|
||||
|
||||
import sqlalchemy as sa
|
||||
@ -506,8 +505,11 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
||||
else:
|
||||
clone_form = ""
|
||||
|
||||
return f"""
|
||||
{html_sco_header.sco_header(page_title=title, javascripts=["js/edit_ue.js"])}
|
||||
return render_template(
|
||||
"sco_page_dept.j2",
|
||||
title=title,
|
||||
javascripts=["js/edit_ue.js"],
|
||||
content=f"""
|
||||
<h2>{title}, (formation {formation.acronyme}, version {formation.version})</h2>
|
||||
<p class="help">Les UEs sont des groupes de modules dans une formation donnée,
|
||||
utilisés pour la validation (on calcule des moyennes par UE et applique des
|
||||
@ -530,9 +532,8 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
||||
|
||||
<div id="bonus_description" class="scobox"></div>
|
||||
<div id="ue_list_code" class="sco_box sco_green_bg"></div>
|
||||
|
||||
{html_sco_header.sco_footer()}
|
||||
"""
|
||||
""",
|
||||
)
|
||||
elif tf[0] == 1:
|
||||
if create:
|
||||
if not tf[2]["ue_code"]:
|
||||
@ -764,20 +765,6 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
||||
"delete_small_dis_img", title="Suppression impossible (module utilisé)"
|
||||
)
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
cssstyles=html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
||||
+ ["libjs/jQuery-tagEditor/jquery.tag-editor.css", "css/ue_table.css"],
|
||||
javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS
|
||||
+ [
|
||||
"libjs/jinplace-1.2.1.min.js",
|
||||
"js/ue_list.js",
|
||||
"js/edit_ue.js",
|
||||
"libjs/jQuery-tagEditor/jquery.tag-editor.min.js",
|
||||
"libjs/jQuery-tagEditor/jquery.caret.min.js",
|
||||
"js/module_tag_editor.js",
|
||||
],
|
||||
page_title=f"Formation {formation.acronyme} v{formation.version}",
|
||||
),
|
||||
f"""<h2>{formation.html()} {lockicon}
|
||||
</h2>
|
||||
""",
|
||||
@ -1068,8 +1055,20 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
warn, _ = sco_formsemestre_validation.check_formation_ues(formation)
|
||||
H.append(warn)
|
||||
|
||||
H.append(html_sco_header.sco_footer())
|
||||
return "".join(H)
|
||||
return render_template(
|
||||
"sco_page_dept.j2",
|
||||
content="".join(H),
|
||||
page_title=f"Formation {formation.acronyme} v{formation.version}",
|
||||
cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css", "css/ue_table.css"],
|
||||
javascripts=[
|
||||
"libjs/jinplace-1.2.1.min.js",
|
||||
"js/ue_list.js",
|
||||
"js/edit_ue.js",
|
||||
"libjs/jQuery-tagEditor/jquery.tag-editor.min.js",
|
||||
"libjs/jQuery-tagEditor/jquery.caret.min.js",
|
||||
"js/module_tag_editor.js",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def _html_select_semestre_idx(formation_id, semestre_ids, semestre_idx):
|
||||
@ -1129,9 +1128,7 @@ def _ue_table_ues(
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
ue_id=ue["ue_id"],
|
||||
)
|
||||
ue[
|
||||
"code_apogee_str"
|
||||
] = f""", Apo: <span
|
||||
ue["code_apogee_str"] = f""", Apo: <span
|
||||
class="{klass}" data-url="{edit_url}" id="{ue['ue_id']}"
|
||||
data-placeholder="{scu.APO_MISSING_CODE_STR}">{
|
||||
ue["code_apogee"] or ""
|
||||
|
@ -597,8 +597,6 @@ def _view_etuds_page(
|
||||
return f"""
|
||||
{html_sco_header.sco_header(
|
||||
page_title=title,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
)}
|
||||
<h2>{title}</h2>
|
||||
|
||||
@ -751,8 +749,6 @@ def view_apo_csv(etape_apo="", semset_id="", fmt="html"):
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"""Maquette Apogée enregistrée pour {etape_apo}""",
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
),
|
||||
f"""<h2>Étudiants dans la maquette Apogée {etape_apo}</h2>
|
||||
<p>Pour l'ensemble <a class="stdlink" href="{
|
||||
|
@ -27,16 +27,16 @@
|
||||
|
||||
"""Vérification des absences à une évaluation
|
||||
"""
|
||||
from flask import url_for, g
|
||||
from flask import g, render_template, url_for
|
||||
from flask_sqlalchemy.query import Query
|
||||
|
||||
from app import db
|
||||
from app.models import Evaluation, FormSemestre, Identite, Assiduite
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_groups
|
||||
from app.views import ScoData
|
||||
|
||||
|
||||
def evaluation_check_absences(evaluation: Evaluation):
|
||||
@ -109,8 +109,10 @@ def evaluation_check_absences(evaluation: Evaluation):
|
||||
|
||||
def evaluation_check_absences_html(
|
||||
evaluation: Evaluation, with_header=True, show_ok=True
|
||||
):
|
||||
"""Affiche état vérification absences d'une évaluation"""
|
||||
) -> str:
|
||||
"""Affiche état vérification absences d'une évaluation.
|
||||
Si with_header, génère page complète, sinon fragment html.
|
||||
"""
|
||||
(
|
||||
note_but_abs, # une note alors qu'il était signalé abs
|
||||
abs_non_signalee, # note ABS alors que pas signalé abs
|
||||
@ -121,10 +123,6 @@ def evaluation_check_absences_html(
|
||||
|
||||
if with_header:
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Vérification absences à l'évaluation",
|
||||
formsemestre_id=evaluation.moduleimpl.formsemestre_id,
|
||||
),
|
||||
sco_evaluations.evaluation_describe(evaluation_id=evaluation.id),
|
||||
"""<p class="help">Vérification de la cohérence entre les notes saisies
|
||||
et les absences signalées.</p>""",
|
||||
@ -208,19 +206,19 @@ def evaluation_check_absences_html(
|
||||
etudlist(abs_but_exc)
|
||||
|
||||
if with_header:
|
||||
H.append(html_sco_header.sco_footer())
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title="Vérification absences à l'évaluation",
|
||||
content="<h2>Vérification absences à l'évaluation</h2>" + "\n".join(H),
|
||||
sco=ScoData(formsemestre=evaluation.moduleimpl.formsemestre),
|
||||
)
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def formsemestre_check_absences_html(formsemestre_id):
|
||||
"""Affiche etat verification absences pour toutes les evaluations du semestre !"""
|
||||
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
||||
dept_id=g.scodoc_dept_id, id=formsemestre_id
|
||||
).first_or_404()
|
||||
def formsemestre_check_absences_html(formsemestre_id: int):
|
||||
"""Affiche état vérification absences pour toutes les évaluations du semestre."""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Vérification absences aux évaluations de ce semestre",
|
||||
),
|
||||
"""<p class="help">Vérification de la cohérence entre les notes saisies
|
||||
et les absences signalées.
|
||||
Sont listés tous les modules avec des évaluations.<br>Aucune action n'est effectuée:
|
||||
@ -248,5 +246,9 @@ def formsemestre_check_absences_html(formsemestre_id):
|
||||
)
|
||||
H.append("</div>")
|
||||
|
||||
H.append(html_sco_header.sco_footer())
|
||||
return "\n".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
content="<h2>Vérification absences aux évaluations de ce semestre</h2>"
|
||||
+ "\n".join(H),
|
||||
title="Vérification absences aux évaluations de ce semestre",
|
||||
)
|
||||
|
@ -10,7 +10,7 @@ avec leur état.
|
||||
Sur une idée de Pascal Bouron, de Lyon.
|
||||
"""
|
||||
import time
|
||||
from flask import g, url_for
|
||||
from flask import g, render_template, url_for
|
||||
|
||||
from app import db
|
||||
from app.models import Evaluation, FormSemestre
|
||||
@ -23,7 +23,7 @@ import app.scodoc.sco_utils as scu
|
||||
|
||||
def evaluations_recap(formsemestre_id: int) -> str:
|
||||
"""Page récap. de toutes les évaluations d'un semestre"""
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
rows, titles = evaluations_recap_table(formsemestre)
|
||||
column_ids = titles.keys()
|
||||
filename = scu.sanitize_filename(
|
||||
@ -32,11 +32,14 @@ def evaluations_recap(formsemestre_id: int) -> str:
|
||||
if not rows:
|
||||
return '<div class="evaluations_recap"><div class="message">aucune évaluation</div></div>'
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Évaluations du semestre",
|
||||
javascripts=["js/evaluations_recap.js"],
|
||||
),
|
||||
f"""<div class="evaluations_recap"><table class="evaluations_recap compact {
|
||||
f"""<h2>Évaluations du semestre</h2>
|
||||
<div>
|
||||
<a class="stdlink" href="{url_for(
|
||||
'notes.formsemestre_check_absences_html',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id,
|
||||
)}">Vérifier les absences aux évaluations</a>
|
||||
</div>
|
||||
<div class="evaluations_recap"><table class="evaluations_recap compact {
|
||||
'apc' if formsemestre.formation.is_apc() else 'classic'
|
||||
}"
|
||||
data-filename="{filename}">""",
|
||||
@ -56,13 +59,19 @@ def evaluations_recap(formsemestre_id: int) -> str:
|
||||
|
||||
H.append(
|
||||
"""</tbody></table></div>
|
||||
<div class="help">Les étudiants démissionnaires ou défaillants ne sont pas pris en compte dans cette table.</div>
|
||||
<div class="help">Les étudiants démissionnaires ou défaillants ne sont
|
||||
pas pris en compte dans cette table.</div>
|
||||
"""
|
||||
)
|
||||
H.append(
|
||||
html_sco_header.sco_footer(),
|
||||
)
|
||||
return "".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title="Évaluations du semestre",
|
||||
javascripts=["js/evaluations_recap.js"],
|
||||
content="".join(H),
|
||||
)
|
||||
|
||||
|
||||
def evaluations_recap_table(formsemestre: FormSemestre) -> list[dict]:
|
||||
|
@ -31,9 +31,7 @@ import collections
|
||||
import datetime
|
||||
import operator
|
||||
|
||||
from flask import url_for
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask import g, render_template, request, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
from app import db
|
||||
@ -45,8 +43,6 @@ from app.models import Evaluation, FormSemestre, ModuleImpl, Module
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_cal
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_groups
|
||||
@ -470,7 +466,7 @@ class CalendrierEval(sco_gen_cal.Calendrier):
|
||||
|
||||
# View
|
||||
def formsemestre_evaluations_cal(formsemestre_id):
|
||||
"""Page avec calendrier de toutes les evaluations de ce semestre"""
|
||||
"""Page avec calendrier de toutes les évaluations de ce semestre"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
@ -481,17 +477,17 @@ def formsemestre_evaluations_cal(formsemestre_id):
|
||||
cal = CalendrierEval(year, evaluations, nt)
|
||||
cal_html = cal.get_html()
|
||||
|
||||
return f"""
|
||||
{
|
||||
html_sco_header.html_sem_header(
|
||||
"Evaluations du semestre",
|
||||
cssstyles=["css/calabs.css"],
|
||||
)
|
||||
}
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
cssstyles=["css/calabs.css"],
|
||||
title="Évaluations du semestre",
|
||||
content=f"""
|
||||
<h2>Évaluations du semestre</h2>
|
||||
<div class="cal_evaluations">
|
||||
{ cal_html }
|
||||
</div>
|
||||
<p>soit {nb_evals} évaluations planifiées;
|
||||
<div class="scobox maxwidth">
|
||||
<p>soit {nb_evals} évaluations planifiées :
|
||||
</p>
|
||||
<ul>
|
||||
<li>en <span style=
|
||||
@ -513,8 +509,9 @@ def formsemestre_evaluations_cal(formsemestre_id):
|
||||
)
|
||||
}" class="stdlink">voir les délais de correction</a>
|
||||
</p>
|
||||
{ html_sco_header.sco_footer() }
|
||||
"""
|
||||
</div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
def evaluation_date_first_completion(evaluation_id) -> datetime.datetime:
|
||||
@ -651,7 +648,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True)
|
||||
"""HTML description of evaluation, for page headers
|
||||
edit_in_place: allow in-place editing when permitted (not implemented)
|
||||
"""
|
||||
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||
evaluation = Evaluation.get_evaluation(evaluation_id)
|
||||
modimpl = evaluation.moduleimpl
|
||||
responsable: User = db.session.get(User, modimpl.responsable_id)
|
||||
resp_nomprenom = responsable.get_prenomnom()
|
||||
|
@ -45,6 +45,7 @@ from openpyxl.worksheet.worksheet import Worksheet
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.models.scolar_event import ScolarEvent
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc import notesdb, sco_preferences
|
||||
|
||||
@ -638,11 +639,12 @@ def excel_feuille_listeappel(
|
||||
lines,
|
||||
partitions=None,
|
||||
with_codes=False,
|
||||
with_date_inscription=False,
|
||||
with_paiement=False,
|
||||
server_name=None,
|
||||
edt_params: dict = None,
|
||||
):
|
||||
"""generation feuille appel
|
||||
"""Génération feuille appel.
|
||||
|
||||
edt_params :
|
||||
- "discipline" : Discipline
|
||||
@ -763,7 +765,8 @@ def excel_feuille_listeappel(
|
||||
cells.append(ws.make_cell("etudid", style3))
|
||||
cells.append(ws.make_cell("code_nip", style3))
|
||||
cells.append(ws.make_cell("code_ine", style3))
|
||||
|
||||
if with_date_inscription:
|
||||
cells.append(ws.make_cell("Date inscr.", style3))
|
||||
# case Groupes
|
||||
cells.append(ws.make_cell("Groupes", style3))
|
||||
letter_int += 1
|
||||
@ -805,7 +808,15 @@ def excel_feuille_listeappel(
|
||||
cells.append(ws.make_cell(code_nip, style2t3))
|
||||
code_ine = t.get("code_ine", "")
|
||||
cells.append(ws.make_cell(code_ine, style2t3))
|
||||
|
||||
if with_date_inscription:
|
||||
event = ScolarEvent.query.filter_by(
|
||||
etudid=t["etudid"],
|
||||
event_type="INSCRIPTION",
|
||||
formsemestre_id=formsemestre_id,
|
||||
).first()
|
||||
if event:
|
||||
date_inscription = event.event_date
|
||||
cells.append(ws.make_cell(date_inscription, style2t3))
|
||||
cells.append(ws.make_cell(style=style2t3))
|
||||
ws.append_row(cells)
|
||||
ws.set_row_dimension_height(row_id, 30)
|
||||
@ -814,7 +825,7 @@ def excel_feuille_listeappel(
|
||||
ws.append_blank_row()
|
||||
|
||||
# bas de page (date, serveur)
|
||||
dt = time.strftime("%d/%m/%Y à %Hh%M")
|
||||
dt = time.strftime(scu.DATEATIME_FMT)
|
||||
if server_name:
|
||||
dt += " sur " + server_name
|
||||
cell_2 = ws.make_cell(("Liste éditée le " + dt), style1i)
|
||||
|
@ -52,10 +52,12 @@ class ScoValueError(ScoException):
|
||||
|
||||
# mal nommée: super classe de toutes les exceptions avec page
|
||||
# d'erreur gentille.
|
||||
def __init__(self, msg, dest_url=None, safe=False):
|
||||
def __init__(self, msg, dest_url=None, dest_label=None, safe=False):
|
||||
super().__init__(msg)
|
||||
# champs utilisés par template sco_value_error.j2
|
||||
self.dest_url = dest_url
|
||||
self.safe = safe # utilisé par template sco_value_error.j2
|
||||
self.dest_label = dest_label
|
||||
self.safe = safe
|
||||
|
||||
|
||||
class ScoPermissionDenied(ScoValueError):
|
||||
|
@ -25,8 +25,8 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Export d'une table avec les résultats de tous les étudiants
|
||||
"""
|
||||
"""Export d'une table avec les résultats de tous les étudiants"""
|
||||
|
||||
from flask import url_for, g, request
|
||||
|
||||
from app.comp import res_sem
|
||||
@ -73,9 +73,7 @@ def _build_results_table(start_date=None, end_date=None, types_parcours=[]):
|
||||
formsemestre_ids_parcours = [sem["formsemestre_id"] for sem in semlist_parcours]
|
||||
|
||||
# Ensemble des étudiants
|
||||
etuds_infos = (
|
||||
{}
|
||||
) # etudid : { formsemestre_id d'inscription le plus recent dans les dates considérées, etud }
|
||||
etuds_infos = {} # etudid : { formsemestre_id d'inscription le plus recent dans les dates considérées, etud }
|
||||
for formsemestre_id in formsemestre_ids_parcours:
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
@ -287,10 +285,7 @@ def scodoc_table_results(
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Export résultats",
|
||||
init_qtip=True,
|
||||
javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS
|
||||
+ ["js/etud_info.js", "js/export_results.js"],
|
||||
cssstyles=html_sco_header.BOOTSTRAP_MULTISELECT_CSS,
|
||||
javascripts=["js/export_results.js"],
|
||||
),
|
||||
# XXX
|
||||
"""
|
||||
@ -327,9 +322,9 @@ _DATE_FORM = """
|
||||
</div>
|
||||
<div>
|
||||
<b>Types de parcours :</b>
|
||||
<select name="types_parcours" id="parcours_sel" class="multiselect" multiple="multiple">
|
||||
<multi-select name="types_parcours" id="parcours_sel" label="Choisir le(s) parcours...">
|
||||
{menu_options}
|
||||
</select>
|
||||
</multi-select>
|
||||
|
||||
<input type="submit" name="" value=" charger " width=100/>
|
||||
</form>
|
||||
|
@ -28,7 +28,7 @@
|
||||
"""Recherche d'étudiants
|
||||
"""
|
||||
import flask
|
||||
from flask import url_for, g, request
|
||||
from flask import url_for, g, render_template, request
|
||||
from flask_login import current_user
|
||||
import sqlalchemy as sa
|
||||
|
||||
@ -36,7 +36,6 @@ from app import db
|
||||
from app.models import Departement, Identite
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc.sco_exceptions import ScoException
|
||||
@ -52,7 +51,7 @@ def form_search_etud(
|
||||
title="Rechercher un étudiant par nom : ",
|
||||
add_headers=False, # complete page
|
||||
):
|
||||
"form recherche par nom"
|
||||
"form recherche par nom: utilisé pour choisir un étudiant à inscrire, par exemple"
|
||||
H = []
|
||||
H.append(
|
||||
f"""<form action="{
|
||||
@ -93,10 +92,8 @@ def form_search_etud(
|
||||
H.append("</form>")
|
||||
|
||||
if add_headers:
|
||||
return (
|
||||
html_sco_header.sco_header(page_title="Choix d'un étudiant")
|
||||
+ "\n".join(H)
|
||||
+ html_sco_header.sco_footer()
|
||||
return render_template(
|
||||
"sco_page.j2", title="Choix d'un étudiant", content="\n".join(H)
|
||||
)
|
||||
else:
|
||||
return "\n".join(H)
|
||||
@ -177,14 +174,7 @@ def search_etud_in_dept(expnom=""):
|
||||
url_args["etudid"] = etuds[0].id
|
||||
return flask.redirect(url_for(endpoint, **url_args))
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Recherche d'un étudiant",
|
||||
no_side_bar=False,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
)
|
||||
]
|
||||
H = []
|
||||
if len(etuds) == 0 and len(etuds) <= 1:
|
||||
H.append("""<h2>chercher un étudiant:</h2>""")
|
||||
else:
|
||||
@ -267,7 +257,9 @@ def search_etud_in_dept(expnom=""):
|
||||
"""<p class="help">La recherche porte sur tout ou partie du NOM ou du NIP
|
||||
de l'étudiant. Saisir au moins deux caractères.</p>"""
|
||||
)
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
return render_template(
|
||||
"sco_page_dept.j2", title="Recherche d'un étudiant", content="\n".join(H)
|
||||
)
|
||||
|
||||
|
||||
def search_etuds_infos(expnom=None, code_nip=None) -> list[dict]:
|
||||
@ -314,7 +306,7 @@ def search_etud_by_name(term: str) -> list:
|
||||
# ---------- Recherche sur plusieurs département
|
||||
|
||||
|
||||
def search_etud_in_accessible_depts(
|
||||
def search_etuds_in_accessible_depts(
|
||||
expnom=None,
|
||||
) -> tuple[list[list[Identite]], list[str]]:
|
||||
"""
|
||||
@ -335,14 +327,14 @@ def search_etud_in_accessible_depts(
|
||||
return result, accessible_depts
|
||||
|
||||
|
||||
def table_etud_in_accessible_depts(expnom=None):
|
||||
def table_etuds_in_accessible_depts(expnom=None):
|
||||
"""
|
||||
Page avec table étudiants trouvés, dans tous les departements.
|
||||
Attention: nous sommes ici au niveau de ScoDoc, pas dans un département
|
||||
"""
|
||||
result, accessible_depts = search_etud_in_accessible_depts(expnom=expnom)
|
||||
result, accessible_depts = search_etuds_in_accessible_depts(expnom=expnom)
|
||||
H = [
|
||||
f"""<div class="table_etud_in_accessible_depts">
|
||||
f"""<div class="table_etuds_in_accessible_depts">
|
||||
<h3>Recherche multi-département de "<tt>{expnom}</tt>"</h3>
|
||||
""",
|
||||
]
|
||||
@ -367,7 +359,7 @@ def table_etud_in_accessible_depts(expnom=None):
|
||||
rows=rows,
|
||||
html_sortable=True,
|
||||
html_class="table_leftalign",
|
||||
# table_id="etud_in_accessible_depts",
|
||||
table_id="etuds_in_accessible_depts",
|
||||
)
|
||||
|
||||
H.append('<div class="table_etud_in_dept">')
|
||||
@ -387,8 +379,10 @@ def table_etud_in_accessible_depts(expnom=None):
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
return (
|
||||
html_sco_header.scodoc_top_html_header(page_title="Choix d'un étudiant")
|
||||
+ "\n".join(H)
|
||||
+ html_sco_header.standard_html_footer()
|
||||
return render_template(
|
||||
"base.j2",
|
||||
title="Choix d'un étudiant",
|
||||
content="\n".join(H),
|
||||
javascripts=["DataTables/datatables.min.js"],
|
||||
cssstyles=["DataTables/datatables.min.css"],
|
||||
)
|
||||
|
@ -174,7 +174,9 @@ def formation_table_recap(formation: Formation, fmt="html") -> Response:
|
||||
preferences=sco_preferences.SemPreferences(),
|
||||
table_id="formation_table_recap",
|
||||
)
|
||||
return tab.make_page(fmt=fmt, javascripts=["js/formation_recap.js"])
|
||||
return tab.make_page(
|
||||
fmt=fmt, javascripts=["js/formation_recap.js"], template="sco_page_dept.j2"
|
||||
)
|
||||
|
||||
|
||||
def export_recap_formations_annee_scolaire(annee_scolaire):
|
||||
|
@ -149,6 +149,7 @@ def formsemestre_associate_new_version(
|
||||
"formation_id": formation_id,
|
||||
"formsemestre_id": formsemestre_id,
|
||||
},
|
||||
template="sco_page_dept.j2",
|
||||
)
|
||||
elif request.method == "POST":
|
||||
if formsemestre_id is not None: # pas dans le form car checkbox disabled
|
||||
|
@ -657,7 +657,7 @@ def formation_list_table(detail: bool) -> GenTable:
|
||||
"version": "Version",
|
||||
"formation_code": "Code",
|
||||
"sems_list_txt": "Semestres",
|
||||
"referentiel": "Réf.",
|
||||
"referentiel": "Réf. Comp.",
|
||||
"date_fin_dernier_sem": "Fin dernier sem.",
|
||||
"annee_dernier_sem": "Année dernier sem.",
|
||||
"semestres_ues": "Semestres avec UEs",
|
||||
|
@ -53,6 +53,7 @@ _formsemestreEditor = ndb.EditableTable(
|
||||
"semestre_id",
|
||||
"formation_id",
|
||||
"titre",
|
||||
"capacite_accueil",
|
||||
"date_debut",
|
||||
"date_fin",
|
||||
"gestion_compensation",
|
||||
|
@ -28,7 +28,7 @@
|
||||
"""Menu "custom" (défini par l'utilisateur) dans les semestres
|
||||
"""
|
||||
import flask
|
||||
from flask import g, url_for, request
|
||||
from flask import g, url_for, render_template, request
|
||||
from flask_login import current_user
|
||||
|
||||
from app.models.config import ScoDocSiteConfig, PersonalizedLink
|
||||
@ -37,9 +37,6 @@ import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import htmlutils
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_edt_cal
|
||||
|
||||
_custommenuEditor = ndb.EditableTable(
|
||||
"notes_formsemestre_custommenu",
|
||||
@ -101,19 +98,18 @@ def formsemestre_custommenu_html(formsemestre_id):
|
||||
"args": {"formsemestre_id": formsemestre_id},
|
||||
}
|
||||
)
|
||||
return htmlutils.make_menu("Liens", menu)
|
||||
return menu
|
||||
|
||||
|
||||
def formsemestre_custommenu_edit(formsemestre_id):
|
||||
"""Dialog to edit the custom menu"""
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
dest_url = url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
H = [
|
||||
html_sco_header.html_sem_header("Modification du menu du semestre "),
|
||||
"""<div class="help">
|
||||
<p>Ce menu, spécifique à chaque semestre, peut être utilisé pour
|
||||
placer des liens vers vos applications préférées.
|
||||
@ -164,7 +160,14 @@ def formsemestre_custommenu_edit(formsemestre_id):
|
||||
name="tf",
|
||||
)
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + "\n" + tf[1] + html_sco_header.sco_footer()
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title="Modification du menu du semestre",
|
||||
content="<h2>Modification du menu du semestre</h2>"
|
||||
+ "\n".join(H)
|
||||
+ "\n"
|
||||
+ tf[1],
|
||||
)
|
||||
elif tf[0] == -1:
|
||||
return flask.redirect(dest_url)
|
||||
else:
|
||||
|
@ -28,8 +28,7 @@
|
||||
"""Form choix modules / responsables et creation formsemestre
|
||||
"""
|
||||
import flask
|
||||
from flask import url_for, flash, redirect
|
||||
from flask import g, request
|
||||
from flask import flash, g, request, redirect, render_template, url_for
|
||||
from flask_login import current_user
|
||||
import sqlalchemy as sa
|
||||
|
||||
@ -61,7 +60,6 @@ from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_vdi import ApoEtapeVDI
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_formsemestre
|
||||
@ -81,36 +79,26 @@ def _default_sem_title(formation):
|
||||
|
||||
def formsemestre_createwithmodules():
|
||||
"""Page création d'un semestre"""
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Création d'un semestre",
|
||||
javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
|
||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||
bodyOnLoad="init_tf_form('')",
|
||||
),
|
||||
"""<h2>Mise en place d'un semestre de formation</h2>""",
|
||||
]
|
||||
H = ["""<h2>Mise en place d'un semestre de formation</h2>"""]
|
||||
r = do_formsemestre_createwithmodules()
|
||||
if isinstance(r, str):
|
||||
H.append(r)
|
||||
else:
|
||||
if not isinstance(r, str):
|
||||
return r # response redirect
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
H.append(r)
|
||||
|
||||
return render_template(
|
||||
"sco_page_dept.j2",
|
||||
title="Création d'un semestre",
|
||||
content="\n".join(H),
|
||||
javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
|
||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||
)
|
||||
|
||||
|
||||
def formsemestre_editwithmodules(formsemestre_id):
|
||||
def formsemestre_editwithmodules(formsemestre_id: int):
|
||||
"""Page modification semestre"""
|
||||
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
||||
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
||||
).first_or_404()
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Modification du semestre",
|
||||
javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
|
||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||
bodyOnLoad="init_tf_form('')",
|
||||
)
|
||||
]
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
H = []
|
||||
if not formsemestre.etat:
|
||||
H.append(
|
||||
f"""<p>{scu.icontag(
|
||||
@ -138,7 +126,13 @@ def formsemestre_editwithmodules(formsemestre_id):
|
||||
"""
|
||||
)
|
||||
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
|
||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||
title="Modification du semestre",
|
||||
content="<h2>Modification du semestre</h2>" + "\n".join(H),
|
||||
)
|
||||
|
||||
|
||||
def can_edit_sem(formsemestre_id: int = None, sem=None):
|
||||
@ -350,8 +344,6 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
||||
"labels": modalites_titles,
|
||||
},
|
||||
),
|
||||
]
|
||||
modform.append(
|
||||
(
|
||||
"semestre_id",
|
||||
{
|
||||
@ -367,10 +359,21 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
||||
"attributes": ['onchange="change_semestre_id();"'] if is_apc else "",
|
||||
},
|
||||
),
|
||||
)
|
||||
(
|
||||
"capacite_accueil",
|
||||
{
|
||||
"title": "Capacité d'accueil",
|
||||
"size": 4,
|
||||
"explanation": "nombre max d'inscrits (hors démissionnaires). Laisser vide si pas de limite.",
|
||||
"type": "int",
|
||||
"allow_null": True,
|
||||
},
|
||||
),
|
||||
]
|
||||
etapes = sco_portal_apogee.get_etapes_apogee_dept()
|
||||
# Propose les etapes renvoyées par le portail
|
||||
# et ajoute les étapes du semestre qui ne sont pas dans la liste (soit la liste a changé, soit l'étape a été ajoutée manuellement)
|
||||
# et ajoute les étapes du semestre qui ne sont pas dans la liste
|
||||
# (soit la liste a changé, soit l'étape a été ajoutée manuellement)
|
||||
etapes_set = {et[0] for et in etapes}
|
||||
if edit:
|
||||
for etape_vdi in formsemestre.etapes_apo_vdi():
|
||||
@ -508,6 +511,12 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||
}">Modifier les codes Apogée et emploi du temps des modules</a>
|
||||
</p>
|
||||
|
||||
<p><a class="stdlink" href="{url_for("notes.edit_formsemestre_description",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||
}">Éditer la description externe du semestre</a>
|
||||
</p>
|
||||
|
||||
<h3>Sélectionner les modules, leurs responsables et les étudiants
|
||||
à inscrire:</h3>
|
||||
"""
|
||||
@ -843,6 +852,14 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
||||
):
|
||||
msg = '<ul class="tf-msg"><li class="tf-msg">Code étape Apogée manquant</li></ul>'
|
||||
|
||||
# check capacité accueil si indiquée
|
||||
if edit and isinstance(tf[2]["capacite_accueil"], int):
|
||||
new_capacite_accueil = tf[2]["capacite_accueil"]
|
||||
inscriptions = formsemestre.get_inscrits(etats={scu.INSCRIT, scu.DEF})
|
||||
if len(inscriptions) > new_capacite_accueil:
|
||||
msg = f"""<ul class="tf-msg"><li class="tf-msg">Capacité d'accueil insuffisante
|
||||
(il y a {len(inscriptions)} inscrits non démissionaires)</li></ul>"""
|
||||
|
||||
if tf[0] == 0 or msg:
|
||||
return f"""<p>Formation <a class="discretelink" href="{
|
||||
url_for("notes.ue_table", scodoc_dept=g.scodoc_dept,
|
||||
@ -1164,13 +1181,9 @@ def formsemestre_clone(formsemestre_id):
|
||||
}
|
||||
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Copie du semestre",
|
||||
javascripts=["libjs/AutoSuggest.js"],
|
||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||
bodyOnLoad="init_tf_form('')",
|
||||
),
|
||||
"""<p class="help">Cette opération duplique un semestre: on reprend les mêmes modules et responsables. Aucun étudiant n'est inscrit.</p>""",
|
||||
"""<p class="help">Cette opération duplique un semestre:
|
||||
on reprend les mêmes modules et responsables.
|
||||
Aucun étudiant n'est inscrit.</p>""",
|
||||
]
|
||||
|
||||
descr = [
|
||||
@ -1247,7 +1260,13 @@ def formsemestre_clone(formsemestre_id):
|
||||
if ndb.DateDMYtoISO(tf[2]["date_debut"]) > ndb.DateDMYtoISO(tf[2]["date_fin"]):
|
||||
msg = '<ul class="tf-msg"><li class="tf-msg">Dates de début et fin incompatibles !</li></ul>'
|
||||
if tf[0] == 0 or msg:
|
||||
return "".join(H) + msg + tf[1] + html_sco_header.sco_footer()
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
javascripts=["libjs/AutoSuggest.js"],
|
||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||
title="Copie du semestre",
|
||||
content="".join(H) + msg + tf[1],
|
||||
)
|
||||
elif tf[0] == -1: # cancel
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
@ -1287,6 +1306,7 @@ def do_formsemestre_clone(
|
||||
clone_partitions=False,
|
||||
):
|
||||
"""Clone a semestre: make copy, same modules, same options, same resps, same partitions.
|
||||
Clone description.
|
||||
New dates, responsable_id
|
||||
"""
|
||||
log(f"do_formsemestre_clone: {orig_formsemestre_id}")
|
||||
@ -1375,10 +1395,15 @@ def do_formsemestre_clone(
|
||||
|
||||
# 5- Copie les parcours
|
||||
formsemestre.parcours = formsemestre_orig.parcours
|
||||
|
||||
# 6- Copy description
|
||||
if formsemestre_orig.description:
|
||||
formsemestre.description = formsemestre_orig.description.clone()
|
||||
|
||||
db.session.add(formsemestre)
|
||||
db.session.commit()
|
||||
|
||||
# 6- Copy partitions and groups
|
||||
# 7- Copy partitions and groups
|
||||
if clone_partitions:
|
||||
sco_groups_copy.clone_partitions_and_groups(
|
||||
orig_formsemestre_id, formsemestre.id
|
||||
@ -1391,8 +1416,8 @@ def formsemestre_delete(formsemestre_id: int) -> str | flask.Response:
|
||||
"""Delete a formsemestre (affiche avertissements)"""
|
||||
formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
H = [
|
||||
html_sco_header.html_sem_header("Suppression du semestre"),
|
||||
"""<div class="ue_warning"><span>Attention !</span>
|
||||
"""<h2>Suppression du semestre</h2>
|
||||
<div class="ue_warning"><span>Attention !</span>
|
||||
<p class="help">A n'utiliser qu'en cas d'erreur lors de la saisie d'une formation. Normalement,
|
||||
<b>un semestre ne doit jamais être supprimé</b>
|
||||
(on perd la mémoire des notes et de tous les événements liés à ce semestre !).
|
||||
@ -1445,8 +1470,9 @@ Ceci n'est possible que si :
|
||||
)
|
||||
else:
|
||||
H.append(tf[1])
|
||||
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
return render_template(
|
||||
"sco_page.j2", title="Suppression du semestre", content="\n".join(H)
|
||||
)
|
||||
|
||||
if tf[0] == -1: # cancel
|
||||
return flask.redirect(
|
||||
@ -1746,7 +1772,6 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||
if not ok:
|
||||
return err
|
||||
|
||||
footer = html_sco_header.sco_footer()
|
||||
help_msg = """<p class="help">
|
||||
Seuls les modules ont un coefficient. Cependant, il est nécessaire d'affecter un coefficient aux UE capitalisée pour pouvoir les prendre en compte dans la moyenne générale.
|
||||
</p>
|
||||
@ -1769,7 +1794,6 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||
</p>
|
||||
"""
|
||||
H = [
|
||||
html_sco_header.html_sem_header("Coefficients des UE du semestre"),
|
||||
help_msg,
|
||||
]
|
||||
#
|
||||
@ -1812,7 +1836,11 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||
initvalues=initvalues,
|
||||
)
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + tf[1] + footer
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title="Coefficients des UE du semestre",
|
||||
content="<h2>Coefficients des UE du semestre</h2>" + "\n".join(H) + tf[1],
|
||||
)
|
||||
elif tf[0] == -1:
|
||||
return redirect(
|
||||
url_for(
|
||||
@ -1849,11 +1877,13 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||
)
|
||||
|
||||
if not ok:
|
||||
return (
|
||||
"\n".join(H)
|
||||
render_template(
|
||||
"sco_page.j2",
|
||||
title="Coefficients des UE du semestre",
|
||||
content="<h2>Coefficients des UE du semestre</h2>"
|
||||
+ "\n".join(H)
|
||||
+ "<p><ul><li>%s</li></ul></p>" % "</li><li>".join(msg)
|
||||
+ tf[1]
|
||||
+ footer
|
||||
+ tf[1],
|
||||
)
|
||||
|
||||
# apply modifications
|
||||
@ -1876,20 +1906,25 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
||||
for ue in ue_deleted:
|
||||
message.append(f"<li>{ue.acronyme}</li>")
|
||||
message.append("</ul>")
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=formsemestre_id
|
||||
) # > modif coef UE cap (modifs notes de _certains_ etudiants)
|
||||
else:
|
||||
message = ["""<h3>Aucune modification</h3>"""]
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=formsemestre_id
|
||||
) # > modif coef UE cap (modifs notes de _certains_ etudiants)
|
||||
|
||||
return f"""{html_sco_header.html_sem_header("Coefficients des UE du semestre")}
|
||||
{" ".join(message)}
|
||||
<p><a class="stdlink" href="{url_for("notes.formsemestre_status",
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title="Coefficients des UE du semestre",
|
||||
content=f"""
|
||||
<h2>Coefficients des UE du semestre</h2>
|
||||
{" ".join(message)}
|
||||
<p><a class="stdlink" href="{url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
||||
}">Revenir au tableau de bord</a>
|
||||
</p>
|
||||
{footer}
|
||||
"""
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
def _get_sem_ues_modimpls(
|
||||
|
@ -51,7 +51,6 @@ import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_formsemestre_validation
|
||||
|
@ -31,7 +31,7 @@ import collections
|
||||
import time
|
||||
|
||||
import flask
|
||||
from flask import flash, url_for, g, request
|
||||
from flask import flash, url_for, g, render_template, request
|
||||
|
||||
from app import db
|
||||
from app.comp import res_sem
|
||||
@ -85,43 +85,6 @@ def do_formsemestre_inscription_listinscrits(formsemestre_id):
|
||||
return r
|
||||
|
||||
|
||||
def do_formsemestre_inscription_create(args, method=None):
|
||||
"create a formsemestre_inscription (and sco event)"
|
||||
cnx = ndb.GetDBConnexion()
|
||||
log(f"do_formsemestre_inscription_create: args={args}")
|
||||
sems = sco_formsemestre.do_formsemestre_list(
|
||||
{"formsemestre_id": args["formsemestre_id"]}
|
||||
)
|
||||
if len(sems) != 1:
|
||||
raise ScoValueError(f"code de semestre invalide: {args['formsemestre_id']}")
|
||||
sem = sems[0]
|
||||
# check lock
|
||||
if not sem["etat"]:
|
||||
raise ScoValueError("inscription: semestre verrouille")
|
||||
#
|
||||
r = _formsemestre_inscriptionEditor.create(cnx, args)
|
||||
# Evenement
|
||||
sco_etud.scolar_events_create(
|
||||
cnx,
|
||||
args={
|
||||
"etudid": args["etudid"],
|
||||
"event_date": time.strftime(scu.DATE_FMT),
|
||||
"formsemestre_id": args["formsemestre_id"],
|
||||
"event_type": "INSCRIPTION",
|
||||
},
|
||||
)
|
||||
# Log etudiant
|
||||
Scolog.logdb(
|
||||
method=method,
|
||||
etudid=args["etudid"],
|
||||
msg=f"inscription en semestre {args['formsemestre_id']}",
|
||||
commit=True,
|
||||
)
|
||||
#
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=args["formsemestre_id"])
|
||||
return r
|
||||
|
||||
|
||||
def do_formsemestre_inscription_delete(oid, formsemestre_id=None):
|
||||
"delete formsemestre_inscription"
|
||||
cnx = ndb.GetDBConnexion()
|
||||
@ -196,7 +159,7 @@ def check_if_has_decision_jury(
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
for etudid in etudids:
|
||||
if nt.etud_has_decision(etudid):
|
||||
etud = Identite.query.get(etudid)
|
||||
etud = db.session.get(Identite, etudid)
|
||||
raise ScoValueError(
|
||||
f"""désinscription impossible: l'étudiant {etud.nomprenom} a
|
||||
une décision de jury (la supprimer avant si nécessaire)"""
|
||||
@ -219,12 +182,11 @@ def do_formsemestre_desinscription(
|
||||
if check_has_dec_jury:
|
||||
check_if_has_decision_jury(formsemestre, [etudid])
|
||||
|
||||
insem = do_formsemestre_inscription_list(
|
||||
args={"formsemestre_id": formsemestre_id, "etudid": etudid}
|
||||
)
|
||||
if not insem:
|
||||
inscr_sem = FormSemestreInscription.query.filter_by(
|
||||
etudid=etudid, formsemestre_id=formsemestre_id
|
||||
).first()
|
||||
if not inscr_sem:
|
||||
raise ScoValueError(f"{etud.nomprenom} n'est pas inscrit au semestre !")
|
||||
insem = insem[0]
|
||||
# -- desinscription de tous les modules
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
@ -248,10 +210,8 @@ def do_formsemestre_desinscription(
|
||||
Partition.formsemestre_remove_etud(formsemestre_id, etud)
|
||||
|
||||
# -- désincription du semestre
|
||||
do_formsemestre_inscription_delete(
|
||||
insem["formsemestre_inscription_id"], formsemestre_id=formsemestre_id
|
||||
)
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
||||
formsemestre.desinscrit_etudiant(etud)
|
||||
|
||||
# --- Semestre extérieur
|
||||
if formsemestre.modalite == "EXT":
|
||||
if 0 == len(formsemestre.inscriptions):
|
||||
@ -263,13 +223,6 @@ def do_formsemestre_desinscription(
|
||||
db.session.commit()
|
||||
flash(f"Semestre extérieur supprimé: {formsemestre.titre_annee()}")
|
||||
|
||||
Scolog.logdb(
|
||||
method="formsemestre_desinscription",
|
||||
etudid=etudid,
|
||||
msg=f"desinscription semestre {formsemestre_id}",
|
||||
commit=True,
|
||||
)
|
||||
|
||||
|
||||
def do_formsemestre_inscription_with_modules(
|
||||
formsemestre_id,
|
||||
@ -283,18 +236,22 @@ def do_formsemestre_inscription_with_modules(
|
||||
"""Inscrit cet etudiant à ce semestre et TOUS ses modules STANDARDS
|
||||
(donc sauf le sport)
|
||||
Si dept_id est spécifié, utilise ce département au lieu du courant.
|
||||
Vérifie la capacité d'accueil.
|
||||
"""
|
||||
etud = Identite.get_etud(etudid)
|
||||
group_ids = group_ids or []
|
||||
if isinstance(group_ids, int):
|
||||
group_ids = [group_ids]
|
||||
# Check that all groups exist before creating the inscription
|
||||
groups = [
|
||||
GroupDescr.query.get_or_404(group_id)
|
||||
for group_id in group_ids
|
||||
if group_id != ""
|
||||
]
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id, dept_id=dept_id)
|
||||
# inscription au semestre
|
||||
# Inscription au semestre
|
||||
args = {"formsemestre_id": formsemestre_id, "etudid": etudid}
|
||||
if etat is not None:
|
||||
args["etat"] = etat
|
||||
if etape is not None:
|
||||
args["etape"] = etape
|
||||
do_formsemestre_inscription_create(args, method=method)
|
||||
formsemestre.inscrit_etudiant(etud, etat=etat, etape=etape, method=method)
|
||||
log(
|
||||
f"""do_formsemestre_inscription_with_modules: etudid={
|
||||
etudid} formsemestre_id={formsemestre_id}"""
|
||||
@ -303,14 +260,13 @@ def do_formsemestre_inscription_with_modules(
|
||||
# 1- inscrit au groupe 'tous'
|
||||
group_id = sco_groups.get_default_group(formsemestre_id)
|
||||
sco_groups.set_group(etudid, group_id)
|
||||
gdone = {group_id: 1} # empeche doublons
|
||||
gdone = {group_id} # empeche doublons
|
||||
|
||||
# 2- inscrit aux groupes
|
||||
for group_id in group_ids:
|
||||
if group_id and group_id not in gdone:
|
||||
_ = GroupDescr.query.get_or_404(group_id)
|
||||
sco_groups.set_group(etudid, group_id)
|
||||
gdone[group_id] = 1
|
||||
for group in groups:
|
||||
if group.id not in gdone:
|
||||
sco_groups.set_group(etudid, group.id)
|
||||
gdone.add(group.id)
|
||||
|
||||
# Inscription à tous les modules de ce semestre
|
||||
for modimpl in formsemestre.modimpls:
|
||||
@ -423,12 +379,7 @@ def formsemestre_inscription_with_modules(
|
||||
etud = Identite.get_etud(etudid)
|
||||
if etud.dept_id != formsemestre.dept_id:
|
||||
raise ScoValueError("l'étudiant n'est pas dans ce département")
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
f"Inscription de {etud.nomprenom} dans ce semestre",
|
||||
)
|
||||
]
|
||||
footer = html_sco_header.sco_footer()
|
||||
H = []
|
||||
# Check 1: déjà inscrit ici ?
|
||||
inscr = FormSemestreInscription.query.filter_by(
|
||||
etudid=etud.id, formsemestre_id=formsemestre.id
|
||||
@ -451,7 +402,12 @@ def formsemestre_inscription_with_modules(
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
return "\n".join(H) + footer
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title=f"Inscription de {etud.nomprenom} dans ce semestre",
|
||||
content=f"<h2>Inscription de {etud.nomprenom} dans ce semestre</h2>"
|
||||
+ "\n".join(H),
|
||||
)
|
||||
# Check 2: déjà inscrit dans un semestre recouvrant les même dates ?
|
||||
# Informe et propose dé-inscriptions
|
||||
others = est_inscrit_ailleurs(etudid, formsemestre_id)
|
||||
@ -473,7 +429,7 @@ def formsemestre_inscription_with_modules(
|
||||
H.append("<ul>")
|
||||
for s in others:
|
||||
H.append(
|
||||
f"""<li><a href="{
|
||||
f"""<li><a class="stdlink" href="{
|
||||
url_for("notes.formsemestre_desinscription", scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=s["formsemestre_id"], etudid=etudid )
|
||||
}" class="stdlink">désinscrire de {s["titreannee"]}
|
||||
@ -481,15 +437,20 @@ def formsemestre_inscription_with_modules(
|
||||
)
|
||||
H.append("</ul>")
|
||||
H.append(
|
||||
f"""<p><a href="{ url_for( "notes.formsemestre_inscription_with_modules",
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid, formsemestre_id=formsemestre_id,
|
||||
multiple_ok=1,
|
||||
group_ids=group_ids )
|
||||
f"""<p><a class="stdlink" href="{
|
||||
url_for( "notes.formsemestre_inscription_with_modules",
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid, formsemestre_id=formsemestre_id,
|
||||
multiple_ok=1,
|
||||
group_ids=group_ids )
|
||||
}">Continuer quand même l'inscription</a>
|
||||
</p>"""
|
||||
# was sco_groups.make_query_groups(group_ids)
|
||||
)
|
||||
return "\n".join(H) + footer
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title=f"Inscription de {etud.nomprenom} dans ce semestre",
|
||||
content=f"<h2>Inscription de {etud.nomprenom} dans ce semestre</h2>"
|
||||
+ "\n".join(H),
|
||||
)
|
||||
#
|
||||
if group_ids is not None:
|
||||
# OK, inscription
|
||||
@ -522,7 +483,12 @@ def formsemestre_inscription_with_modules(
|
||||
</form>
|
||||
"""
|
||||
)
|
||||
return "\n".join(H) + footer
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title=f"Inscription de {etud.nomprenom} dans ce semestre",
|
||||
content=f"<h2>Inscription de {etud.nomprenom} dans ce semestre</h2>"
|
||||
+ "\n".join(H),
|
||||
)
|
||||
|
||||
|
||||
def formsemestre_inscription_option(etudid, formsemestre_id):
|
||||
@ -850,13 +816,7 @@ def formsemestre_inscrits_ailleurs(formsemestre_id):
|
||||
"""Page listant les étudiants inscrits dans un autre semestre
|
||||
dont les dates recouvrent le semestre indiqué.
|
||||
"""
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Inscriptions multiples parmi les étudiants du semestre ",
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
)
|
||||
]
|
||||
H = []
|
||||
insd = list_inscrits_ailleurs(formsemestre_id)
|
||||
# liste ordonnée par nom
|
||||
etudlist = [Identite.get_etud(etudid) for etudid, sems in insd.items() if sems]
|
||||
@ -908,4 +868,9 @@ def formsemestre_inscrits_ailleurs(formsemestre_id):
|
||||
)
|
||||
else:
|
||||
H.append("""<p>Aucun étudiant en inscription multiple (c'est normal) !</p>""")
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title="Inscriptions multiples parmi les étudiants du semestre",
|
||||
content="<h2>Inscriptions multiples parmi les étudiants du semestre</h2>"
|
||||
+ "\n".join(H),
|
||||
)
|
||||
|
@ -59,8 +59,6 @@ import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import htmlutils
|
||||
from app.scodoc import sco_archives_formsemestre
|
||||
from app.scodoc import sco_assiduites as scass
|
||||
from app.scodoc import sco_bulletins
|
||||
@ -335,7 +333,7 @@ def formsemestre_status_menubar(formsemestre: FormSemestre | None) -> str:
|
||||
},
|
||||
{
|
||||
"title": "Exporter table des étudiants",
|
||||
"endpoint": "scolar.groups_view",
|
||||
"endpoint": "scolar.groups_lists",
|
||||
"args": {
|
||||
"fmt": "allxls",
|
||||
"group_ids": sco_groups.get_default_group(
|
||||
@ -354,12 +352,26 @@ def formsemestre_status_menubar(formsemestre: FormSemestre | None) -> str:
|
||||
can_change_groups = formsemestre.can_change_groups()
|
||||
menu_groupes = [
|
||||
{
|
||||
"title": "Listes, photos, feuilles...",
|
||||
"endpoint": "scolar.groups_view",
|
||||
"title": "Listes des groupes",
|
||||
"endpoint": "scolar.groups_lists",
|
||||
"args": {"formsemestre_id": formsemestre_id},
|
||||
"enabled": True,
|
||||
"helpmsg": "Accès aux listes des groupes d'étudiants",
|
||||
},
|
||||
{
|
||||
"title": "Trombinoscopes",
|
||||
"endpoint": "scolar.groups_photos",
|
||||
"args": {"formsemestre_id": formsemestre_id},
|
||||
"enabled": True,
|
||||
"helpmsg": "Accès aux photos des groupes d'étudiants",
|
||||
},
|
||||
{
|
||||
"title": "Assiduité, feuilles d'appel, ...",
|
||||
"endpoint": "scolar.groups_feuilles",
|
||||
"args": {"formsemestre_id": formsemestre_id},
|
||||
"enabled": True,
|
||||
"helpmsg": "Accès aux feuilles d'appel des groupes d'étudiants",
|
||||
},
|
||||
{
|
||||
"title": "Modifier groupes et partitions",
|
||||
"endpoint": "scolar.partition_editor",
|
||||
@ -473,18 +485,20 @@ def formsemestre_status_menubar(formsemestre: FormSemestre | None) -> str:
|
||||
]
|
||||
|
||||
menu_stats = _build_menu_stats(formsemestre)
|
||||
H = [
|
||||
'<ul id="sco_menu">',
|
||||
htmlutils.make_menu("Semestre", menu_semestre),
|
||||
htmlutils.make_menu("Inscriptions", menu_inscriptions),
|
||||
htmlutils.make_menu("Groupes", menu_groupes),
|
||||
htmlutils.make_menu("Notes", menu_notes),
|
||||
htmlutils.make_menu("Jury", menu_jury),
|
||||
htmlutils.make_menu("Statistiques", menu_stats),
|
||||
formsemestre_custommenu_html(formsemestre_id),
|
||||
"</ul>",
|
||||
]
|
||||
return "\n".join(H)
|
||||
|
||||
menus = {
|
||||
"Semestre": menu_semestre,
|
||||
"Inscriptions": menu_inscriptions,
|
||||
"Groupes": menu_groupes,
|
||||
"Notes": menu_notes,
|
||||
"Jury": menu_jury,
|
||||
"Statistiques": menu_stats,
|
||||
"Liens": formsemestre_custommenu_html(formsemestre_id),
|
||||
}
|
||||
|
||||
return render_template(
|
||||
"formsemestre/menu.j2", menu=menus, formsemestre=formsemestre
|
||||
)
|
||||
|
||||
|
||||
# Element HTML decrivant un semestre (barre de menu et infos)
|
||||
@ -739,9 +753,7 @@ def formsemestre_description_table(
|
||||
columns_ids=columns_ids,
|
||||
html_caption=title,
|
||||
html_class="table_leftalign formsemestre_description",
|
||||
html_title=html_sco_header.html_sem_header(
|
||||
"Description du semestre", with_page_header=False
|
||||
),
|
||||
html_title="<h2>Description du semestre</h2>",
|
||||
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
||||
page_title=title,
|
||||
pdf_title=title,
|
||||
@ -790,7 +802,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
||||
})</span></h3>"""
|
||||
)
|
||||
#
|
||||
H.append('<div class="sem-groups-abs">')
|
||||
H.append('<div class="sem-groups-abs space-before-18">')
|
||||
|
||||
disable_abs: str | bool = scass.has_assiduites_disable_pref(formsemestre)
|
||||
show_abs: str = "hidden" if disable_abs else ""
|
||||
@ -800,6 +812,8 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
||||
groups = partition.groups.all()
|
||||
effectifs = {g.id: g.get_nb_inscrits() for g in groups}
|
||||
partition_is_empty = sum(effectifs.values()) == 0
|
||||
if partition_is_empty and (partition.is_default() or partition.is_parcours()):
|
||||
continue # inutile de montrer des partitions vides non éditables
|
||||
H.append(
|
||||
f"""
|
||||
<div class="sem-groups-partition">
|
||||
@ -826,7 +840,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
||||
<div class="sem-groups-list">
|
||||
<div>
|
||||
<a class="stdlink" href="{
|
||||
url_for("scolar.groups_view",
|
||||
url_for("scolar.groups_lists",
|
||||
group_ids=group.id,
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
)
|
||||
@ -897,7 +911,9 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
||||
)
|
||||
|
||||
H.append("</div>") # /sem-groups-assi
|
||||
if partition_is_empty and not partition.is_default():
|
||||
if partition_is_empty and not (
|
||||
partition.is_default() or partition.is_parcours()
|
||||
):
|
||||
H.append(
|
||||
'<div class="help sem-groups-none">Aucun groupe peuplé dans cette partition'
|
||||
)
|
||||
@ -912,41 +928,72 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
||||
H.append("</div>")
|
||||
H.append("</div>") # /sem-groups-partition
|
||||
|
||||
# Boite avec liens divers
|
||||
autres_liens = []
|
||||
if formsemestre.can_change_groups():
|
||||
H.append(
|
||||
f"""<h4><a class="stdlink"
|
||||
autres_liens.append(
|
||||
f"""<a class="stdlink"
|
||||
title="une partition est un ensemble de groupes: TD, TP, ..."
|
||||
href="{url_for("scolar.partition_editor",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
edit_partition=1)
|
||||
}">Ajouter une partition</a></h4>"""
|
||||
}">Ajouter une partition</a>"""
|
||||
)
|
||||
|
||||
# --- Formulaire importation Assiduité excel (si autorisé)
|
||||
if current_user.has_permission(Permission.AbsChange) and not disable_abs:
|
||||
H.append(
|
||||
f"""<p>
|
||||
autres_liens.append(
|
||||
f"""
|
||||
<a class="stdlink" href="{url_for('assiduites.feuille_abs_formsemestre',
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id)}">
|
||||
Importation de l'assiduité depuis un fichier excel</a>
|
||||
</p>"""
|
||||
"""
|
||||
)
|
||||
|
||||
# --- Lien Traitement Justificatifs:
|
||||
|
||||
if (
|
||||
current_user.has_permission(Permission.AbsJustifView)
|
||||
and current_user.has_permission(Permission.JustifValidate)
|
||||
and not disable_abs
|
||||
):
|
||||
H.append(
|
||||
f"""<p>
|
||||
autres_liens.append(
|
||||
f"""
|
||||
<a class="stdlink" href="{url_for('assiduites.traitement_justificatifs',
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id)}">
|
||||
Traitement des justificatifs d'absence</a>
|
||||
</p>"""
|
||||
"""
|
||||
)
|
||||
|
||||
# --- Lien pour mail aux enseignants
|
||||
# Construit la liste de tous les enseignants de ce semestre:
|
||||
mails_enseignants = set(u.email for u in formsemestre.responsables)
|
||||
for modimpl in formsemestre.modimpls_sorted:
|
||||
mails_enseignants.add(sco_users.user_info(modimpl.responsable_id)["email"])
|
||||
mails_enseignants |= {u.email for u in modimpl.enseignants if u.email}
|
||||
adrlist = list(mails_enseignants - {None, ""})
|
||||
if adrlist:
|
||||
autres_liens.append(
|
||||
f"""
|
||||
<a class="stdlink" href="mailto:?cc={','.join(adrlist)}">Courrier aux {
|
||||
len(adrlist)} enseignants du semestre</a>
|
||||
"""
|
||||
)
|
||||
|
||||
# Met le tout en boite
|
||||
if autres_liens:
|
||||
H.append(
|
||||
f"""
|
||||
<div class="sem-groups-partition sem-groups-autres-liens">
|
||||
<div class="sem-groups-none">
|
||||
<ul>
|
||||
<li>{'</li><li>'.join(autres_liens)}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
||||
H.append("</div>")
|
||||
@ -975,11 +1022,8 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
|
||||
page_title = page_title or "Modules de "
|
||||
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
page_title, with_page_header=False, with_h2=False
|
||||
),
|
||||
f"""<table>
|
||||
<tr><td class="fichetitre2">Formation: </td><td>
|
||||
f"""<table class="formsemestre_status_head">
|
||||
<tr><td class="fichetitre2">Formation : </td><td>
|
||||
<a href="{url_for('notes.ue_table',
|
||||
scodoc_dept=g.scodoc_dept, formation_id=formsemestre.formation.id)}"
|
||||
class="discretelink" title="Formation {
|
||||
@ -1002,21 +1046,31 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
|
||||
sem_parcours = formsemestre.get_parcours_apc()
|
||||
H.append(
|
||||
f"""
|
||||
<tr><td class="fichetitre2">Parcours: </td>
|
||||
<tr><td class="fichetitre2">Parcours : </td>
|
||||
<td style="color: blue;">{', '.join(parcours.code for parcours in sem_parcours)}</td>
|
||||
</tr>
|
||||
"""
|
||||
)
|
||||
if formsemestre.capacite_accueil is not None:
|
||||
H.append(
|
||||
f"""
|
||||
<tr><td class="fichetitre2">Capacité d'accueil : </td>
|
||||
<td>{formsemestre.capacite_accueil}</td>
|
||||
</tr>
|
||||
"""
|
||||
)
|
||||
|
||||
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre)
|
||||
H.append(
|
||||
'<tr><td class="fichetitre2">Évaluations: </td><td> %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides'
|
||||
"""<tr><td class="fichetitre2">Évaluations : </td>
|
||||
<td> %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides"""
|
||||
% evals
|
||||
)
|
||||
if evals["last_modif"]:
|
||||
H.append(
|
||||
" <em>(dernière note saisie le %s)</em>"
|
||||
% evals["last_modif"].strftime("%d/%m/%Y à %Hh%M")
|
||||
f""" <em>(dernière note saisie le {
|
||||
evals["last_modif"].strftime(scu.DATEATIME_FMT)
|
||||
})</em>"""
|
||||
)
|
||||
H.append("</td></tr>")
|
||||
H.append("</table>")
|
||||
@ -1055,12 +1109,6 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
||||
modimpls = formsemestre.modimpls_sorted
|
||||
nt = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
# Construit la liste de tous les enseignants de ce semestre:
|
||||
mails_enseignants = set(u.email for u in formsemestre.responsables)
|
||||
for modimpl in modimpls:
|
||||
mails_enseignants.add(sco_users.user_info(modimpl.responsable_id)["email"])
|
||||
mails_enseignants |= {u.email for u in modimpl.enseignants if u.email}
|
||||
|
||||
can_edit = formsemestre.can_be_edited_by(current_user)
|
||||
can_change_all_notes = current_user.has_permission(Permission.EditAllNotes) or (
|
||||
current_user.id in [resp.id for resp in formsemestre.responsables]
|
||||
@ -1115,6 +1163,18 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
||||
]
|
||||
H += [
|
||||
f"""
|
||||
<details id="tableau-modules-details" open>
|
||||
<!-- script pour fermer automatiquement si mobile -->
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {{
|
||||
if (window.innerWidth < 769) {{
|
||||
document.getElementById("tableau-modules-details").open = false;
|
||||
}}
|
||||
}});
|
||||
</script>
|
||||
<summary id="tableau-modules-summary">
|
||||
<h3 title="cliquer pour afficher ou cacher le tableau">Tableau des Ressources et SAEs</h3>
|
||||
</summary>
|
||||
<div class="tableau_modules">
|
||||
{_TABLEAU_MODULES_HEAD}
|
||||
<tr class="formsemestre_status_cat">
|
||||
@ -1144,7 +1204,7 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
||||
autres, nt, formsemestre, can_edit=can_edit, show_ues=False
|
||||
),
|
||||
]
|
||||
H += [_TABLEAU_MODULES_FOOT, "</div>"]
|
||||
H += [_TABLEAU_MODULES_FOOT, "</div></details>"]
|
||||
else:
|
||||
# formations classiques: groupe par UE
|
||||
# élimine les modules BUT qui aurait pu se glisser là suite à un
|
||||
@ -1176,20 +1236,11 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
|
||||
)
|
||||
# --- LISTE DES ETUDIANTS
|
||||
H += [
|
||||
'<div class="formsemestre-groupes">',
|
||||
'<div class="formsemestre-groupes space-before-24">',
|
||||
_make_listes_sem(formsemestre),
|
||||
"</div>",
|
||||
]
|
||||
|
||||
# --- Lien mail enseignants:
|
||||
adrlist = list(mails_enseignants - {None, ""})
|
||||
if adrlist:
|
||||
H.append(
|
||||
f"""<p>
|
||||
<a class="stdlink" href="mailto:?cc={','.join(adrlist)}">Courrier aux {
|
||||
len(adrlist)} enseignants du semestre</a>
|
||||
</p>"""
|
||||
)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
content="".join(H),
|
||||
|
@ -30,8 +30,7 @@
|
||||
import time
|
||||
|
||||
import flask
|
||||
from flask import url_for, flash, g, request
|
||||
from flask.templating import render_template
|
||||
from flask import flash, g, render_template, request, url_for
|
||||
import sqlalchemy as sa
|
||||
|
||||
from app.models import Identite, Evaluation
|
||||
@ -584,6 +583,11 @@ def formsemestre_recap_parcours_table(
|
||||
)
|
||||
is_cur = situation_etud_cursus.formsemestre_id == formsemestre.id
|
||||
num_sem += 1
|
||||
url_status = url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
)
|
||||
|
||||
dpv = sco_pv_dict.dict_pvjury(formsemestre.id, etudids=[etudid])
|
||||
pv = dpv["decisions"][0]
|
||||
@ -643,7 +647,7 @@ def formsemestre_recap_parcours_table(
|
||||
H.append(
|
||||
f"""
|
||||
<td class="rcp_type_sem" style="background-color:{bgcolor};">{num_sem}{pm}</td>
|
||||
<td class="datedebut">{formsemestre.mois_debut()}</td>
|
||||
<td class="datedebut"><a href="{url_status}">{formsemestre.mois_debut()}</a></td>
|
||||
<td class="rcp_titre_sem"><a class="formsemestre_status_link"
|
||||
href="{
|
||||
url_for("notes.formsemestre_bulletinetud", scodoc_dept=g.scodoc_dept,
|
||||
@ -725,8 +729,9 @@ def formsemestre_recap_parcours_table(
|
||||
f"""Autre formation: {formsemestre.formation.formation_code}"""
|
||||
)
|
||||
H.append(
|
||||
'<td class="datefin">%s</td><td class="sem_info">%s</td>'
|
||||
% (formsemestre.mois_fin(), sem_info.get(formsemestre.id, default_sem_info))
|
||||
f"""<td class="datefin"><a href="{url_status}">{formsemestre.mois_fin()}</a></td>
|
||||
<td class="sem_info">{sem_info.get(formsemestre.id, default_sem_info)}</td>
|
||||
"""
|
||||
)
|
||||
# Moy Gen (sous le code decision)
|
||||
H.append(
|
||||
@ -957,11 +962,13 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None
|
||||
|
||||
|
||||
# -----------
|
||||
def formsemestre_validation_auto(formsemestre_id):
|
||||
"Formulaire saisie automatisee des decisions d'un semestre"
|
||||
H = [
|
||||
html_sco_header.html_sem_header("Saisie automatique des décisions du semestre"),
|
||||
f"""
|
||||
def formsemestre_validation_auto(formsemestre_id: int):
|
||||
"Formulaire saisie automatisée des décisions d'un semestre"
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title="Saisie automatique des décisions",
|
||||
content=f"""
|
||||
<h2>Saisie automatique des décisions du semestre</h2>
|
||||
<ul>
|
||||
<li>Seuls les étudiants qui obtiennent le semestre seront affectés (code ADM, moyenne générale et
|
||||
toutes les barres, semestre précédent validé);</li>
|
||||
@ -978,9 +985,7 @@ def formsemestre_validation_auto(formsemestre_id):
|
||||
<p><em>Le calcul prend quelques minutes, soyez patients !</em></p>
|
||||
</form>
|
||||
""",
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
return "\n".join(H)
|
||||
)
|
||||
|
||||
|
||||
def do_formsemestre_validation_auto(formsemestre_id):
|
||||
|
@ -65,7 +65,7 @@ def groups_export_annotations(group_ids, formsemestre_id=None, fmt="html"):
|
||||
)
|
||||
annotations = groups_list_annotation(groups_infos.group_ids)
|
||||
for annotation in annotations:
|
||||
annotation["date_str"] = annotation["date"].strftime("%d/%m/%Y à %Hh%M")
|
||||
annotation["date_str"] = annotation["date"].strftime(scu.DATEATIME_FMT)
|
||||
if fmt == "xls":
|
||||
columns_ids = ("etudid", "nom", "prenom", "date", "comment")
|
||||
else:
|
||||
|
@ -30,16 +30,17 @@ sous forme: de liste html (table exportable), de trombinoscope (exportable en pd
|
||||
"""
|
||||
|
||||
# Re-ecriture en 2014 (re-organisation de l'interface, modernisation du code)
|
||||
# Modif en 2024 (9.7/revamp, abandon des tabs bootstrap)
|
||||
|
||||
import datetime
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
|
||||
from flask import url_for, g, request
|
||||
from flask import url_for, g, render_template, request
|
||||
from flask_login import current_user
|
||||
|
||||
from app import db
|
||||
from app.models import FormSemestre, Identite
|
||||
from app import db, log
|
||||
from app.models import FormSemestre, Identite, ScolarEvent
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_assiduites as scass
|
||||
@ -56,19 +57,13 @@ from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.sco_exceptions import ScoValueError, ScoPermissionDenied
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + [
|
||||
"js/etud_info.js",
|
||||
"js/groups_view.js",
|
||||
]
|
||||
|
||||
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
||||
|
||||
|
||||
# view:
|
||||
def groups_view(
|
||||
# view
|
||||
def groups_lists(
|
||||
group_ids=(),
|
||||
fmt="html",
|
||||
with_codes=0,
|
||||
with_date_inscription=0,
|
||||
etat=None,
|
||||
with_paiement=0,
|
||||
with_archives=0,
|
||||
@ -87,6 +82,7 @@ def groups_view(
|
||||
|
||||
formsemestre_id est utilisé si aucun groupe selectionné pour construire la liste des groupes.
|
||||
"""
|
||||
# version sans tabs: juste la liste des étudiants
|
||||
# Informations sur les groupes à afficher:
|
||||
groups_infos = DisplayedGroupsInfos(
|
||||
group_ids,
|
||||
@ -100,6 +96,7 @@ def groups_view(
|
||||
groups_infos=groups_infos,
|
||||
fmt=fmt,
|
||||
with_codes=with_codes,
|
||||
with_date_inscription=with_date_inscription,
|
||||
etat=etat,
|
||||
with_paiement=with_paiement,
|
||||
with_archives=with_archives,
|
||||
@ -112,57 +109,59 @@ def groups_view(
|
||||
# - charger tous les etudiants au debut, quels que soient les groupes selectionnés
|
||||
# - ajouter du JS pour modifier les liens (arguments group_ids) quand le menu change
|
||||
|
||||
return f"""
|
||||
{ html_sco_header.sco_header(
|
||||
javascripts=JAVASCRIPTS,
|
||||
cssstyles=CSSSTYLES,
|
||||
init_qtip=True,
|
||||
)
|
||||
}
|
||||
<style>
|
||||
div.multiselect-container.dropdown-menu {{
|
||||
min-width: 180px;
|
||||
}}
|
||||
span.warning_unauthorized {{
|
||||
color: pink;
|
||||
font-style: italic;
|
||||
margin-left: 12px;
|
||||
}}
|
||||
</style>
|
||||
<div id="group-tabs">
|
||||
<!-- Menu choix groupe -->
|
||||
{form_groups_choice(groups_infos, submit_on_change=True)}
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="#tab-listes" data-toggle="tab">Listes</a></li>
|
||||
<li><a href="#tab-photos" data-toggle="tab">Photos</a></li>
|
||||
<li><a href="#tab-abs" data-toggle="tab">Absences et feuilles...</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="tab-listes">
|
||||
{
|
||||
groups_table(
|
||||
groups_infos=groups_infos,
|
||||
fmt=fmt,
|
||||
with_codes=with_codes,
|
||||
etat=etat,
|
||||
with_paiement=with_paiement,
|
||||
with_archives=with_archives,
|
||||
with_annotations=with_annotations,
|
||||
with_bourse=with_bourse,
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div class="tab-pane" id="tab-photos">
|
||||
{ tab_photos_html(groups_infos, etat=etat) }
|
||||
</div>
|
||||
<div class="tab-pane" id="tab-abs">
|
||||
{ tab_absences_html(groups_infos, etat=etat) }
|
||||
</div>
|
||||
</div>
|
||||
{ html_sco_header.sco_footer() }
|
||||
return render_template(
|
||||
"formsemestre/groups_lists.j2",
|
||||
form_groups_choice=form_groups_choice(groups_infos, submit_on_change=True),
|
||||
groups_table=groups_table(
|
||||
groups_infos=groups_infos,
|
||||
fmt=fmt,
|
||||
with_codes=with_codes,
|
||||
with_date_inscription=with_date_inscription,
|
||||
etat=etat,
|
||||
with_paiement=with_paiement,
|
||||
with_archives=with_archives,
|
||||
with_annotations=with_annotations,
|
||||
with_bourse=with_bourse,
|
||||
),
|
||||
groups_titles=groups_infos.groups_titles,
|
||||
)
|
||||
|
||||
|
||||
# view
|
||||
def groups_photos(group_ids=(), etat=None, formsemestre_id=None):
|
||||
"""Affichage des photos des étudiants (trombi) des groupes indiqués
|
||||
group_ids: liste de group_id
|
||||
formsemestre_id est utilisé si aucun groupe selectionné pour construire la liste des groupes.
|
||||
"""
|
||||
groups_infos = DisplayedGroupsInfos(
|
||||
group_ids,
|
||||
formsemestre_id=formsemestre_id,
|
||||
select_all_when_unspecified=True,
|
||||
)
|
||||
return render_template(
|
||||
"formsemestre/groups_photos.j2",
|
||||
form_groups_choice=form_groups_choice(groups_infos, submit_on_change=True),
|
||||
tab_photos_html=tab_photos_html(groups_infos, etat=etat),
|
||||
groups_titles=groups_infos.groups_titles,
|
||||
)
|
||||
|
||||
|
||||
def groups_feuilles(group_ids=(), etat=None, formsemestre_id=None):
|
||||
"""Affichage des feuilles d'appel des groupes indiqués
|
||||
group_ids: liste de group_id
|
||||
formsemestre_id est utilisé si aucun groupe selectionné pour construire la liste des groupes.
|
||||
"""
|
||||
groups_infos = DisplayedGroupsInfos(
|
||||
group_ids,
|
||||
formsemestre_id=formsemestre_id,
|
||||
select_all_when_unspecified=True,
|
||||
)
|
||||
return render_template(
|
||||
"formsemestre/groups_feuilles.j2",
|
||||
form_groups_choice=form_groups_choice(groups_infos, submit_on_change=True),
|
||||
tab_absences_html=tab_absences_html(groups_infos, etat=etat),
|
||||
groups_titles=groups_infos.groups_titles,
|
||||
)
|
||||
|
||||
|
||||
def form_groups_choice(
|
||||
@ -215,47 +214,61 @@ def form_groups_choice(
|
||||
|
||||
|
||||
def menu_groups_choice(
|
||||
groups_infos, submit_on_change=False, default_deselect_others=True
|
||||
groups_infos,
|
||||
submit_on_change=False,
|
||||
default_deselect_others=True,
|
||||
):
|
||||
"""menu pour selection groupes
|
||||
"""Menu pour selection groupes
|
||||
group_ids est la liste des groupes actuellement sélectionnés
|
||||
et doit comporter au moins un élément, sauf si formsemestre_id est spécifié.
|
||||
(utilisé pour retrouver le semestre et proposer la liste des autres groupes)
|
||||
|
||||
Si url_export :
|
||||
selecteur.value = &group_ids=xxx&group_ids=yyy...
|
||||
sinon :
|
||||
selecteur.value = [xxx, yyy, ...]
|
||||
|
||||
"""
|
||||
default_group_id = sco_groups.get_default_group(groups_infos.formsemestre_id)
|
||||
n_members = len(sco_groups.get_group_members(default_group_id))
|
||||
|
||||
H = [
|
||||
f"""<select name="group_ids" id="group_ids_sel"
|
||||
class="multiselect
|
||||
{'submit_on_change' if submit_on_change else ''}
|
||||
{'default_deselect_others' if default_deselect_others else ''}
|
||||
"
|
||||
multiple="multiple">
|
||||
<option class="default_group"
|
||||
value="{default_group_id}"
|
||||
{'selected' if default_group_id in groups_infos.group_ids else ''}
|
||||
>Tous ({n_members})</option>
|
||||
"""
|
||||
]
|
||||
values: dict = {
|
||||
# Choix : Tous (tous les groupes)
|
||||
"": [
|
||||
{
|
||||
"value": default_group_id,
|
||||
"label": f"Tous ({n_members})",
|
||||
"selected": default_group_id in groups_infos.group_ids,
|
||||
"single": default_deselect_others,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
for partition in groups_infos.partitions:
|
||||
H.append('<optgroup label="%s">' % partition["partition_name"])
|
||||
p_name: str = partition["partition_name"]
|
||||
vals: list[tuple[str, str, bool]] = []
|
||||
# Les groupes dans cette partition:
|
||||
for g in sco_groups.get_partition_groups(partition):
|
||||
if g["group_id"] in groups_infos.group_ids:
|
||||
selected = "selected"
|
||||
else:
|
||||
selected = ""
|
||||
if g["group_name"]:
|
||||
n_members = len(sco_groups.get_group_members(g["group_id"]))
|
||||
H.append(
|
||||
'<option value="%s" %s>%s (%s)</option>'
|
||||
% (g["group_id"], selected, g["group_name"], n_members)
|
||||
for grp in sco_groups.get_partition_groups(partition):
|
||||
selected: bool = grp["group_id"] in groups_infos.group_ids
|
||||
if grp["group_name"]:
|
||||
vals.append(
|
||||
{
|
||||
"value": grp["group_id"],
|
||||
"label": f"{grp['group_name']} ({len(sco_groups.get_group_members(grp['group_id']))})",
|
||||
"selected": selected,
|
||||
}
|
||||
)
|
||||
H.append("</optgroup>")
|
||||
H.append("</select> ")
|
||||
return "\n".join(H)
|
||||
|
||||
values[p_name] = vals
|
||||
|
||||
multi_select: scu.MultiSelect = scu.MultiSelect(
|
||||
values=values, name="group_ids", html_id="group_ids_sel"
|
||||
)
|
||||
|
||||
if submit_on_change:
|
||||
multi_select.change_event("submit_group_selector();")
|
||||
|
||||
return multi_select.html()
|
||||
|
||||
|
||||
def menu_group_choice(group_id=None, formsemestre_id=None):
|
||||
@ -340,6 +353,7 @@ class DisplayedGroupsInfos:
|
||||
try:
|
||||
group_ids = [int(g) for g in group_ids]
|
||||
except ValueError as exc:
|
||||
log(f"DisplayedGroupsInfos: invalid group_id '{group_ids}'")
|
||||
raise ScoValueError(
|
||||
"identifiant de groupe invalide (mettre à jour vos bookmarks ?)"
|
||||
) from exc
|
||||
@ -481,6 +495,7 @@ class DisplayedGroupsInfos:
|
||||
def groups_table(
|
||||
groups_infos: DisplayedGroupsInfos = None,
|
||||
with_codes=0,
|
||||
with_date_inscription=0,
|
||||
etat=None,
|
||||
fmt="html",
|
||||
with_paiement=0, # si vrai, ajoute colonnes infos paiement droits et finalisation inscription (lent car interrogation portail)
|
||||
@ -496,15 +511,16 @@ def groups_table(
|
||||
|
||||
can_view_etud_data = int(current_user.has_permission(Permission.ViewEtudData))
|
||||
with_codes = int(with_codes)
|
||||
with_date_inscription = int(with_date_inscription)
|
||||
with_paiement = int(with_paiement) and can_view_etud_data
|
||||
with_archives = int(with_archives) and can_view_etud_data
|
||||
with_annotations = int(with_annotations) and can_view_etud_data
|
||||
with_bourse = int(with_bourse) and can_view_etud_data
|
||||
|
||||
base_url_np = groups_infos.base_url + f"&with_codes={with_codes}"
|
||||
base_url = (
|
||||
base_url_np
|
||||
+ f"""&with_paiement={with_paiement}&with_archives={
|
||||
groups_infos.base_url
|
||||
+ f"""&with_codes={with_codes}&with_date_inscription={
|
||||
with_date_inscription}&with_paiement={with_paiement}&with_archives={
|
||||
with_archives}&with_annotations={with_annotations
|
||||
}&with_bourse={with_bourse}"""
|
||||
)
|
||||
@ -520,6 +536,7 @@ def groups_table(
|
||||
"etudid": "etudid",
|
||||
"code_nip": "code_nip",
|
||||
"code_ine": "code_ine",
|
||||
"date_inscription": "Date inscription",
|
||||
"datefinalisationinscription_str": "Finalisation inscr.",
|
||||
"paiementinscription_str": "Paiement",
|
||||
"etudarchive": "Fichiers",
|
||||
@ -553,9 +570,11 @@ def groups_table(
|
||||
|
||||
if with_codes:
|
||||
columns_ids += ["etape", "etudid", "code_nip", "code_ine"]
|
||||
if with_date_inscription:
|
||||
columns_ids += ["date_inscription"]
|
||||
if with_paiement:
|
||||
columns_ids += ["datefinalisationinscription_str", "paiementinscription_str"]
|
||||
if with_paiement: # or with_codes:
|
||||
if with_paiement:
|
||||
sco_portal_apogee.check_paiement_etuds(groups_infos.members)
|
||||
if with_archives:
|
||||
from app.scodoc import sco_archives_etud
|
||||
@ -571,6 +590,16 @@ def groups_table(
|
||||
moodle_groupenames = set()
|
||||
# ajoute liens
|
||||
for etud_info in groups_infos.members:
|
||||
if with_date_inscription:
|
||||
event = ScolarEvent.query.filter_by(
|
||||
etudid=etud_info["etudid"],
|
||||
event_type="INSCRIPTION",
|
||||
formsemestre_id=groups_infos.formsemestre_id,
|
||||
).first()
|
||||
if event:
|
||||
etud_info["date_inscription"] = event.event_date.strftime(scu.DATE_FMT)
|
||||
etud_info["_date_inscription_xls"] = event.event_date
|
||||
etud_info["_date_inscription_order"] = event.event_date.isoformat
|
||||
if etud_info["email"]:
|
||||
etud_info["_email_target"] = "mailto:" + etud_info["email"]
|
||||
else:
|
||||
@ -587,7 +616,7 @@ def groups_table(
|
||||
etud_info["_prenom_target"] = fiche_url
|
||||
|
||||
etud_info["_nom_disp_td_attrs"] = (
|
||||
'id="%s" class="etudinfo"' % (etud_info["etudid"])
|
||||
f"""id="{etud_info['etudid']}" class="etudinfo" """
|
||||
)
|
||||
etud_info["bourse_str"] = "oui" if etud_info["boursier"] else "non"
|
||||
if etud_info["etat"] == "D":
|
||||
@ -692,9 +721,9 @@ def groups_table(
|
||||
"""
|
||||
]
|
||||
if groups_infos.members:
|
||||
menu_options = []
|
||||
options = {
|
||||
"with_codes": "Affiche codes",
|
||||
"with_date_inscription": "Date inscription",
|
||||
}
|
||||
if can_view_etud_data:
|
||||
options.update(
|
||||
@ -705,34 +734,33 @@ def groups_table(
|
||||
"with_bourse": "Statut boursier",
|
||||
}
|
||||
)
|
||||
valeurs: list[tuple[str, str]] = []
|
||||
for option, label in options.items():
|
||||
if locals().get(option, False):
|
||||
selected = "selected"
|
||||
else:
|
||||
selected = ""
|
||||
menu_options.append(
|
||||
f"""<option value="{option}" {selected}>{label}</option>"""
|
||||
selected = locals().get(option, False)
|
||||
valeurs.append(
|
||||
{
|
||||
"value": option,
|
||||
"label": label,
|
||||
"selected": selected,
|
||||
}
|
||||
)
|
||||
|
||||
multi_select: scu.MultiSelect = scu.MultiSelect(
|
||||
values={"": valeurs},
|
||||
label="Options",
|
||||
name="options",
|
||||
html_id="group_list_options",
|
||||
)
|
||||
multi_select.change_event("change_list_options(values);")
|
||||
H.extend(
|
||||
# ;
|
||||
[
|
||||
"""<span style="margin-left: 2em;">
|
||||
<select name="group_list_options" id="group_list_options" class="multiselect" multiple="multiple">""",
|
||||
"\n".join(menu_options),
|
||||
"""</select></span>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('#group_list_options').multiselect(
|
||||
{
|
||||
includeSelectAllOption: false,
|
||||
nonSelectedText:'Options...',
|
||||
onChange: function(element, checked){
|
||||
change_list_options();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
</script>
|
||||
f"""
|
||||
<span style="margin-left: 2em;">
|
||||
|
||||
{multi_select.html()}
|
||||
|
||||
</span>
|
||||
""",
|
||||
(
|
||||
"""<span class="warning_unauthorized">accès aux données personnelles interdit</span>"""
|
||||
@ -747,16 +775,21 @@ def groups_table(
|
||||
[
|
||||
tab.html(),
|
||||
f"""
|
||||
<ul>
|
||||
<li><a class="stdlink" href="{tab.base_url}&fmt=xls">Table Excel</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="{tab.base_url}&fmt=moodlecsv">Fichier CSV pour Moodle (groupe sélectionné)</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="stdlink" href="export_groups_as_moodle_csv?formsemestre_id={groups_infos.formsemestre_id}">
|
||||
Fichier CSV pour Moodle (tous les groupes)</a>
|
||||
<em>(voir le paramétrage pour modifier le format des fichiers Moodle exportés)</em>
|
||||
</li>""",
|
||||
<ul>
|
||||
<li><a class="stdlink" href="{tab.base_url}&fmt=xls">Table Excel
|
||||
groupe(s) {groups_infos.groups_titles}</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="{tab.base_url}&fmt=moodlecsv">Fichier CSV pour
|
||||
Moodle groupe(s) {groups_infos.groups_titles}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="stdlink" href="{{
|
||||
url_for('notes.export_groups_as_moodle_csv',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=groups_infos.formsemestre_id)
|
||||
}}">
|
||||
Fichier CSV pour Moodle (tous les groupes)</a>
|
||||
<em>(voir le paramétrage pour modifier le format des fichiers Moodle exportés)</em>
|
||||
</li>""",
|
||||
]
|
||||
)
|
||||
if amail_inst:
|
||||
@ -800,6 +833,7 @@ def groups_table(
|
||||
groups_infos.members,
|
||||
partitions=groups_infos.partitions,
|
||||
with_codes=with_codes,
|
||||
with_date_inscription=with_date_inscription,
|
||||
with_paiement=with_paiement,
|
||||
server_name=request.url_root,
|
||||
)
|
||||
@ -923,11 +957,14 @@ def tab_absences_html(groups_infos, etat=None):
|
||||
"""
|
||||
]
|
||||
|
||||
url_feuille_appel: str = url_for(
|
||||
"scolar.formulaire_feuille_appel",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=groups_infos.formsemestre_id,
|
||||
group_ids=group_ids,
|
||||
url_feuille_appel: str = (
|
||||
url_for(
|
||||
"scolar.formulaire_feuille_appel",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=groups_infos.formsemestre_id,
|
||||
)
|
||||
+ "?"
|
||||
+ groups_infos.groups_query_args
|
||||
)
|
||||
|
||||
H.extend(
|
||||
|
@ -743,17 +743,21 @@ def scolars_import_admission(
|
||||
# Type admission: traitement particulier
|
||||
if not cur_adm["type_admission"] and not args.get("type_admission"):
|
||||
args["type_admission"] = type_admission
|
||||
sco_etud.etudident_edit(cnx, args, disable_notify=True)
|
||||
sco_etud.etudident_edit( # TODO utiliser modèle
|
||||
cnx, args, disable_notify=True
|
||||
)
|
||||
adr = sco_etud.adresse_list(cnx, args={"etudid": etud["etudid"]})
|
||||
if adr:
|
||||
args["adresse_id"] = adr[0]["adresse_id"]
|
||||
sco_etud.adresse_edit(
|
||||
sco_etud.adresse_edit( # TODO utiliser modèle
|
||||
cnx, args, disable_notify=True
|
||||
) # pas de notification ici
|
||||
else:
|
||||
args["typeadresse"] = "domicile"
|
||||
args["description"] = "(infos admission)"
|
||||
adresse_id = sco_etud.adresse_create(cnx, args)
|
||||
adresse_id = sco_etud.adresse_create( # TODO utiliser modèle
|
||||
cnx, args
|
||||
)
|
||||
# log('import_adm: %s' % args )
|
||||
# Change les groupes si nécessaire:
|
||||
if "groupes" in args:
|
||||
|
@ -31,16 +31,13 @@
|
||||
import datetime
|
||||
from operator import itemgetter
|
||||
|
||||
from flask import url_for, g, request
|
||||
from flask import url_for, g, render_template, request
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import db, log
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import Formation, FormSemestre, GroupDescr, Identite
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_etud
|
||||
@ -312,14 +309,7 @@ def formsemestre_inscr_passage(
|
||||
# -- check lock
|
||||
if not formsemestre.etat:
|
||||
raise ScoValueError("opération impossible: semestre verrouille")
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Passage des étudiants",
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
)
|
||||
]
|
||||
footer = html_sco_header.sco_footer()
|
||||
H = []
|
||||
etuds = [] if etuds is None else etuds
|
||||
if isinstance(etuds, str):
|
||||
# string, vient du form de confirmation
|
||||
@ -377,7 +367,7 @@ def formsemestre_inscr_passage(
|
||||
if a_desinscrire:
|
||||
H.append("<h3>Étudiants à désinscrire</h3><ol>")
|
||||
a_desinscrire_ident = sorted(
|
||||
(Identite.query.get(eid) for eid in a_desinscrire),
|
||||
(db.session.get(Identite, eid) for eid in a_desinscrire),
|
||||
key=lambda x: x.sort_key,
|
||||
)
|
||||
for etud in a_desinscrire_ident:
|
||||
@ -458,8 +448,9 @@ def formsemestre_inscr_passage(
|
||||
)
|
||||
|
||||
#
|
||||
H.append(footer)
|
||||
return "\n".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2", title="Passage des étudiants", content="\n".join(H)
|
||||
)
|
||||
|
||||
|
||||
def _build_page(
|
||||
@ -488,10 +479,9 @@ def _build_page(
|
||||
else:
|
||||
ignore_jury_checked = ""
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Passages dans le semestre", with_page_header=False
|
||||
),
|
||||
f"""<form name="f" method="post" action="{request.base_url}">
|
||||
f"""
|
||||
<h2 class="formsemestre">Passages dans le semestre</h2>
|
||||
<form name="f" method="post" action="{request.base_url}">
|
||||
|
||||
<input type="hidden" name="formsemestre_id" value="{formsemestre.id}"/>
|
||||
|
||||
@ -590,7 +580,7 @@ def formsemestre_inscr_passage_help(formsemestre: FormSemestre):
|
||||
|
||||
def etuds_select_boxes(
|
||||
auth_etuds_by_cat,
|
||||
inscrits_ailleurs={},
|
||||
inscrits_ailleurs: dict = None,
|
||||
sel_inscrits=True,
|
||||
show_empty_boxes=False,
|
||||
export_cat_xls=None,
|
||||
@ -603,6 +593,7 @@ def etuds_select_boxes(
|
||||
sel_inscrits=
|
||||
export_cat_xls =
|
||||
"""
|
||||
inscrits_ailleurs = inscrits_ailleurs or {}
|
||||
if export_cat_xls:
|
||||
return etuds_select_box_xls(auth_etuds_by_cat[export_cat_xls])
|
||||
|
||||
@ -634,7 +625,7 @@ def etuds_select_boxes(
|
||||
for src_cat in auth_etuds_by_cat.keys():
|
||||
infos = auth_etuds_by_cat[src_cat]["infos"]
|
||||
infos["comment"] = infos.get("comment", "") # commentaire dans sous-titre boite
|
||||
help = infos.get("help", "")
|
||||
help_txt = infos.get("help", "")
|
||||
etuds = auth_etuds_by_cat[src_cat]["etuds"]
|
||||
etuds.sort(key=itemgetter("nom"))
|
||||
with_checkbox = (not read_only) and auth_etuds_by_cat[src_cat]["infos"].get(
|
||||
@ -651,8 +642,8 @@ def etuds_select_boxes(
|
||||
<div class="pas_sembox_title"><a href="%(title_target)s" """
|
||||
% infos
|
||||
)
|
||||
if help: # bubble
|
||||
H.append('title="%s"' % help)
|
||||
if help_txt: # bubble
|
||||
H.append('title="%s"' % help_txt)
|
||||
H.append(
|
||||
""">%(title)s</a></div>
|
||||
<div class="pas_sembox_subtitle">(%(nbetuds)d étudiants%(comment)s)"""
|
||||
|
@ -98,8 +98,7 @@ def scodoc_table_etuds_lycees(fmt="html"):
|
||||
html_sco_header.sco_header(
|
||||
page_title=tab.page_title,
|
||||
init_google_maps=True,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js", "js/map_lycees.js"],
|
||||
javascripts=["js/map_lycees.js"],
|
||||
),
|
||||
"""<h2 class="formsemestre">Lycées d'origine des %d étudiants (%d semestres)</h2>"""
|
||||
% (len(etuds), len(semdepts)),
|
||||
@ -219,10 +218,8 @@ def formsemestre_etuds_lycees(
|
||||
html_sco_header.sco_header(
|
||||
page_title=tab.page_title,
|
||||
init_google_maps=True,
|
||||
init_qtip=True,
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
javascripts=sco_groups_view.JAVASCRIPTS
|
||||
+ ["js/etud_info.js", "js/map_lycees.js"],
|
||||
javascripts=sco_groups_view.JAVASCRIPTS + ["js/map_lycees.js"],
|
||||
),
|
||||
"""<h2 class="formsemestre">Lycées d'origine des étudiants</h2>""",
|
||||
"\n".join(F),
|
||||
|
@ -31,7 +31,7 @@ import collections
|
||||
from operator import attrgetter
|
||||
|
||||
import flask
|
||||
from flask import url_for, g, request
|
||||
from flask import url_for, g, render_template, request
|
||||
from flask_login import current_user
|
||||
|
||||
from app import db, log
|
||||
@ -46,7 +46,6 @@ from app.models import (
|
||||
UniteEns,
|
||||
Scolog,
|
||||
)
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import htmlutils
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import codes_cursus
|
||||
@ -82,14 +81,8 @@ def moduleimpl_inscriptions_edit(
|
||||
# -- check permission (and lock)
|
||||
if not modimpl.can_change_inscriptions():
|
||||
return # can_change_inscriptions raises exception
|
||||
header = html_sco_header.sco_header(
|
||||
page_title="Inscription au module",
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
)
|
||||
footer = html_sco_header.sco_footer()
|
||||
|
||||
H = [
|
||||
header,
|
||||
f"""<h2>Inscriptions au module <a class="stdlink" href="{
|
||||
url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=moduleimpl_id)
|
||||
@ -133,14 +126,15 @@ def moduleimpl_inscriptions_edit(
|
||||
|
||||
if (partitionIdx==-1) {
|
||||
for (var i =nb_inputs_to_skip; i < elems.length; i++) {
|
||||
elems[i].checked=check;
|
||||
elems[i].checked=check;
|
||||
}
|
||||
} else {
|
||||
for (var i =nb_inputs_to_skip; i < elems.length; i++) {
|
||||
var cells = elems[i].parentNode.parentNode.getElementsByTagName("td")[partitionIdx].childNodes;
|
||||
if (cells.length && cells[0].nodeValue == groupName) {
|
||||
elems[i].checked=check;
|
||||
}
|
||||
let tds = elems[i].parentNode.parentNode.getElementsByTagName("td");
|
||||
var cells = tds[partitionIdx].childNodes;
|
||||
if (cells.length && cells[0].nodeValue == groupName) {
|
||||
elems[i].checked=check;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -179,19 +173,19 @@ def moduleimpl_inscriptions_edit(
|
||||
else:
|
||||
checked = ""
|
||||
H.append(
|
||||
f"""<tr><td class="etud"><input type="checkbox" name="etudids:list" value="{etud['etudid']}" {checked}>"""
|
||||
f"""<tr><td class="etud">
|
||||
<input type="checkbox" name="etudids:list" value="{etud['etudid']}" {checked}>
|
||||
<a class="discretelink etudinfo" href="{
|
||||
url_for(
|
||||
"scolar.fiche_etud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etud["etudid"],
|
||||
)
|
||||
}" id="{etud['etudid']}">{etud['nomprenom']}</a>
|
||||
</input>
|
||||
</td>
|
||||
"""
|
||||
)
|
||||
H.append(
|
||||
f"""<a class="discretelink etudinfo" href="{
|
||||
url_for(
|
||||
"scolar.fiche_etud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etud["etudid"],
|
||||
)
|
||||
}" id="{etud['etudid']}">{etud['nomprenom']}</a>"""
|
||||
)
|
||||
H.append("""</input></td>""")
|
||||
|
||||
groups = sco_groups.get_etud_groups(etud["etudid"], formsemestre.id)
|
||||
for partition in partitions:
|
||||
if partition["partition_name"]:
|
||||
@ -216,8 +210,9 @@ def moduleimpl_inscriptions_edit(
|
||||
)
|
||||
)
|
||||
#
|
||||
H.append(footer)
|
||||
return "\n".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2", title="Inscriptions au module", content="\n".join(H)
|
||||
)
|
||||
|
||||
|
||||
def _make_menu(partitions: list[dict], title="", check="true") -> str:
|
||||
@ -301,15 +296,12 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
||||
|
||||
# Page HTML:
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Inscriptions aux modules et UE du semestre",
|
||||
javascripts=["js/etud_info.js", "js/moduleimpl_inscriptions_stats.js"],
|
||||
init_qtip=True,
|
||||
)
|
||||
f"""
|
||||
<h2 class="formsemestre">Inscriptions aux modules et UE du semestre</h2>
|
||||
<h3>Inscrits au semestre: {len(inscrits)} étudiants</h3>
|
||||
"""
|
||||
]
|
||||
|
||||
H.append(f"<h3>Inscrits au semestre: {len(inscrits)} étudiants</h3>")
|
||||
|
||||
if options:
|
||||
H.append("<h3>Modules auxquels tous les étudiants ne sont pas inscrits:</h3>")
|
||||
H.append(
|
||||
@ -496,8 +488,12 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
||||
"""
|
||||
)
|
||||
|
||||
H.append(html_sco_header.sco_footer())
|
||||
return "\n".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title="Inscriptions aux modules et UE du semestre",
|
||||
javascripts=["js/moduleimpl_inscriptions_stats.js"],
|
||||
content="\n".join(H),
|
||||
)
|
||||
|
||||
|
||||
def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) -> str:
|
||||
|
@ -483,6 +483,8 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
||||
}" title="Charger toutles les notes via tableur">Importer les notes</a>
|
||||
"""
|
||||
)
|
||||
else:
|
||||
bot_table_links = top_table_links
|
||||
if nb_evaluations > 0:
|
||||
H.append(
|
||||
'<div class="moduleimpl_evaluations_top_links">'
|
||||
@ -654,7 +656,7 @@ def _ligne_evaluation(
|
||||
if etat["last_modif"]:
|
||||
H.append(
|
||||
f"""<span class="mievr_lastmodif">(dernière modif le {
|
||||
etat["last_modif"].strftime("%d/%m/%Y à %Hh%M")})</span>"""
|
||||
etat["last_modif"].strftime(scu.DATEATIME_FMT)})</span>"""
|
||||
)
|
||||
#
|
||||
H.append(
|
||||
|
@ -47,7 +47,6 @@ from app.models import (
|
||||
)
|
||||
from app.scodoc import (
|
||||
codes_cursus,
|
||||
html_sco_header,
|
||||
htmlutils,
|
||||
sco_archives_etud,
|
||||
sco_bac,
|
||||
@ -255,7 +254,7 @@ def fiche_etud(etudid=None):
|
||||
|
||||
grlinks.append(
|
||||
f"""<a class="discretelink" href="{
|
||||
url_for('scolar.groups_view',
|
||||
url_for('scolar.groups_lists',
|
||||
scodoc_dept=g.scodoc_dept, group_ids=partition['group_id'])
|
||||
}" title="Liste du groupe {gr_name}">{gr_name}</a>
|
||||
"""
|
||||
@ -628,8 +627,10 @@ def fiche_etud(etudid=None):
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
header = html_sco_header.sco_header(
|
||||
page_title=f"Fiche étudiant {etud.nomprenom}",
|
||||
return render_template(
|
||||
"sco_page_dept.j2",
|
||||
content=tmpl % info,
|
||||
title=f"Fiche étudiant {etud.nomprenom}",
|
||||
cssstyles=[
|
||||
"libjs/jQuery-tagEditor/jquery.tag-editor.css",
|
||||
"css/jury_but.css",
|
||||
@ -644,7 +645,6 @@ def fiche_etud(etudid=None):
|
||||
"js/etud_debouche.js",
|
||||
],
|
||||
)
|
||||
return header + tmpl % info + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
def _format_adresse(adresse: Adresse | None) -> dict:
|
||||
@ -874,10 +874,6 @@ def etud_info_html(etudid, with_photo="1", debug=False):
|
||||
|
||||
H += "</div>"
|
||||
if debug:
|
||||
return (
|
||||
html_sco_header.standard_html_header()
|
||||
+ H
|
||||
+ html_sco_header.standard_html_footer()
|
||||
)
|
||||
else:
|
||||
return H
|
||||
return render_template("sco_page.j2", title="debug", content=H)
|
||||
|
||||
return H
|
||||
|
@ -51,7 +51,7 @@ from wtforms import (
|
||||
from app.models import Evaluation, ModuleImpl
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import html_sco_header, sco_preferences
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import sco_excel
|
||||
@ -204,14 +204,14 @@ def placement_eval_selectetuds(evaluation_id):
|
||||
% runner.__dict__
|
||||
)
|
||||
return runner.exec_placement() # calcul et generation du fichier
|
||||
htmls = [
|
||||
html_sco_header.sco_header(),
|
||||
sco_evaluations.evaluation_describe(evaluation_id=evaluation_id),
|
||||
"<h3>Placement et émargement des étudiants</h3>",
|
||||
render_template("scodoc/forms/placement.j2", form=form),
|
||||
]
|
||||
footer = html_sco_header.sco_footer()
|
||||
return "\n".join(htmls) + "<p>" + footer
|
||||
|
||||
return render_template(
|
||||
"scodoc/forms/placement.j2",
|
||||
evaluations_description=sco_evaluations.evaluation_describe(
|
||||
evaluation_id=evaluation_id
|
||||
),
|
||||
form=form,
|
||||
)
|
||||
|
||||
|
||||
class PlacementRunner:
|
||||
|
@ -233,7 +233,6 @@ def formsemestre_poursuite_report(formsemestre_id, fmt="html"):
|
||||
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
|
||||
return tab.make_page(
|
||||
title="""<h2 class="formsemestre">Poursuite d'études</h2>""",
|
||||
javascripts=["js/etud_info.js"],
|
||||
fmt=fmt,
|
||||
with_html_headers=True,
|
||||
)
|
||||
|
@ -112,7 +112,7 @@ get_base_preferences(formsemestre_id)
|
||||
"""
|
||||
|
||||
import flask
|
||||
from flask import current_app, flash, g, request, url_for
|
||||
from flask import current_app, flash, g, render_template, request, url_for
|
||||
|
||||
from app import db, log
|
||||
from app.models import Departement
|
||||
@ -369,10 +369,23 @@ class BasePreferences:
|
||||
"emails_notifications",
|
||||
{
|
||||
"initvalue": "",
|
||||
"title": "e-mails à qui notifier les opérations",
|
||||
"title": "e-mail(s) à qui notifier les opérations",
|
||||
"size": 70,
|
||||
"explanation": """adresses séparées par des virgules; notifie les opérations
|
||||
(saisies de notes, etc).
|
||||
"explanation": """optionnel; adresses séparées par des virgules;
|
||||
notifie les opérations (saisies de notes, etc).
|
||||
""",
|
||||
"category": "general",
|
||||
"only_global": False, # peut être spécifique à un semestre
|
||||
},
|
||||
),
|
||||
(
|
||||
"emails_notifications_inscriptions",
|
||||
{
|
||||
"initvalue": "",
|
||||
"title": "e-mail(s) à qui notifier les inscriptions d'étudiants",
|
||||
"size": 70,
|
||||
"explanation": """optionnel; adresses séparées par des virgules;
|
||||
notifie les inscriptions/désincriptions de chaque individu.
|
||||
""",
|
||||
"category": "general",
|
||||
"only_global": False, # peut être spécifique à un semestre
|
||||
@ -2243,14 +2256,8 @@ class BasePreferences:
|
||||
|
||||
def edit(self):
|
||||
"""HTML dialog: edit global preferences"""
|
||||
from app.scodoc import html_sco_header
|
||||
|
||||
self.load()
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"Préférences {g.scodoc_dept}",
|
||||
javascripts=["js/detail_summary_persistence.js"],
|
||||
),
|
||||
f"<h2>Préférences globales pour le département {g.scodoc_dept}</h2>",
|
||||
# f"""<p><a href="{url_for("scodoc.configure_logos", scodoc_dept=g.scodoc_dept)
|
||||
# }">modification des logos du département (pour documents pdf)</a></p>"""
|
||||
@ -2277,7 +2284,12 @@ class BasePreferences:
|
||||
)
|
||||
dest_url = url_for("scolar.index_html", scodoc_dept=g.scodoc_dept)
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
||||
return render_template(
|
||||
"sco_page_dept.j2",
|
||||
content="\n".join(H) + tf[1],
|
||||
title=f"Préférences {g.scodoc_dept}",
|
||||
javascripts=["js/detail_summary_persistence.js"],
|
||||
)
|
||||
if tf[0] == -1:
|
||||
return flask.redirect(dest_url) # cancel
|
||||
#
|
||||
@ -2321,6 +2333,7 @@ class BasePreferences:
|
||||
<option value="create">Spécifier valeur pour ce
|
||||
semestre seulement</option>
|
||||
</select>
|
||||
<span class="pref-comment">{descr["comment"]}</span>
|
||||
"""
|
||||
descr["explanation"] = menu_global
|
||||
|
||||
@ -2384,18 +2397,11 @@ class SemPreferences:
|
||||
# The dialog
|
||||
def edit(self, categories=[]):
|
||||
"""Dialog to edit semestre preferences in given categories"""
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_formsemestre
|
||||
|
||||
if not self.formsemestre_id:
|
||||
raise ScoValueError(
|
||||
"sem_preferences.edit doit etre appele sur un semestre !"
|
||||
) # a bug !
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Préférences du semestre",
|
||||
javascripts=["js/detail_summary_persistence.js"],
|
||||
),
|
||||
"""
|
||||
<p class="help">Les paramètres définis ici ne s'appliqueront qu'à ce semestre.</p>
|
||||
<p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
|
||||
@ -2456,7 +2462,12 @@ function set_global_pref(el, pref_name) {
|
||||
)
|
||||
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
content="\n".join(H) + tf[1],
|
||||
title="Préférences du semestre",
|
||||
javascripts=["js/detail_summary_persistence.js"],
|
||||
)
|
||||
elif tf[0] == -1:
|
||||
flash("Annulé")
|
||||
return flask.redirect(dest_url)
|
||||
|
@ -292,7 +292,7 @@ def _descr_decisions_ues(nt, etudid, decisions_ue, decision_sem) -> list[dict]:
|
||||
)
|
||||
)
|
||||
):
|
||||
ue = UniteEns.query.get(ue_id)
|
||||
ue = db.session.get(UniteEns, ue_id)
|
||||
assert ue
|
||||
# note modernisation code: on utilise des dict tant que get_etud_ue_status renvoie des dicts
|
||||
uelist.append(ue.to_dict())
|
||||
|
@ -35,14 +35,12 @@ from reportlab.platypus import Paragraph
|
||||
from reportlab.lib import styles
|
||||
|
||||
import flask
|
||||
from flask import flash, redirect, url_for
|
||||
from flask import g, request
|
||||
from flask import flash, g, redirect, render_template, request, url_for
|
||||
|
||||
from app.models import FormSemestre, Identite
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_pv_dict
|
||||
from app.scodoc import sco_etud
|
||||
@ -223,15 +221,13 @@ def formsemestre_pvjury(formsemestre_id, fmt="html", publish=True):
|
||||
)
|
||||
)
|
||||
|
||||
footer = html_sco_header.sco_footer()
|
||||
|
||||
dpv = sco_pv_dict.dict_pvjury(formsemestre_id, with_prev=True)
|
||||
if not dpv:
|
||||
if fmt == "html":
|
||||
return (
|
||||
html_sco_header.sco_header()
|
||||
+ "<h2>Aucune information disponible !</h2>"
|
||||
+ footer
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title="PV Jury",
|
||||
content="<h2>Aucune information disponible !</h2>",
|
||||
)
|
||||
else:
|
||||
return None
|
||||
@ -262,12 +258,10 @@ def formsemestre_pvjury(formsemestre_id, fmt="html", publish=True):
|
||||
)
|
||||
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Décisions du jury pour le semestre",
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
),
|
||||
"""<p>(dernière modif le %s)</p>""" % dpv["date"],
|
||||
f"""
|
||||
<h2 class="formsemestre">Décisions du jury pour le semestre</h2>
|
||||
<p>(dernière modif le {dpv["date"]})</p>
|
||||
""",
|
||||
]
|
||||
|
||||
H.append(
|
||||
@ -334,7 +328,9 @@ def formsemestre_pvjury(formsemestre_id, fmt="html", publish=True):
|
||||
"""
|
||||
)
|
||||
H.append("</div>") # /codes
|
||||
return "\n".join(H) + footer
|
||||
return render_template(
|
||||
"sco_page.j2", title="Décisions du jury pour le semestre", content="\n".join(H)
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@ -352,12 +348,9 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
|
||||
if etudid:
|
||||
# PV pour ce seul étudiant:
|
||||
etud = Identite.get_etud(etudid)
|
||||
etuddescr = f"""<a class="discretelink" href="{
|
||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">{etud.nomprenom}</a>"""
|
||||
etudids = [etudid]
|
||||
else:
|
||||
etuddescr = ""
|
||||
etud = None
|
||||
if not group_ids:
|
||||
# tous les inscrits du semestre
|
||||
group_ids = [sco_groups.get_default_group(formsemestre_id)]
|
||||
@ -368,12 +361,6 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
|
||||
etudids = [m["etudid"] for m in groups_infos.members]
|
||||
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
f"Édition du PV de jury {etuddescr}",
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
init_qtip=True,
|
||||
),
|
||||
f"""<div class="help">Utiliser cette page pour éditer des versions provisoires des PV.
|
||||
<span class="fontred">Il est recommandé d'archiver les versions définitives:
|
||||
<a class="stdlink" href="{url_for(
|
||||
@ -386,7 +373,6 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
|
||||
"""<p><em>Voir aussi si besoin les réglages sur la page "Paramétrage"
|
||||
(accessible à l'administrateur du département).</em>
|
||||
</p>""",
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
descr = descrform_pvjury(formsemestre)
|
||||
if etudid:
|
||||
@ -411,7 +397,20 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid
|
||||
html_foot_markup=menu_choix_groupe,
|
||||
)
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + "\n" + tf[1] + "\n".join(F)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title=f"Édition du PV de jury {('de ' + etud.nom_prenom()) if etud else ''}",
|
||||
content=f"""<h2 class="formsemestre">Édition du PV de jury
|
||||
de <a class="discretelink" href="{
|
||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">{etud.nomprenom}</a></h2>"""
|
||||
+ "\n".join(H)
|
||||
+ "\n"
|
||||
+ tf[1]
|
||||
+ "\n".join(F),
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
)
|
||||
elif tf[0] == -1:
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
@ -543,7 +542,7 @@ def descrform_pvjury(formsemestre: FormSemestre):
|
||||
]
|
||||
|
||||
|
||||
def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
|
||||
def formsemestre_lettres_individuelles(formsemestre_id, group_ids=()):
|
||||
"Lettres avis jury en PDF"
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
if not group_ids:
|
||||
@ -555,13 +554,9 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
|
||||
etudids = [m["etudid"] for m in groups_infos.members]
|
||||
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
"Édition des lettres individuelles",
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
init_qtip=True,
|
||||
),
|
||||
f"""<p class="help">Utiliser cette page pour éditer des versions provisoires des PV.
|
||||
f"""
|
||||
<h2 class="formsemestre">Édition des lettres individuelles</h2>
|
||||
<p class="help">Utiliser cette page pour éditer des versions provisoires des PV.
|
||||
<span class="fontred">Il est recommandé d'archiver les versions définitives: <a
|
||||
href="{url_for(
|
||||
"notes.formsemestre_archive",
|
||||
@ -571,7 +566,6 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
|
||||
>voir cette page</a></span></p>
|
||||
""",
|
||||
]
|
||||
F = html_sco_header.sco_footer()
|
||||
descr = descrform_lettres_individuelles()
|
||||
menu_choix_groupe = (
|
||||
"""<div class="group_ids_sel_menu">Groupes d'étudiants à lister: """
|
||||
@ -590,7 +584,13 @@ def formsemestre_lettres_individuelles(formsemestre_id, group_ids=[]):
|
||||
html_foot_markup=menu_choix_groupe,
|
||||
)
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + "\n" + tf[1] + F
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title="Édition des lettres individuelles",
|
||||
content="\n".join(H) + "\n" + tf[1],
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
)
|
||||
elif tf[0] == -1:
|
||||
return flask.redirect(
|
||||
url_for(
|
||||
|
@ -32,8 +32,7 @@ import datetime
|
||||
import time
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from flask import g, request
|
||||
from flask import abort, url_for
|
||||
from flask import abort, g, render_template, request, url_for
|
||||
|
||||
from app import log
|
||||
from app.but import bulletin_but
|
||||
@ -44,14 +43,12 @@ from app.models import FormSemestre
|
||||
from app.models.etudiants import Identite
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_bulletins_json
|
||||
from app.scodoc import sco_bulletins_xml
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_status
|
||||
from app.scodoc import sco_preferences
|
||||
from app.tables.recap import TableRecap
|
||||
from app.tables.jury_recap import TableJury
|
||||
@ -119,16 +116,9 @@ def formsemestre_recapcomplet(
|
||||
)
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"{formsemestre.sem_modalite()}: "
|
||||
+ ("jury" if mode_jury else "moyennes"),
|
||||
no_side_bar=True,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js", "js/table_recap.js"],
|
||||
),
|
||||
sco_formsemestre_status.formsemestre_status_head(
|
||||
formsemestre_id=formsemestre_id
|
||||
),
|
||||
# sco_formsemestre_status.formsemestre_status_head(
|
||||
# formsemestre_id=formsemestre_id
|
||||
# ),
|
||||
]
|
||||
if len(formsemestre.inscriptions) > 0:
|
||||
H.append(
|
||||
@ -274,10 +264,17 @@ def formsemestre_recapcomplet(
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
H.append(html_sco_header.sco_footer())
|
||||
# HTML or binary data ?
|
||||
if len(H) > 1:
|
||||
return "".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
content="".join(H),
|
||||
title=f"{formsemestre.sem_modalite()}: "
|
||||
+ ("jury" if mode_jury else "moyennes"),
|
||||
javascripts=["js/table_recap.js"],
|
||||
formsemestre_id=formsemestre_id,
|
||||
no_sidebar=True,
|
||||
)
|
||||
elif len(H) == 1:
|
||||
return H[0]
|
||||
else:
|
||||
|
@ -37,7 +37,7 @@ import time
|
||||
import datetime
|
||||
from operator import itemgetter
|
||||
|
||||
from flask import url_for, g, request
|
||||
from flask import url_for, g, render_template, request
|
||||
import pydot
|
||||
|
||||
from app import log
|
||||
@ -50,7 +50,6 @@ from app.models.etudiants import Identite
|
||||
|
||||
from app.scodoc import (
|
||||
codes_cursus,
|
||||
html_sco_header,
|
||||
sco_etud,
|
||||
sco_formsemestre,
|
||||
sco_formsemestre_inscriptions,
|
||||
@ -411,11 +410,6 @@ def formsemestre_report_counts(
|
||||
if fmt != "html":
|
||||
return tableau
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
page_title=title,
|
||||
),
|
||||
tableau,
|
||||
"\n".join(F),
|
||||
"""<p class="help">Le tableau affiche le nombre d'étudiants de ce semestre dans chacun
|
||||
@ -423,9 +417,14 @@ def formsemestre_report_counts(
|
||||
pour les lignes et les colonnes. Le <tt>codedecision</tt> est le code de la décision
|
||||
du jury.
|
||||
</p>""",
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
return "\n".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
title=title,
|
||||
content="\n".join(H),
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
@ -813,11 +812,6 @@ def formsemestre_suivi_cohorte(
|
||||
href="{burl}&percent=1">Afficher les résultats en pourcentages</a></p>"""
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
page_title=tab.page_title,
|
||||
),
|
||||
"""<h2 class="formsemestre">Suivi cohorte: devenir des étudiants de ce semestre</h2>""",
|
||||
_gen_form_selectetuds(
|
||||
formsemestre.id,
|
||||
@ -853,9 +847,14 @@ def formsemestre_suivi_cohorte(
|
||||
</p>
|
||||
""",
|
||||
expl,
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
return "\n".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
title=tab.page_title,
|
||||
content="\n".join(H),
|
||||
)
|
||||
|
||||
|
||||
def _gen_form_selectetuds(
|
||||
@ -1365,17 +1364,11 @@ def formsemestre_suivi_cursus(
|
||||
]
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=tab.page_title,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
),
|
||||
"""<h2 class="formsemestre">Cursus suivis par les étudiants de ce semestre</h2>""",
|
||||
"\n".join(F),
|
||||
t,
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
return "\n".join(H)
|
||||
return render_template("sco_page.j2", title=tab.page_title, content="\n".join(H))
|
||||
|
||||
|
||||
# -------------
|
||||
@ -1744,12 +1737,6 @@ def formsemestre_graph_cursus(
|
||||
)
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
page_title="Graphe cursus de %(titreannee)s" % sem,
|
||||
no_side_bar=True,
|
||||
),
|
||||
"""<h2 class="formsemestre">Cursus des étudiants de ce semestre</h2>""",
|
||||
doc,
|
||||
f"<p>{len(etuds)} étudiants sélectionnés</p>",
|
||||
@ -1773,11 +1760,12 @@ def formsemestre_graph_cursus(
|
||||
),
|
||||
"""<p>Origine et devenir des étudiants inscrits dans %(titreannee)s"""
|
||||
% sem,
|
||||
"""(<a href="%s">version pdf</a>"""
|
||||
% url_for("notes.formsemestre_graph_cursus", fmt="pdf", **url_kw),
|
||||
""", <a href="%s">image PNG</a>)"""
|
||||
% url_for("notes.formsemestre_graph_cursus", fmt="png", **url_kw),
|
||||
f"""
|
||||
f"""(<a href="{
|
||||
url_for("notes.formsemestre_graph_cursus", fmt="pdf", **url_kw)
|
||||
}">version pdf</a>,
|
||||
<a href="{
|
||||
url_for("notes.formsemestre_graph_cursus", fmt="png", **url_kw)}">image PNG</a>)
|
||||
|
||||
</p>
|
||||
<p class="help">Le graphe permet de suivre les étudiants inscrits dans le semestre
|
||||
sélectionné (dessiné en vert). Chaque rectangle représente un semestre (cliquez dedans
|
||||
@ -1790,8 +1778,14 @@ def formsemestre_graph_cursus(
|
||||
étudiants appartenant aux groupes indiqués <em>dans le semestre d'origine</em>.
|
||||
</p>
|
||||
""",
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
return "\n".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
page_title=f"Graphe cursus de {sem['titreannee']}",
|
||||
no_sidebar=True,
|
||||
content="\n".join(H),
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"invalid format: {fmt}")
|
||||
|
@ -31,21 +31,18 @@
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
from flask import request
|
||||
from flask import render_template, request
|
||||
|
||||
from app import db
|
||||
from app.but import jury_but
|
||||
from app.models import FormSemestre
|
||||
from app.models.formsemestre import FormSemestreInscription
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
from app.scodoc import sco_preferences
|
||||
import sco_version
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
import sco_version
|
||||
|
||||
|
||||
# Titres, ordonnés
|
||||
@ -107,13 +104,11 @@ def formsemestre_but_indicateurs(formsemestre_id: int, fmt="html"):
|
||||
if fmt != "html":
|
||||
return t
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title=title),
|
||||
t,
|
||||
"""<p class="help">
|
||||
</p>""",
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
return "\n".join(H)
|
||||
return render_template("sco_page.j2", title=title, content="\n".join(H))
|
||||
|
||||
|
||||
def but_indicateurs_by_bac(formsemestre: FormSemestre) -> dict[str:dict]:
|
||||
|
@ -51,7 +51,6 @@ from flask_login import current_user
|
||||
from app.models import Evaluation, FormSemestre, Identite, ModuleImpl, ScolarNews
|
||||
from app.scodoc.sco_excel import COLORS, ScoExcelSheet
|
||||
from app.scodoc import (
|
||||
html_sco_header,
|
||||
sco_cache,
|
||||
sco_evaluations,
|
||||
sco_evaluation_db,
|
||||
@ -651,7 +650,7 @@ def do_evaluations_upload_xls(
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_NOTE,
|
||||
obj=obj_id,
|
||||
text=f"""Chargement notes dans <a href="{status_url}">{modules_str}</a>""",
|
||||
text=f"""Notes dans <a href="{status_url}">{modules_str}</a>""",
|
||||
url=status_url,
|
||||
max_frequency=10 * 60, # 10 minutes
|
||||
)
|
||||
@ -969,19 +968,21 @@ def saisie_notes_tableur(evaluation_id: int, group_ids=()):
|
||||
moduleimpl_id = evaluation.moduleimpl.id
|
||||
formsemestre_id = evaluation.moduleimpl.formsemestre_id
|
||||
if not evaluation.moduleimpl.can_edit_notes(current_user):
|
||||
return (
|
||||
html_sco_header.sco_header()
|
||||
+ f"""
|
||||
<h2>Modification des notes impossible pour {current_user.user_name}</h2>
|
||||
<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
||||
avez l'autorisation d'effectuer cette opération)
|
||||
</p>
|
||||
<p><a class="stdlink" href="{
|
||||
url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=moduleimpl_id)
|
||||
}">Continuer</a></p>
|
||||
"""
|
||||
+ html_sco_header.sco_footer()
|
||||
dest_url = url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=moduleimpl_id,
|
||||
)
|
||||
raise ScoValueError(
|
||||
f"""
|
||||
<h2>Modification des notes impossible pour {current_user.user_name}</h2>
|
||||
<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
||||
avez l'autorisation d'effectuer cette opération)
|
||||
</p>
|
||||
<p><a class="stdlink" href="{dest_url}">Continuer</a></p>
|
||||
""",
|
||||
safe=True,
|
||||
dest_url="",
|
||||
)
|
||||
|
||||
page_title = "Saisie des notes" + (
|
||||
@ -997,12 +998,6 @@ def saisie_notes_tableur(evaluation_id: int, group_ids=()):
|
||||
)
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=page_title,
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
init_qtip=True,
|
||||
),
|
||||
sco_evaluations.evaluation_describe(evaluation_id=evaluation_id),
|
||||
"""<span class="eval_title">Saisie des notes par fichier</span>""",
|
||||
]
|
||||
@ -1177,8 +1172,13 @@ def saisie_notes_tableur(evaluation_id: int, group_ids=()):
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
H.append(html_sco_header.sco_footer())
|
||||
return "\n".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
content="\n".join(H),
|
||||
page_title=page_title,
|
||||
javascripts=sco_groups_view.JAVASCRIPTS,
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
)
|
||||
|
||||
|
||||
def formsemestre_import_notes(
|
||||
|
@ -31,7 +31,7 @@ import time
|
||||
|
||||
|
||||
import flask
|
||||
from flask import g, url_for
|
||||
from flask import g, render_template, url_for
|
||||
from flask_login import current_user
|
||||
from flask_sqlalchemy.query import Query
|
||||
import psycopg2
|
||||
@ -57,7 +57,6 @@ from app.scodoc.sco_exceptions import (
|
||||
ScoInvalidParamError,
|
||||
ScoValueError,
|
||||
)
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import htmlutils
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_etud
|
||||
@ -247,14 +246,15 @@ def do_evaluation_set_missing(
|
||||
if len(invalids) > 0:
|
||||
diag = f"Valeur {value} invalide ou hors barème"
|
||||
if diag:
|
||||
return f"""
|
||||
{html_sco_header.sco_header()}
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
content=f"""
|
||||
<h2>{diag}</h2>
|
||||
<p><a href="{ dest_url }">
|
||||
Recommencer</a>
|
||||
</p>
|
||||
{html_sco_header.sco_footer()}
|
||||
"""
|
||||
""",
|
||||
)
|
||||
# Confirm action
|
||||
if not dialog_confirmed:
|
||||
plural = len(valid_notes) > 1
|
||||
@ -293,23 +293,24 @@ def do_evaluation_set_missing(
|
||||
url=url,
|
||||
max_frequency=30 * 60,
|
||||
)
|
||||
return f"""
|
||||
{ html_sco_header.sco_header() }
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
content=f"""
|
||||
<h2>{len(etudids_changed)} notes changées</h2>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="{dest_url}">
|
||||
Revenir au formulaire de saisie des notes</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="{
|
||||
url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=evaluation.moduleimpl_id,
|
||||
)}">Tableau de bord du module</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="{dest_url}">
|
||||
Revenir au formulaire de saisie des notes</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="{
|
||||
url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=evaluation.moduleimpl_id,
|
||||
)}">Tableau de bord du module</a>
|
||||
</li>
|
||||
</ul>
|
||||
{ html_sco_header.sco_footer() }
|
||||
"""
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
||||
@ -390,7 +391,7 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
||||
url=status_url,
|
||||
)
|
||||
|
||||
return html_sco_header.sco_header() + "\n".join(H) + html_sco_header.sco_footer()
|
||||
return render_template("sco_page.j2", content="\n".join(H))
|
||||
|
||||
|
||||
def _check_inscription(
|
||||
@ -408,7 +409,7 @@ def _check_inscription(
|
||||
elif etudid not in etudids_inscrits_mod:
|
||||
msg_err = "non inscrit au module"
|
||||
if msg_err:
|
||||
etud = Identite.query.get(etudid) if isinstance(etudid, int) else None
|
||||
etud = db.session.get(Identite, etudid) if isinstance(etudid, int) else None
|
||||
msg = f"étudiant {etud.nomprenom if etud else etudid} {msg_err}"
|
||||
log(f"notes_add: {etudid} {msg}: aborting")
|
||||
raise NoteProcessError(msg)
|
||||
@ -454,7 +455,7 @@ def notes_add(
|
||||
|
||||
if (value is not None) and not isinstance(value, float):
|
||||
log(f"notes_add: {etudid} valeur de note invalide ({value}): aborting")
|
||||
etud = Identite.query.get(etudid) if isinstance(etudid, int) else None
|
||||
etud = db.session.get(Identite, etudid) if isinstance(etudid, int) else None
|
||||
raise NoteProcessError(
|
||||
f"etudiant {etud.nomprenom if etud else etudid}: valeur de note invalide ({value})"
|
||||
)
|
||||
@ -491,7 +492,9 @@ def notes_add(
|
||||
# si change sur DEM/DEF ajoute message warning aux messages
|
||||
if etudid not in etudids_actifs: # DEM ou DEF
|
||||
etud = (
|
||||
Identite.query.get(etudid) if isinstance(etudid, int) else None
|
||||
db.session.get(Identite, etudid)
|
||||
if isinstance(etudid, int)
|
||||
else None
|
||||
)
|
||||
messages.append(
|
||||
f"""étudiant {etud.nomprenom if etud else etudid
|
||||
@ -638,16 +641,17 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
|
||||
# Check access
|
||||
# (admin, respformation, and responsable_id)
|
||||
if not evaluation.moduleimpl.can_edit_notes(current_user):
|
||||
return f"""
|
||||
{html_sco_header.sco_header()}
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
content=f"""
|
||||
<h2>Modification des notes impossible pour {current_user.user_name}</h2>
|
||||
|
||||
<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_url }">Continuer</a>
|
||||
</p>
|
||||
{html_sco_header.sco_footer()}
|
||||
"""
|
||||
""",
|
||||
)
|
||||
|
||||
# Informations sur les groupes à afficher:
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
@ -664,12 +668,6 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
|
||||
)
|
||||
# HTML page:
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=page_title,
|
||||
javascripts=sco_groups_view.JAVASCRIPTS + ["js/saisie_notes.js"],
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
init_qtip=True,
|
||||
),
|
||||
sco_evaluations.evaluation_describe(
|
||||
evaluation_id=evaluation_id, link_saisie=False
|
||||
),
|
||||
@ -753,9 +751,13 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
|
||||
</ul>
|
||||
</div>"""
|
||||
)
|
||||
|
||||
H.append(html_sco_header.sco_footer())
|
||||
return "\n".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
content="\n".join(H),
|
||||
title=page_title,
|
||||
javascripts=sco_groups_view.JAVASCRIPTS + ["js/saisie_notes.js"],
|
||||
cssstyles=sco_groups_view.CSSSTYLES,
|
||||
)
|
||||
|
||||
|
||||
def get_sorted_etuds_notes(
|
||||
@ -802,7 +804,7 @@ def get_sorted_etuds_notes(
|
||||
notes_db[etudid]["value"], fixed_precision_str=False
|
||||
)
|
||||
user = (
|
||||
User.query.get(notes_db[etudid]["uid"])
|
||||
db.session.get(User, notes_db[etudid]["uid"])
|
||||
if notes_db[etudid]["uid"]
|
||||
else None
|
||||
)
|
||||
@ -1047,7 +1049,7 @@ def save_notes(
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_NOTE,
|
||||
obj=evaluation.moduleimpl_id,
|
||||
text=f"""Chargement notes dans <a href="{status_url}">{
|
||||
text=f"""Notes dans <a href="{status_url}">{
|
||||
evaluation.moduleimpl.module.titre or evaluation.moduleimpl.module.code}</a>""",
|
||||
url=status_url,
|
||||
max_frequency=30 * 60, # 30 minutes
|
||||
|
@ -40,17 +40,15 @@ sem_set_list()
|
||||
"""
|
||||
|
||||
import flask
|
||||
from flask import g, url_for
|
||||
from flask import g, render_template, url_for
|
||||
|
||||
from app import db, log
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_etape_apogee
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_status
|
||||
from app.scodoc import sco_portal_apogee
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.sco_etape_bilan import EtapeBilan
|
||||
@ -412,7 +410,7 @@ def do_semset_delete(semset_id, dialog_confirmed=False):
|
||||
s = SemSet(semset_id=semset_id)
|
||||
if not dialog_confirmed:
|
||||
return scu.confirm_dialog(
|
||||
"<h2>Suppression de l'ensemble %(title)s ?</h2>" % s,
|
||||
f"<h2>Suppression de l'ensemble {s['title']} ?</h2>",
|
||||
dest_url="",
|
||||
parameters={"semset_id": semset_id},
|
||||
cancel_url="semset_page",
|
||||
@ -421,14 +419,14 @@ def do_semset_delete(semset_id, dialog_confirmed=False):
|
||||
return flask.redirect("semset_page")
|
||||
|
||||
|
||||
def edit_semset_set_title(id=None, value=None):
|
||||
def edit_semset_set_title(oid=None, value=None):
|
||||
"""Change title of semset"""
|
||||
title = value.strip()
|
||||
if not id:
|
||||
if not oid:
|
||||
raise ScoValueError("empty semset_id")
|
||||
SemSet(semset_id=id)
|
||||
SemSet(semset_id=oid)
|
||||
cnx = ndb.GetDBConnexion()
|
||||
semset_edit(cnx, {"semset_id": id, "title": title})
|
||||
semset_edit(cnx, {"semset_id": oid, "title": title})
|
||||
return title
|
||||
|
||||
|
||||
@ -517,23 +515,18 @@ def semset_page(fmt="html"):
|
||||
|
||||
page_title = "Ensembles de semestres"
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=page_title,
|
||||
init_qtip=True,
|
||||
javascripts=["libjs/jinplace-1.2.1.min.js"],
|
||||
),
|
||||
"""<script>$(function() {
|
||||
$('.inplace_edit').jinplace();
|
||||
});
|
||||
</script>""",
|
||||
"<h2>%s</h2>" % page_title,
|
||||
f"<h2>{page_title}</h2>",
|
||||
]
|
||||
H.append(tab.html())
|
||||
|
||||
annee_courante = int(scu.annee_scolaire())
|
||||
menu_annee = "\n".join(
|
||||
[
|
||||
'<option value="%s">%s</option>' % (i, i)
|
||||
f"""<option value="{i}">{i}</option>"""
|
||||
for i in range(2014, annee_courante + 1)
|
||||
]
|
||||
)
|
||||
@ -562,8 +555,8 @@ def semset_page(fmt="html"):
|
||||
|
||||
H.append(
|
||||
"""
|
||||
<div>
|
||||
<h4>Autres opérations:</h4>
|
||||
<div class="scobox space-before-24">
|
||||
<div class="scobox-title">Autres opérations :</div>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="scodoc_table_results">
|
||||
Table des résultats de tous les semestres
|
||||
@ -576,4 +569,9 @@ def semset_page(fmt="html"):
|
||||
"""
|
||||
)
|
||||
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
return render_template(
|
||||
"sco_page_dept.j2",
|
||||
title=page_title,
|
||||
javascripts=["libjs/jinplace-1.2.1.min.js"],
|
||||
content="\n".join(H),
|
||||
)
|
||||
|
@ -31,7 +31,7 @@
|
||||
import time
|
||||
from operator import itemgetter
|
||||
|
||||
from flask import g, url_for
|
||||
from flask import g, render_template, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
from app import db, log
|
||||
@ -39,7 +39,6 @@ from app.models import Admission, Adresse, FormSemestre, Identite, ScolarNews
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
@ -118,7 +117,6 @@ def formsemestre_synchro_etuds(
|
||||
""",
|
||||
safe=True,
|
||||
)
|
||||
footer = html_sco_header.sco_footer()
|
||||
base_url = url_for(
|
||||
"notes.formsemestre_synchro_etuds",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
@ -168,13 +166,7 @@ def formsemestre_synchro_etuds(
|
||||
suffix=scu.XLSX_SUFFIX,
|
||||
)
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Synchronisation étudiants",
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
)
|
||||
]
|
||||
H = []
|
||||
if not submitted:
|
||||
H += _build_page(
|
||||
sem,
|
||||
@ -306,8 +298,9 @@ def formsemestre_synchro_etuds(
|
||||
"""
|
||||
)
|
||||
|
||||
H.append(footer)
|
||||
return "\n".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2", title="Synchronisation des étudiants", content="\n".join(H)
|
||||
)
|
||||
|
||||
|
||||
def _build_page(
|
||||
|
@ -49,7 +49,6 @@ import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc.sco_exceptions import ScoPDFFormatError, ScoValueError
|
||||
from app.scodoc.sco_pdf import SU
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import htmlutils
|
||||
from app.scodoc import sco_import_etuds
|
||||
from app.scodoc import sco_excel
|
||||
@ -90,12 +89,7 @@ def trombino(
|
||||
return _listeappel_photos_pdf(groups_infos)
|
||||
elif fmt == "doc":
|
||||
return sco_trombino_doc.trombino_doc(groups_infos)
|
||||
else:
|
||||
raise Exception("invalid format")
|
||||
|
||||
|
||||
def _trombino_html_header():
|
||||
return html_sco_header.sco_header(javascripts=["js/trombino.js"])
|
||||
raise ValueError("invalid format")
|
||||
|
||||
|
||||
def trombino_html(groups_infos):
|
||||
@ -208,8 +202,7 @@ def check_local_photos_availability(groups_infos, fmt=""):
|
||||
>exporter seulement les photos existantes</a>""",
|
||||
dest_url="trombino",
|
||||
OK="Exporter seulement les photos existantes",
|
||||
cancel_url="groups_view?curtab=tab-photos&"
|
||||
+ groups_infos.groups_query_args,
|
||||
cancel_url="groups_photos?" + groups_infos.groups_query_args,
|
||||
parameters=parameters,
|
||||
),
|
||||
)
|
||||
@ -249,17 +242,21 @@ def trombino_copy_photos(group_ids=None, dialog_confirmed=False):
|
||||
"Copy photos from portal to ScoDoc (overwriting local copy)"
|
||||
group_ids = [] if group_ids is None else group_ids
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
||||
back_url = "groups_view?%s&curtab=tab-photos" % groups_infos.groups_query_args
|
||||
back_url = "groups_photos?" + str(groups_infos.groups_query_args)
|
||||
|
||||
portal_url = sco_portal_apogee.get_portal_url()
|
||||
header = html_sco_header.sco_header(page_title="Chargement des photos")
|
||||
footer = html_sco_header.sco_footer()
|
||||
if not portal_url:
|
||||
return f"""{ header }
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
content=f"""
|
||||
<p>portail non configuré</p>
|
||||
<p><a href="{back_url}" class="stdlink">Retour au trombinoscope</a></p>
|
||||
{ footer }
|
||||
"""
|
||||
<div>
|
||||
<a class="stdlink" href="{back_url}" class="stdlink">
|
||||
Retour au trombinoscope
|
||||
</a>
|
||||
</div>
|
||||
""",
|
||||
)
|
||||
if not dialog_confirmed:
|
||||
return scu.confirm_dialog(
|
||||
f"""<h2>Copier les photos du portail vers ScoDoc ?</h2>
|
||||
@ -286,14 +283,18 @@ def trombino_copy_photos(group_ids=None, dialog_confirmed=False):
|
||||
|
||||
msg.append(f"<b>{nok} photos correctement chargées</b>")
|
||||
|
||||
return f"""{ header }
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
content=f"""
|
||||
<h2>Chargement des photos depuis le portail</h2>
|
||||
<ul><li>
|
||||
{ '</li><li>'.join(msg) }
|
||||
</li></ul>
|
||||
<p><a href="{back_url}">retour au trombinoscope</a>
|
||||
{ footer }
|
||||
"""
|
||||
<div class="space-before-24">
|
||||
<a class="stdlink" href="{back_url}">retour au trombinoscope</a>
|
||||
</div>
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
def _get_etud_platypus_image(t, image_width=2 * cm):
|
||||
@ -504,10 +505,9 @@ def photos_import_files_form(group_ids=()):
|
||||
if not group_ids:
|
||||
raise ScoValueError("paramètre manquant !")
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
||||
back_url = f"groups_view?{groups_infos.groups_query_args}&curtab=tab-photos"
|
||||
back_url = f"groups_photos?{groups_infos.groups_query_args}"
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Import des photos des étudiants"),
|
||||
f"""<h2 class="formsemestre">Téléchargement des photos des étudiants</h2>
|
||||
<p><b>Vous pouvez aussi charger les photos individuellement via la fiche
|
||||
de chaque étudiant (menu "Étudiant" / "Changer la photo").</b>
|
||||
@ -527,7 +527,6 @@ def photos_import_files_form(group_ids=()):
|
||||
<li style="padding-top: 2em;">
|
||||
""",
|
||||
]
|
||||
F = html_sco_header.sco_footer()
|
||||
vals = scu.get_request_args()
|
||||
vals["group_ids"] = groups_infos.group_ids
|
||||
tf = TrivialFormulator(
|
||||
@ -541,38 +540,40 @@ def photos_import_files_form(group_ids=()):
|
||||
)
|
||||
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + tf[1] + "</li></ol>" + F
|
||||
elif tf[0] == -1:
|
||||
return flask.redirect(back_url)
|
||||
else:
|
||||
|
||||
def callback(etud: Identite, data, filename):
|
||||
return sco_photos.store_photo(etud, data, filename)
|
||||
|
||||
(
|
||||
ignored_zipfiles,
|
||||
unmatched_files,
|
||||
stored_etud_filename,
|
||||
) = zip_excel_import_files(
|
||||
xlsfile=tf[2]["xlsfile"],
|
||||
zipfile=tf[2]["zipfile"],
|
||||
callback=callback,
|
||||
filename_title="fichier_photo",
|
||||
back_url=back_url,
|
||||
)
|
||||
return render_template(
|
||||
"scolar/photos_import_files.j2",
|
||||
page_title="Téléchargement des photos des étudiants",
|
||||
ignored_zipfiles=ignored_zipfiles,
|
||||
unmatched_files=unmatched_files,
|
||||
stored_etud_filename=stored_etud_filename,
|
||||
next_page=url_for(
|
||||
"scolar.groups_view",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=groups_infos.formsemestre_id,
|
||||
curtab="tab-photos",
|
||||
),
|
||||
"sco_page.j2",
|
||||
title="Import des photos des étudiants",
|
||||
content="\n".join(H) + tf[1] + "</li></ol>",
|
||||
)
|
||||
if tf[0] == -1:
|
||||
return flask.redirect(back_url)
|
||||
|
||||
def callback(etud: Identite, data, filename):
|
||||
return sco_photos.store_photo(etud, data, filename)
|
||||
|
||||
(
|
||||
ignored_zipfiles,
|
||||
unmatched_files,
|
||||
stored_etud_filename,
|
||||
) = zip_excel_import_files(
|
||||
xlsfile=tf[2]["xlsfile"],
|
||||
zipfile=tf[2]["zipfile"],
|
||||
callback=callback,
|
||||
filename_title="fichier_photo",
|
||||
back_url=back_url,
|
||||
)
|
||||
return render_template(
|
||||
"scolar/photos_import_files.j2",
|
||||
page_title="Téléchargement des photos des étudiants",
|
||||
ignored_zipfiles=ignored_zipfiles,
|
||||
unmatched_files=unmatched_files,
|
||||
stored_etud_filename=stored_etud_filename,
|
||||
next_page=url_for(
|
||||
"scolar.groups_photos",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=groups_infos.formsemestre_id,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _norm_zip_filename(fn, lowercase=True):
|
||||
|
@ -54,14 +54,13 @@ Solution proposée (nov 2014):
|
||||
|
||||
"""
|
||||
import flask
|
||||
from flask import flash, g, request, url_for
|
||||
from flask import flash, g, request, render_template, url_for
|
||||
from flask_login import current_user
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
|
||||
from app import db, log
|
||||
from app.models import Evaluation, Identite, ModuleImpl, UniteEns
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_edit_matiere
|
||||
from app.scodoc import sco_edit_module
|
||||
@ -144,7 +143,7 @@ def external_ue_create(
|
||||
),
|
||||
},
|
||||
)
|
||||
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||
modimpl = db.session.get(ModuleImpl, moduleimpl_id)
|
||||
assert modimpl
|
||||
return modimpl
|
||||
|
||||
@ -206,7 +205,7 @@ def get_external_moduleimpl(formsemestre_id: int, ue_id: int) -> ModuleImpl:
|
||||
)
|
||||
if r:
|
||||
modimpl_id = r[0]["moduleimpl_id"]
|
||||
modimpl = ModuleImpl.query.get(modimpl_id)
|
||||
modimpl = db.session.get(ModuleImpl, modimpl_id)
|
||||
assert modimpl
|
||||
return modimpl
|
||||
else:
|
||||
@ -240,11 +239,9 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
|
||||
existing_external_ue = get_existing_external_ue(formation_id)
|
||||
|
||||
H = [
|
||||
html_sco_header.html_sem_header(
|
||||
f"Ajout d'une UE externe pour {etud.nomprenom}",
|
||||
javascripts=["js/sco_ue_external.js"],
|
||||
),
|
||||
"""<p class="help">Cette page permet d'indiquer que l'étudiant a suivi une UE
|
||||
f"""
|
||||
<h2 class="formsemestre">Ajout d'une UE externe pour {etud.nomprenom}</h2>
|
||||
<p class="help">Cette page permet d'indiquer que l'étudiant a suivi une UE
|
||||
dans un autre établissement et qu'elle doit être intégrée dans le semestre courant.<br>
|
||||
La note (/20) obtenue par l'étudiant doit toujours être spécifiée.</br>
|
||||
On peut choisir une UE externe existante (dans le menu), ou bien en créer une, qui sera
|
||||
@ -252,7 +249,6 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
|
||||
</p>
|
||||
""",
|
||||
]
|
||||
html_footer = html_sco_header.sco_footer()
|
||||
parcours = formsemestre.formation.get_cursus()
|
||||
ue_types = [
|
||||
typ for typ in parcours.ALLOWED_UE_TYPES if typ != codes_cursus.UE_SPORT
|
||||
@ -349,7 +345,12 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
|
||||
etudid=etudid,
|
||||
)
|
||||
if tf[0] == 0:
|
||||
return "\n".join(H) + "\n" + tf[1] + html_footer
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title=f"Ajout d'une UE externe pour {etud.nomprenom}",
|
||||
javascripts=["js/sco_ue_external.js"],
|
||||
content="\n".join(H) + "\n" + tf[1],
|
||||
)
|
||||
elif tf[0] == -1:
|
||||
return flask.redirect(bull_url)
|
||||
else:
|
||||
@ -358,12 +359,14 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
|
||||
note, 20.0, etudid=etudid, absents=[], invalids=[]
|
||||
)
|
||||
if invalid:
|
||||
return (
|
||||
"\n".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title=f"Ajout d'une UE externe pour {etud.nomprenom}",
|
||||
javascripts=["js/sco_ue_external.js"],
|
||||
content="\n".join(H)
|
||||
+ "\n"
|
||||
+ tf_error_message("valeur note invalide")
|
||||
+ tf[1]
|
||||
+ html_footer
|
||||
+ tf[1],
|
||||
)
|
||||
if tf[2]["existing_ue"]:
|
||||
ue_id = int(tf[2]["existing_ue"])
|
||||
@ -371,12 +374,14 @@ def external_ue_create_form(formsemestre_id: int, etudid: int):
|
||||
else:
|
||||
acronyme = tf[2]["acronyme"].strip()
|
||||
if not acronyme:
|
||||
return (
|
||||
"\n".join(H)
|
||||
return render_template(
|
||||
"sco_page.j2",
|
||||
title=f"Ajout d'une UE externe pour {etud.nomprenom}",
|
||||
javascripts=["js/sco_ue_external.js"],
|
||||
content="\n".join(H)
|
||||
+ "\n"
|
||||
+ tf_error_message("spécifier acronyme d'UE")
|
||||
+ tf[1]
|
||||
+ html_footer
|
||||
+ tf[1],
|
||||
)
|
||||
modimpl = external_ue_create(
|
||||
formsemestre_id,
|
||||
|
@ -31,7 +31,7 @@
|
||||
# Anciennement ZScoUsers.py, fonctions de gestion des données réécrites avec flask/SQLAlchemy
|
||||
import re
|
||||
|
||||
from flask import url_for, g, request
|
||||
from flask import url_for, g, render_template, request
|
||||
from flask_login import current_user
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ def index_html(
|
||||
all_depts = int(all_depts)
|
||||
with_inactives = int(with_inactives)
|
||||
|
||||
H = [html_sco_header.html_sem_header("Gestion des utilisateurs")]
|
||||
H = ["<h1>Gestion des utilisateurs</h1>"]
|
||||
|
||||
if current_user.has_permission(Permission.UsersAdmin, g.scodoc_dept):
|
||||
H.append(
|
||||
@ -112,6 +112,7 @@ def index_html(
|
||||
raise ScoValueError("nom de rôle invalide")
|
||||
else:
|
||||
having_role = None
|
||||
|
||||
content = list_users(
|
||||
g.scodoc_dept,
|
||||
all_depts=all_depts,
|
||||
@ -122,10 +123,12 @@ def index_html(
|
||||
)
|
||||
if fmt != "html":
|
||||
return content
|
||||
|
||||
H.append(content)
|
||||
|
||||
F = html_sco_header.sco_footer()
|
||||
return "\n".join(H) + F
|
||||
return render_template(
|
||||
"sco_page_dept.j2", content="\n".join(H), title="Gestion des utilisateurs"
|
||||
)
|
||||
|
||||
|
||||
def list_users(
|
||||
|
@ -26,8 +26,8 @@
|
||||
##############################################################################
|
||||
|
||||
|
||||
""" Common definitions
|
||||
"""
|
||||
"""Common definitions"""
|
||||
|
||||
import base64
|
||||
import bisect
|
||||
import collections
|
||||
@ -55,7 +55,7 @@ from pytz import timezone
|
||||
|
||||
|
||||
import flask
|
||||
from flask import g, request, Response
|
||||
from flask import g, render_template, request, Response
|
||||
from flask import flash, make_response
|
||||
from flask_json import json_response
|
||||
from werkzeug.http import HTTP_STATUS_CODES
|
||||
@ -63,6 +63,8 @@ from werkzeug.http import HTTP_STATUS_CODES
|
||||
from config import Config
|
||||
from app import log, ScoDocJSONEncoder
|
||||
|
||||
from app.forms.multiselect import MultiSelect
|
||||
|
||||
from app.scodoc.codes_cursus import NOTES_TOLERANCE, CODES_EXPL
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc import sco_xml
|
||||
@ -554,7 +556,7 @@ MONTH_NAMES = (
|
||||
)
|
||||
DAY_NAMES = ("lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche")
|
||||
|
||||
TIME_FMT = "%H:%M" # affichage des heures
|
||||
TIME_FMT = "%Hh%M" # affichage des heures
|
||||
DATE_FMT = "%d/%m/%Y" # affichage des dates
|
||||
DATEATIME_FMT = DATE_FMT + " à " + TIME_FMT
|
||||
DATETIME_FMT = DATE_FMT + " " + TIME_FMT
|
||||
@ -1056,6 +1058,15 @@ def flash_once(message: str):
|
||||
g.sco_flashed_once.add(message)
|
||||
|
||||
|
||||
def html_flash_message(message: str):
|
||||
"""HTML for flashed messaged, for legacy codes"""
|
||||
return f"""<div class="container flashes">
|
||||
<div class="alert alert-info alert-message" role="alert">
|
||||
{message}
|
||||
</div>
|
||||
</div>"""
|
||||
|
||||
|
||||
def sendCSVFile(data, filename): # DEPRECATED utiliser send_file
|
||||
"""publication fichier CSV."""
|
||||
return send_file(data, filename=filename, mime=CSV_MIMETYPE, attached=True)
|
||||
@ -1328,7 +1339,7 @@ def format_telephone(n: str | None) -> str:
|
||||
#
|
||||
def timedate_human_repr():
|
||||
"representation du temps courant pour utilisateur"
|
||||
return time.strftime("%d/%m/%Y à %Hh%M")
|
||||
return time.strftime(DATEATIME_FMT)
|
||||
|
||||
|
||||
def annee_scolaire_repr(year, month):
|
||||
@ -1514,10 +1525,9 @@ def confirm_dialog(
|
||||
help_msg=None,
|
||||
parameters: dict = None,
|
||||
target_variable="dialog_confirmed",
|
||||
template="sco_page.j2",
|
||||
):
|
||||
"""HTML confirmation dialog: submit (POST) to same page or dest_url if given."""
|
||||
from app.scodoc import html_sco_header
|
||||
|
||||
parameters = parameters or {}
|
||||
# dialog de confirmation simple
|
||||
parameters[target_variable] = 1
|
||||
@ -1556,9 +1566,7 @@ def confirm_dialog(
|
||||
if help_msg:
|
||||
H.append('<p class="help">' + help_msg + "</p>")
|
||||
if add_headers:
|
||||
return (
|
||||
html_sco_header.sco_header() + "\n".join(H) + html_sco_header.sco_footer()
|
||||
)
|
||||
return render_template(template, content="\n".join(H))
|
||||
else:
|
||||
return "\n".join(H)
|
||||
|
||||
|
@ -1,380 +0,0 @@
|
||||
@keyframes dtb-spinner {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-o-keyframes dtb-spinner {
|
||||
100% {
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-ms-keyframes dtb-spinner {
|
||||
100% {
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes dtb-spinner {
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-moz-keyframes dtb-spinner {
|
||||
100% {
|
||||
-moz-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
div.dataTables_wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.dt-buttons {
|
||||
position: initial;
|
||||
}
|
||||
|
||||
div.dt-button-info {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 400px;
|
||||
margin-top: -100px;
|
||||
margin-left: -200px;
|
||||
background-color: white;
|
||||
border: 2px solid #111;
|
||||
box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
z-index: 21;
|
||||
}
|
||||
div.dt-button-info h2 {
|
||||
padding: 0.5em;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
div.dt-button-info > div {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
div.dtb-popover-close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 1px solid #eaeaea;
|
||||
background-color: #f9f9f9;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
button.dtb-hide-drop {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
div.dt-button-collection-title {
|
||||
text-align: center;
|
||||
padding: 0.3em 0 0.5em;
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
div.dt-button-collection-title:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.dt-button-spacer {
|
||||
display: inline-block;
|
||||
margin: 0.5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
span.dt-button-spacer.bar {
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.3);
|
||||
vertical-align: middle;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
span.dt-button-spacer.bar:empty {
|
||||
height: 1em;
|
||||
width: 1px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
div.dt-button-collection span.dt-button-spacer {
|
||||
width: 100%;
|
||||
font-size: 0.9em;
|
||||
text-align: center;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
div.dt-button-collection span.dt-button-spacer:empty {
|
||||
height: 0;
|
||||
width: 100%;
|
||||
}
|
||||
div.dt-button-collection span.dt-button-spacer.bar {
|
||||
border-left: none;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
div.dt-button-collection {
|
||||
position: absolute;
|
||||
z-index: 2001;
|
||||
background-color: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
padding: 0.5rem 0;
|
||||
min-width: 200px;
|
||||
}
|
||||
div.dt-button-collection ul.dropdown-menu {
|
||||
position: relative;
|
||||
display: block;
|
||||
z-index: 2002;
|
||||
min-width: 100%;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
div.dt-button-collection button.dt-btn-split-drop-button {
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
margin-left: 0px !important;
|
||||
}
|
||||
div.dt-button-collection button.dt-btn-split-drop-button:focus {
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
outline: none;
|
||||
}
|
||||
div.dt-button-collection.fixed {
|
||||
position: fixed;
|
||||
display: block;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -75px;
|
||||
border-radius: 5px;
|
||||
background-color: white;
|
||||
}
|
||||
div.dt-button-collection.fixed.two-column {
|
||||
margin-left: -200px;
|
||||
}
|
||||
div.dt-button-collection.fixed.three-column {
|
||||
margin-left: -225px;
|
||||
}
|
||||
div.dt-button-collection.fixed.four-column {
|
||||
margin-left: -300px;
|
||||
}
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -409px;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -308px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 640px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -203px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 460px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -100px;
|
||||
}
|
||||
}
|
||||
div.dt-button-collection.fixed > :last-child {
|
||||
max-height: 100vh;
|
||||
overflow: auto;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child, div.dt-button-collection.three-column > :last-child, div.dt-button-collection.four-column > :last-child {
|
||||
display: block !important;
|
||||
-webkit-column-gap: 8px;
|
||||
-moz-column-gap: 8px;
|
||||
-ms-column-gap: 8px;
|
||||
-o-column-gap: 8px;
|
||||
column-gap: 8px;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child > *, div.dt-button-collection.three-column > :last-child > *, div.dt-button-collection.four-column > :last-child > * {
|
||||
-webkit-column-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
div.dt-button-collection.two-column {
|
||||
width: 400px;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 2;
|
||||
}
|
||||
div.dt-button-collection.three-column {
|
||||
width: 450px;
|
||||
}
|
||||
div.dt-button-collection.three-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 3;
|
||||
}
|
||||
div.dt-button-collection.four-column {
|
||||
width: 600px;
|
||||
}
|
||||
div.dt-button-collection.four-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 4;
|
||||
}
|
||||
div.dt-button-collection .dt-button {
|
||||
border-radius: 0;
|
||||
}
|
||||
div.dt-button-collection.columns {
|
||||
width: auto;
|
||||
}
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 818px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
div.dt-button-collection.columns > :last-child .dt-button {
|
||||
min-width: 200px;
|
||||
flex: 0 1;
|
||||
margin: 0;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 > :last-child, div.dt-button-collection.columns.dtb-b2 > :last-child, div.dt-button-collection.columns.dtb-b1 > :last-child {
|
||||
justify-content: space-between;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 .dt-button {
|
||||
flex: 1 1 32%;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b2 .dt-button {
|
||||
flex: 1 1 48%;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b1 .dt-button {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 612px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 640px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 406px;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 .dt-button {
|
||||
flex: 0 1 32%;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 460px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
div.dt-button-collection .dt-button {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
div.dt-button-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2001;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
div.dt-buttons {
|
||||
float: none;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
div.dt-buttons a.btn {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
div.dt-buttons button.btn.processing,
|
||||
div.dt-buttons div.btn.processing,
|
||||
div.dt-buttons a.btn.processing {
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
div.dt-buttons button.btn.processing:after,
|
||||
div.dt-buttons div.btn.processing:after,
|
||||
div.dt-buttons a.btn.processing:after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: -8px 0 0 -8px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
content: " ";
|
||||
border: 2px solid #282828;
|
||||
border-radius: 50%;
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
animation: dtb-spinner 1500ms infinite linear;
|
||||
-o-animation: dtb-spinner 1500ms infinite linear;
|
||||
-ms-animation: dtb-spinner 1500ms infinite linear;
|
||||
-webkit-animation: dtb-spinner 1500ms infinite linear;
|
||||
-moz-animation: dtb-spinner 1500ms infinite linear;
|
||||
}
|
||||
|
||||
div.dt-btn-split-wrapper button.dt-btn-split-drop {
|
||||
border-top-right-radius: 4px !important;
|
||||
border-bottom-right-radius: 4px !important;
|
||||
}
|
||||
div.dt-btn-split-wrapper:active:not(.disabled) button, div.dt-btn-split-wrapper.active:not(.disabled) button {
|
||||
background-color: #e6e6e6;
|
||||
border-color: #adadad;
|
||||
}
|
||||
div.dt-btn-split-wrapper:active:not(.disabled) button.dt-btn-split-drop, div.dt-btn-split-wrapper.active:not(.disabled) button.dt-btn-split-drop {
|
||||
box-shadow: none;
|
||||
background-color: #fff;
|
||||
border-color: #adadad;
|
||||
}
|
||||
div.dt-btn-split-wrapper:active:not(.disabled) button:hover, div.dt-btn-split-wrapper.active:not(.disabled) button:hover {
|
||||
background-color: #e6e6e6;
|
||||
border-color: #adadad;
|
||||
}
|
||||
|
||||
span.dt-down-arrow {
|
||||
color: rgba(70, 70, 70, 0.9);
|
||||
font-size: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
div.dataTables_wrapper div.dt-buttons.btn-group button.btn:last-of-type:first-of-type {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
span.dt-down-arrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.dt-button-spacer {
|
||||
float: left;
|
||||
}
|
||||
span.dt-button-spacer.bar:empty {
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
div.dt-button-collection span.dt-button-spacer {
|
||||
padding-left: 1rem !important;
|
||||
text-align: left;
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -1,426 +0,0 @@
|
||||
@keyframes dtb-spinner {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-o-keyframes dtb-spinner {
|
||||
100% {
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-ms-keyframes dtb-spinner {
|
||||
100% {
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes dtb-spinner {
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-moz-keyframes dtb-spinner {
|
||||
100% {
|
||||
-moz-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
div.dataTables_wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.dt-buttons {
|
||||
position: initial;
|
||||
}
|
||||
|
||||
div.dt-button-info {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 400px;
|
||||
margin-top: -100px;
|
||||
margin-left: -200px;
|
||||
background-color: white;
|
||||
border: 2px solid #111;
|
||||
box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
z-index: 21;
|
||||
}
|
||||
div.dt-button-info h2 {
|
||||
padding: 0.5em;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
div.dt-button-info > div {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
div.dtb-popover-close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 1px solid #eaeaea;
|
||||
background-color: #f9f9f9;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
button.dtb-hide-drop {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
div.dt-button-collection-title {
|
||||
text-align: center;
|
||||
padding: 0.3em 0 0.5em;
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
div.dt-button-collection-title:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.dt-button-spacer {
|
||||
display: inline-block;
|
||||
margin: 0.5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
span.dt-button-spacer.bar {
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.3);
|
||||
vertical-align: middle;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
span.dt-button-spacer.bar:empty {
|
||||
height: 1em;
|
||||
width: 1px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
div.dt-button-collection span.dt-button-spacer {
|
||||
width: 100%;
|
||||
font-size: 0.9em;
|
||||
text-align: center;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
div.dt-button-collection span.dt-button-spacer:empty {
|
||||
height: 0;
|
||||
width: 100%;
|
||||
}
|
||||
div.dt-button-collection span.dt-button-spacer.bar {
|
||||
border-left: none;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
div.dt-button-collection {
|
||||
position: absolute;
|
||||
z-index: 2001;
|
||||
background-color: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
padding: 0.5rem 0;
|
||||
width: 200px;
|
||||
}
|
||||
div.dt-button-collection div.dropdown-menu {
|
||||
position: relative;
|
||||
display: block;
|
||||
z-index: 2002;
|
||||
min-width: 100%;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
div.dt-button-collection.fixed {
|
||||
position: fixed;
|
||||
display: block;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -75px;
|
||||
border-radius: 5px;
|
||||
background-color: white;
|
||||
}
|
||||
div.dt-button-collection.fixed.two-column {
|
||||
margin-left: -200px;
|
||||
}
|
||||
div.dt-button-collection.fixed.three-column {
|
||||
margin-left: -225px;
|
||||
}
|
||||
div.dt-button-collection.fixed.four-column {
|
||||
margin-left: -300px;
|
||||
}
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -409px;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -308px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 640px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -203px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 460px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -100px;
|
||||
}
|
||||
}
|
||||
div.dt-button-collection.fixed > :last-child {
|
||||
max-height: 100vh;
|
||||
overflow: auto;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child, div.dt-button-collection.three-column > :last-child, div.dt-button-collection.four-column > :last-child {
|
||||
display: block !important;
|
||||
-webkit-column-gap: 8px;
|
||||
-moz-column-gap: 8px;
|
||||
-ms-column-gap: 8px;
|
||||
-o-column-gap: 8px;
|
||||
column-gap: 8px;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child > *, div.dt-button-collection.three-column > :last-child > *, div.dt-button-collection.four-column > :last-child > * {
|
||||
-webkit-column-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
div.dt-button-collection.two-column {
|
||||
width: 400px;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 2;
|
||||
}
|
||||
div.dt-button-collection.three-column {
|
||||
width: 450px;
|
||||
}
|
||||
div.dt-button-collection.three-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 3;
|
||||
}
|
||||
div.dt-button-collection.four-column {
|
||||
width: 600px;
|
||||
}
|
||||
div.dt-button-collection.four-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 4;
|
||||
}
|
||||
div.dt-button-collection .dt-button {
|
||||
border-radius: 0;
|
||||
}
|
||||
div.dt-button-collection.columns {
|
||||
width: auto;
|
||||
}
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 818px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
div.dt-button-collection.columns > :last-child .dt-button {
|
||||
min-width: 200px;
|
||||
flex: 0 1;
|
||||
margin: 0;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 > :last-child, div.dt-button-collection.columns.dtb-b2 > :last-child, div.dt-button-collection.columns.dtb-b1 > :last-child {
|
||||
justify-content: space-between;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 .dt-button {
|
||||
flex: 1 1 32%;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b2 .dt-button {
|
||||
flex: 1 1 48%;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b1 .dt-button {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 612px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 640px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 406px;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 .dt-button {
|
||||
flex: 0 1 32%;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 460px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
div.dt-button-collection.fixed:before, div.dt-button-collection.fixed:after {
|
||||
display: none;
|
||||
}
|
||||
div.dt-button-collection .btn-group {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
div.dt-button-collection .dt-button {
|
||||
min-width: 200px;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper {
|
||||
width: 100%;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
div.dt-button-collection button.dt-btn-split-drop-button {
|
||||
width: 100%;
|
||||
color: #212529;
|
||||
border: none;
|
||||
background-color: white;
|
||||
border-radius: 0px;
|
||||
margin-left: 0px !important;
|
||||
}
|
||||
div.dt-button-collection button.dt-btn-split-drop-button:focus {
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
outline: none;
|
||||
}
|
||||
div.dt-button-collection button.dt-btn-split-drop-button:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
div.dt-button-collection button.dt-btn-split-drop-button:active {
|
||||
background-color: #007bff !important;
|
||||
}
|
||||
|
||||
div.dt-button-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
div.dt-buttons {
|
||||
float: none;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
div.dt-buttons a.btn {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
div.dt-buttons button.btn.processing,
|
||||
div.dt-buttons div.btn.processing,
|
||||
div.dt-buttons a.btn.processing {
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
div.dt-buttons button.btn.processing:after,
|
||||
div.dt-buttons div.btn.processing:after,
|
||||
div.dt-buttons a.btn.processing:after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: -8px 0 0 -8px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
content: " ";
|
||||
border: 2px solid #282828;
|
||||
border-radius: 50%;
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
animation: dtb-spinner 1500ms infinite linear;
|
||||
-o-animation: dtb-spinner 1500ms infinite linear;
|
||||
-ms-animation: dtb-spinner 1500ms infinite linear;
|
||||
-webkit-animation: dtb-spinner 1500ms infinite linear;
|
||||
-moz-animation: dtb-spinner 1500ms infinite linear;
|
||||
}
|
||||
div.dt-buttons div.btn-group {
|
||||
position: initial;
|
||||
}
|
||||
|
||||
div.dt-btn-split-wrapper:active:not(.disabled) button, div.dt-btn-split-wrapper.active:not(.disabled) button {
|
||||
background-color: #5a6268;
|
||||
border-color: #545b62;
|
||||
}
|
||||
div.dt-btn-split-wrapper:active:not(.disabled) button.dt-btn-split-drop, div.dt-btn-split-wrapper.active:not(.disabled) button.dt-btn-split-drop {
|
||||
box-shadow: none;
|
||||
background-color: #6c757d;
|
||||
border-color: #6c757d;
|
||||
}
|
||||
div.dt-btn-split-wrapper:active:not(.disabled) button:hover, div.dt-btn-split-wrapper.active:not(.disabled) button:hover {
|
||||
background-color: #5a6268;
|
||||
border-color: #545b62;
|
||||
}
|
||||
|
||||
div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group:last-child {
|
||||
border-top-left-radius: 0px !important;
|
||||
border-bottom-left-radius: 0px !important;
|
||||
}
|
||||
div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group:first-child {
|
||||
border-top-right-radius: 0px !important;
|
||||
border-bottom-right-radius: 0px !important;
|
||||
}
|
||||
div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group:last-child:first-child {
|
||||
border-top-left-radius: 4px !important;
|
||||
border-bottom-left-radius: 4px !important;
|
||||
border-top-right-radius: 4px !important;
|
||||
border-bottom-right-radius: 4px !important;
|
||||
}
|
||||
div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group button.dt-btn-split-drop:last-child {
|
||||
border: 1px solid #6c757d;
|
||||
}
|
||||
div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group div.dt-btn-split-wrapper {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.dt-button-collection div.btn-group {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
div.dt-button-collection div.btn-group button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
div.dt-button-collection div.btn-group button:last-child {
|
||||
border-top-left-radius: 0px !important;
|
||||
border-bottom-left-radius: 0px !important;
|
||||
}
|
||||
div.dt-button-collection div.btn-group button:first-child {
|
||||
border-top-right-radius: 0px !important;
|
||||
border-bottom-right-radius: 0px !important;
|
||||
}
|
||||
div.dt-button-collection div.btn-group button:last-child:first-child {
|
||||
border-top-left-radius: 4px !important;
|
||||
border-bottom-left-radius: 4px !important;
|
||||
border-top-right-radius: 4px !important;
|
||||
border-bottom-right-radius: 4px !important;
|
||||
}
|
||||
div.dt-button-collection div.btn-group button.dt-btn-split-drop:last-child {
|
||||
border: 1px solid #6c757d;
|
||||
}
|
||||
div.dt-button-collection div.btn-group div.dt-btn-split-wrapper {
|
||||
border: none;
|
||||
}
|
||||
|
||||
span.dt-button-spacer.bar:empty {
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
div.dt-button-collection span.dt-button-spacer {
|
||||
padding-left: 1rem !important;
|
||||
text-align: left;
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -1,428 +0,0 @@
|
||||
@keyframes dtb-spinner {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-o-keyframes dtb-spinner {
|
||||
100% {
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-ms-keyframes dtb-spinner {
|
||||
100% {
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes dtb-spinner {
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-moz-keyframes dtb-spinner {
|
||||
100% {
|
||||
-moz-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
div.dataTables_wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.dt-buttons {
|
||||
position: initial;
|
||||
}
|
||||
|
||||
div.dt-button-info {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 400px;
|
||||
margin-top: -100px;
|
||||
margin-left: -200px;
|
||||
background-color: white;
|
||||
border: 2px solid #111;
|
||||
box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
z-index: 21;
|
||||
}
|
||||
div.dt-button-info h2 {
|
||||
padding: 0.5em;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
div.dt-button-info > div {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
div.dtb-popover-close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 1px solid #eaeaea;
|
||||
background-color: #f9f9f9;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
button.dtb-hide-drop {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
div.dt-button-collection-title {
|
||||
text-align: center;
|
||||
padding: 0.3em 0 0.5em;
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
div.dt-button-collection-title:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.dt-button-spacer {
|
||||
display: inline-block;
|
||||
margin: 0.5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
span.dt-button-spacer.bar {
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.3);
|
||||
vertical-align: middle;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
span.dt-button-spacer.bar:empty {
|
||||
height: 1em;
|
||||
width: 1px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
div.dt-button-collection span.dt-button-spacer {
|
||||
width: 100%;
|
||||
font-size: 0.9em;
|
||||
text-align: center;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
div.dt-button-collection span.dt-button-spacer:empty {
|
||||
height: 0;
|
||||
width: 100%;
|
||||
}
|
||||
div.dt-button-collection span.dt-button-spacer.bar {
|
||||
border-left: none;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
div.dt-button-collection {
|
||||
position: absolute;
|
||||
z-index: 2001;
|
||||
background-color: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
padding: 0.5rem 0;
|
||||
width: 200px;
|
||||
}
|
||||
div.dt-button-collection div.dropdown-menu {
|
||||
position: relative;
|
||||
display: block;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
z-index: 2002;
|
||||
min-width: 100%;
|
||||
}
|
||||
div.dt-button-collection.fixed {
|
||||
position: fixed;
|
||||
display: block;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -75px;
|
||||
border-radius: 5px;
|
||||
background-color: white;
|
||||
}
|
||||
div.dt-button-collection.fixed.two-column {
|
||||
margin-left: -200px;
|
||||
}
|
||||
div.dt-button-collection.fixed.three-column {
|
||||
margin-left: -225px;
|
||||
}
|
||||
div.dt-button-collection.fixed.four-column {
|
||||
margin-left: -300px;
|
||||
}
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -409px;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -308px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 640px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -203px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 460px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -100px;
|
||||
}
|
||||
}
|
||||
div.dt-button-collection.fixed > :last-child {
|
||||
max-height: 100vh;
|
||||
overflow: auto;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child, div.dt-button-collection.three-column > :last-child, div.dt-button-collection.four-column > :last-child {
|
||||
display: block !important;
|
||||
-webkit-column-gap: 8px;
|
||||
-moz-column-gap: 8px;
|
||||
-ms-column-gap: 8px;
|
||||
-o-column-gap: 8px;
|
||||
column-gap: 8px;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child > *, div.dt-button-collection.three-column > :last-child > *, div.dt-button-collection.four-column > :last-child > * {
|
||||
-webkit-column-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
div.dt-button-collection.two-column {
|
||||
width: 400px;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 2;
|
||||
}
|
||||
div.dt-button-collection.three-column {
|
||||
width: 450px;
|
||||
}
|
||||
div.dt-button-collection.three-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 3;
|
||||
}
|
||||
div.dt-button-collection.four-column {
|
||||
width: 600px;
|
||||
}
|
||||
div.dt-button-collection.four-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 4;
|
||||
}
|
||||
div.dt-button-collection .dt-button {
|
||||
border-radius: 0;
|
||||
}
|
||||
div.dt-button-collection.columns {
|
||||
width: auto;
|
||||
}
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 818px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
div.dt-button-collection.columns > :last-child .dt-button {
|
||||
min-width: 200px;
|
||||
flex: 0 1;
|
||||
margin: 0;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 > :last-child, div.dt-button-collection.columns.dtb-b2 > :last-child, div.dt-button-collection.columns.dtb-b1 > :last-child {
|
||||
justify-content: space-between;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 .dt-button {
|
||||
flex: 1 1 32%;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b2 .dt-button {
|
||||
flex: 1 1 48%;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b1 .dt-button {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 612px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 640px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 406px;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 .dt-button {
|
||||
flex: 0 1 32%;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 460px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
div.dt-button-collection.fixed:before, div.dt-button-collection.fixed:after {
|
||||
display: none;
|
||||
}
|
||||
div.dt-button-collection .btn-group {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
div.dt-button-collection .dt-button {
|
||||
min-width: 200px;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
div.dt-button-collection button.dt-btn-split-drop-button {
|
||||
width: 100%;
|
||||
color: #212529;
|
||||
border: none;
|
||||
background-color: white;
|
||||
border-radius: 0px;
|
||||
margin-left: 0px !important;
|
||||
}
|
||||
div.dt-button-collection button.dt-btn-split-drop-button:focus {
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
outline: none;
|
||||
}
|
||||
div.dt-button-collection button.dt-btn-split-drop-button:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
div.dt-button-collection button.dt-btn-split-drop-button:active {
|
||||
background-color: #007bff !important;
|
||||
}
|
||||
|
||||
div.dt-button-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
div.dt-buttons {
|
||||
float: none;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
div.dt-buttons a.btn {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
div.dt-buttons button.btn.processing,
|
||||
div.dt-buttons div.btn.processing,
|
||||
div.dt-buttons a.btn.processing {
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
div.dt-buttons button.btn.processing:after,
|
||||
div.dt-buttons div.btn.processing:after,
|
||||
div.dt-buttons a.btn.processing:after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: -8px 0 0 -8px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
content: " ";
|
||||
border: 2px solid #282828;
|
||||
border-radius: 50%;
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
animation: dtb-spinner 1500ms infinite linear;
|
||||
-o-animation: dtb-spinner 1500ms infinite linear;
|
||||
-ms-animation: dtb-spinner 1500ms infinite linear;
|
||||
-webkit-animation: dtb-spinner 1500ms infinite linear;
|
||||
-moz-animation: dtb-spinner 1500ms infinite linear;
|
||||
}
|
||||
div.dt-buttons div.btn-group {
|
||||
position: initial;
|
||||
}
|
||||
|
||||
div.dt-btn-split-wrapper button.dt-btn-split-drop {
|
||||
border-top-right-radius: 0.25rem !important;
|
||||
border-bottom-right-radius: 0.25rem !important;
|
||||
}
|
||||
div.dt-btn-split-wrapper:active:not(.disabled) button, div.dt-btn-split-wrapper.active:not(.disabled) button {
|
||||
background-color: #5a6268;
|
||||
border-color: #545b62;
|
||||
}
|
||||
div.dt-btn-split-wrapper:active:not(.disabled) button.dt-btn-split-drop, div.dt-btn-split-wrapper.active:not(.disabled) button.dt-btn-split-drop {
|
||||
box-shadow: none;
|
||||
background-color: #6c757d;
|
||||
border-color: #6c757d;
|
||||
}
|
||||
div.dt-btn-split-wrapper:active:not(.disabled) button:hover, div.dt-btn-split-wrapper.active:not(.disabled) button:hover {
|
||||
background-color: #5a6268;
|
||||
border-color: #545b62;
|
||||
}
|
||||
|
||||
div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group:last-child {
|
||||
border-top-left-radius: 0px !important;
|
||||
border-bottom-left-radius: 0px !important;
|
||||
}
|
||||
div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group:first-child {
|
||||
border-top-right-radius: 0px !important;
|
||||
border-bottom-right-radius: 0px !important;
|
||||
}
|
||||
div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group:last-child:first-child {
|
||||
border-top-left-radius: 4px !important;
|
||||
border-bottom-left-radius: 4px !important;
|
||||
border-top-right-radius: 4px !important;
|
||||
border-bottom-right-radius: 4px !important;
|
||||
}
|
||||
div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group button.dt-btn-split-drop:last-child {
|
||||
border: 1px solid #6c757d;
|
||||
}
|
||||
div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group div.dt-btn-split-wrapper {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.dt-button-collection div.btn-group {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
div.dt-button-collection div.btn-group button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
div.dt-button-collection div.btn-group button:last-child {
|
||||
border-top-left-radius: 0px !important;
|
||||
border-bottom-left-radius: 0px !important;
|
||||
}
|
||||
div.dt-button-collection div.btn-group button:first-child {
|
||||
border-top-right-radius: 0px !important;
|
||||
border-bottom-right-radius: 0px !important;
|
||||
}
|
||||
div.dt-button-collection div.btn-group button:last-child:first-child {
|
||||
border-top-left-radius: 4px !important;
|
||||
border-bottom-left-radius: 4px !important;
|
||||
border-top-right-radius: 4px !important;
|
||||
border-bottom-right-radius: 4px !important;
|
||||
}
|
||||
div.dt-button-collection div.btn-group button.dt-btn-split-drop:last-child {
|
||||
border: 1px solid #6c757d;
|
||||
}
|
||||
div.dt-button-collection div.btn-group div.dt-btn-split-wrapper {
|
||||
border: none;
|
||||
}
|
||||
|
||||
span.dt-button-spacer.bar:empty {
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
div.dt-button-collection span.dt-button-spacer {
|
||||
padding-left: 1rem !important;
|
||||
text-align: left;
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -1,425 +0,0 @@
|
||||
@keyframes dtb-spinner {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-o-keyframes dtb-spinner {
|
||||
100% {
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-ms-keyframes dtb-spinner {
|
||||
100% {
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes dtb-spinner {
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-moz-keyframes dtb-spinner {
|
||||
100% {
|
||||
-moz-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
div.dataTables_wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.dt-buttons {
|
||||
position: initial;
|
||||
}
|
||||
|
||||
div.dt-button-info {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 400px;
|
||||
margin-top: -100px;
|
||||
margin-left: -200px;
|
||||
background-color: white;
|
||||
border: 2px solid #111;
|
||||
box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
z-index: 21;
|
||||
}
|
||||
div.dt-button-info h2 {
|
||||
padding: 0.5em;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
div.dt-button-info > div {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
div.dtb-popover-close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 1px solid #eaeaea;
|
||||
background-color: #f9f9f9;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
button.dtb-hide-drop {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
div.dt-button-collection-title {
|
||||
text-align: center;
|
||||
padding: 0.3em 0 0.5em;
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
div.dt-button-collection-title:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.dt-button-spacer {
|
||||
display: inline-block;
|
||||
margin: 0.5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
span.dt-button-spacer.bar {
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.3);
|
||||
vertical-align: middle;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
span.dt-button-spacer.bar:empty {
|
||||
height: 1em;
|
||||
width: 1px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
div.dt-button-collection span.dt-button-spacer {
|
||||
width: 100%;
|
||||
font-size: 0.9em;
|
||||
text-align: center;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
div.dt-button-collection span.dt-button-spacer:empty {
|
||||
height: 0;
|
||||
width: 100%;
|
||||
}
|
||||
div.dt-button-collection span.dt-button-spacer.bar {
|
||||
border-left: none;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
div.dt-button-collection {
|
||||
position: absolute;
|
||||
z-index: 2001;
|
||||
min-width: 200px;
|
||||
background: white;
|
||||
max-width: none;
|
||||
display: block;
|
||||
box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.02);
|
||||
border-radius: 4;
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
div.dt-button-collection div.dropdown-menu {
|
||||
display: block;
|
||||
z-index: 2002;
|
||||
min-width: 100%;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper {
|
||||
width: 100%;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
margin-bottom: 0px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper button {
|
||||
margin-right: 0px;
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
flex-basis: 50px;
|
||||
margin-top: 0px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper button.dt-button {
|
||||
min-width: 30px;
|
||||
margin-left: -1px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: 0;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-top-left-radius: 0px;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
padding: 0px;
|
||||
}
|
||||
div.dt-button-collection.fixed {
|
||||
position: fixed;
|
||||
display: block;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -75px;
|
||||
border-radius: 5px;
|
||||
background-color: white;
|
||||
}
|
||||
div.dt-button-collection.fixed.two-column {
|
||||
margin-left: -200px;
|
||||
}
|
||||
div.dt-button-collection.fixed.three-column {
|
||||
margin-left: -225px;
|
||||
}
|
||||
div.dt-button-collection.fixed.four-column {
|
||||
margin-left: -300px;
|
||||
}
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -409px;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -308px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 640px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -203px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 460px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -100px;
|
||||
}
|
||||
}
|
||||
div.dt-button-collection.fixed > :last-child {
|
||||
max-height: 100vh;
|
||||
overflow: auto;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child, div.dt-button-collection.three-column > :last-child, div.dt-button-collection.four-column > :last-child {
|
||||
display: block !important;
|
||||
-webkit-column-gap: 8px;
|
||||
-moz-column-gap: 8px;
|
||||
-ms-column-gap: 8px;
|
||||
-o-column-gap: 8px;
|
||||
column-gap: 8px;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child > *, div.dt-button-collection.three-column > :last-child > *, div.dt-button-collection.four-column > :last-child > * {
|
||||
-webkit-column-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
div.dt-button-collection.two-column {
|
||||
width: 400px;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 2;
|
||||
}
|
||||
div.dt-button-collection.three-column {
|
||||
width: 450px;
|
||||
}
|
||||
div.dt-button-collection.three-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 3;
|
||||
}
|
||||
div.dt-button-collection.four-column {
|
||||
width: 600px;
|
||||
}
|
||||
div.dt-button-collection.four-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 4;
|
||||
}
|
||||
div.dt-button-collection .dt-button {
|
||||
border-radius: 0;
|
||||
}
|
||||
div.dt-button-collection.columns {
|
||||
width: auto;
|
||||
}
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 818px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
div.dt-button-collection.columns > :last-child .dt-button {
|
||||
min-width: 200px;
|
||||
flex: 0 1;
|
||||
margin: 0;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 > :last-child, div.dt-button-collection.columns.dtb-b2 > :last-child, div.dt-button-collection.columns.dtb-b1 > :last-child {
|
||||
justify-content: space-between;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 .dt-button {
|
||||
flex: 1 1 32%;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b2 .dt-button {
|
||||
flex: 1 1 48%;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b1 .dt-button {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 612px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 640px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 406px;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 .dt-button {
|
||||
flex: 0 1 32%;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 460px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
div.dt-button-collection .dropdown-content {
|
||||
box-shadow: none;
|
||||
padding-top: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
div.dt-button-collection.fixed:before, div.dt-button-collection.fixed:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.dt-button-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
div.dt-buttons {
|
||||
float: none;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
div.dt-buttons a.btn {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
div.dt-buttons button.btn.processing,
|
||||
div.dt-buttons div.btn.processing,
|
||||
div.dt-buttons a.btn.processing {
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
div.dt-buttons button.btn.processing:after,
|
||||
div.dt-buttons div.btn.processing:after,
|
||||
div.dt-buttons a.btn.processing:after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: -8px 0 0 -8px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
content: " ";
|
||||
border: 2px solid #282828;
|
||||
border-radius: 50%;
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
animation: dtb-spinner 1500ms infinite linear;
|
||||
-o-animation: dtb-spinner 1500ms infinite linear;
|
||||
-ms-animation: dtb-spinner 1500ms infinite linear;
|
||||
-webkit-animation: dtb-spinner 1500ms infinite linear;
|
||||
-moz-animation: dtb-spinner 1500ms infinite linear;
|
||||
}
|
||||
div.dt-buttons button.button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
div.dt-buttons button.button:first-child {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
span.dt-down-arrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.dt-button-spacer {
|
||||
display: inline-flex;
|
||||
margin: 0.5em;
|
||||
white-space: nowrap;
|
||||
align-items: center;
|
||||
font-size: 1rem;
|
||||
}
|
||||
span.dt-button-spacer.bar:empty {
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
div.dt-button-collection span.dt-button-spacer {
|
||||
text-align: left;
|
||||
font-size: 0.875rem;
|
||||
padding-left: 1rem !important;
|
||||
}
|
||||
|
||||
div.dt-btn-split-wrapper {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
margin-bottom: 0px;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
div.dt-btn-split-wrapper button {
|
||||
margin-right: 0px;
|
||||
display: inline-block;
|
||||
margin-top: 0px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
div.dt-btn-split-wrapper button.dt-button {
|
||||
min-width: 30px;
|
||||
margin-left: -1px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-top-left-radius: 0px;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
padding: 0px;
|
||||
}
|
||||
div.dt-btn-split-wrapper:active:not(.disabled) button, div.dt-btn-split-wrapper.active:not(.disabled) button, div.dt-btn-split-wrapper.is-active:not(.disabled) button {
|
||||
background-color: #eee;
|
||||
border-color: transparent;
|
||||
}
|
||||
div.dt-btn-split-wrapper:active:not(.disabled) button.dt-button, div.dt-btn-split-wrapper.active:not(.disabled) button.dt-button, div.dt-btn-split-wrapper.is-active:not(.disabled) button.dt-button {
|
||||
box-shadow: none;
|
||||
background-color: whitesmoke;
|
||||
border-color: transparent;
|
||||
}
|
||||
div.dt-btn-split-wrapper:active:not(.disabled) button:hover, div.dt-btn-split-wrapper.active:not(.disabled) button:hover, div.dt-btn-split-wrapper.is-active:not(.disabled) button:hover {
|
||||
background-color: #eee;
|
||||
border-color: transparent;
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -1,631 +0,0 @@
|
||||
@keyframes dtb-spinner {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-o-keyframes dtb-spinner {
|
||||
100% {
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-ms-keyframes dtb-spinner {
|
||||
100% {
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes dtb-spinner {
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-moz-keyframes dtb-spinner {
|
||||
100% {
|
||||
-moz-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
div.dataTables_wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.dt-buttons {
|
||||
position: initial;
|
||||
}
|
||||
|
||||
div.dt-button-info {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 400px;
|
||||
margin-top: -100px;
|
||||
margin-left: -200px;
|
||||
background-color: white;
|
||||
border: 2px solid #111;
|
||||
box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
z-index: 21;
|
||||
}
|
||||
div.dt-button-info h2 {
|
||||
padding: 0.5em;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
div.dt-button-info > div {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
div.dtb-popover-close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 1px solid #eaeaea;
|
||||
background-color: #f9f9f9;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
button.dtb-hide-drop {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
div.dt-button-collection-title {
|
||||
text-align: center;
|
||||
padding: 0.3em 0 0.5em;
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
div.dt-button-collection-title:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.dt-button-spacer {
|
||||
display: inline-block;
|
||||
margin: 0.5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
span.dt-button-spacer.bar {
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.3);
|
||||
vertical-align: middle;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
span.dt-button-spacer.bar:empty {
|
||||
height: 1em;
|
||||
width: 1px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
div.dt-button-collection span.dt-button-spacer {
|
||||
width: 100%;
|
||||
font-size: 0.9em;
|
||||
text-align: center;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
div.dt-button-collection span.dt-button-spacer:empty {
|
||||
height: 0;
|
||||
width: 100%;
|
||||
}
|
||||
div.dt-button-collection span.dt-button-spacer.bar {
|
||||
border-left: none;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
button.dt-button,
|
||||
div.dt-button,
|
||||
a.dt-button,
|
||||
input.dt-button {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
margin-left: 0.167em;
|
||||
margin-right: 0.167em;
|
||||
margin-bottom: 0.333em;
|
||||
padding: 0.5em 1em;
|
||||
border: 1px solid rgba(0, 0, 0, 0.3);
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
font-size: 0.88em;
|
||||
line-height: 1.6em;
|
||||
color: black;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
/* Fallback */
|
||||
background: -webkit-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* Chrome 10+, Saf5.1+, iOS 5+ */
|
||||
background: -moz-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* FF3.6 */
|
||||
background: -ms-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* IE10 */
|
||||
background: -o-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* Opera 11.10+ */
|
||||
background: linear-gradient(to bottom, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(230, 230, 230, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)");
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
button.dt-button:first-child,
|
||||
div.dt-button:first-child,
|
||||
a.dt-button:first-child,
|
||||
input.dt-button:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
button.dt-button.disabled,
|
||||
div.dt-button.disabled,
|
||||
a.dt-button.disabled,
|
||||
input.dt-button.disabled {
|
||||
cursor: default;
|
||||
opacity: 0.4;
|
||||
}
|
||||
button.dt-button:active:not(.disabled), button.dt-button.active:not(.disabled),
|
||||
div.dt-button:active:not(.disabled),
|
||||
div.dt-button.active:not(.disabled),
|
||||
a.dt-button:active:not(.disabled),
|
||||
a.dt-button.active:not(.disabled),
|
||||
input.dt-button:active:not(.disabled),
|
||||
input.dt-button.active:not(.disabled) {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
/* Fallback */
|
||||
background: -webkit-linear-gradient(top, rgba(179, 179, 179, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* Chrome 10+, Saf5.1+, iOS 5+ */
|
||||
background: -moz-linear-gradient(top, rgba(179, 179, 179, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* FF3.6 */
|
||||
background: -ms-linear-gradient(top, rgba(179, 179, 179, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* IE10 */
|
||||
background: -o-linear-gradient(top, rgba(179, 179, 179, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* Opera 11.10+ */
|
||||
background: linear-gradient(to bottom, rgba(179, 179, 179, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(179, 179, 179, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)");
|
||||
box-shadow: inset 1px 1px 3px #999999;
|
||||
}
|
||||
button.dt-button:active:not(.disabled):hover:not(.disabled), button.dt-button.active:not(.disabled):hover:not(.disabled),
|
||||
div.dt-button:active:not(.disabled):hover:not(.disabled),
|
||||
div.dt-button.active:not(.disabled):hover:not(.disabled),
|
||||
a.dt-button:active:not(.disabled):hover:not(.disabled),
|
||||
a.dt-button.active:not(.disabled):hover:not(.disabled),
|
||||
input.dt-button:active:not(.disabled):hover:not(.disabled),
|
||||
input.dt-button.active:not(.disabled):hover:not(.disabled) {
|
||||
box-shadow: inset 1px 1px 3px #999999;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
/* Fallback */
|
||||
background: -webkit-linear-gradient(top, rgba(128, 128, 128, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* Chrome 10+, Saf5.1+, iOS 5+ */
|
||||
background: -moz-linear-gradient(top, rgba(128, 128, 128, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* FF3.6 */
|
||||
background: -ms-linear-gradient(top, rgba(128, 128, 128, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* IE10 */
|
||||
background: -o-linear-gradient(top, rgba(128, 128, 128, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* Opera 11.10+ */
|
||||
background: linear-gradient(to bottom, rgba(128, 128, 128, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(128, 128, 128, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)");
|
||||
}
|
||||
button.dt-button:hover,
|
||||
div.dt-button:hover,
|
||||
a.dt-button:hover,
|
||||
input.dt-button:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
button.dt-button:hover:not(.disabled),
|
||||
div.dt-button:hover:not(.disabled),
|
||||
a.dt-button:hover:not(.disabled),
|
||||
input.dt-button:hover:not(.disabled) {
|
||||
border: 1px solid #666;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
/* Fallback */
|
||||
background: -webkit-linear-gradient(top, rgba(153, 153, 153, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* Chrome 10+, Saf5.1+, iOS 5+ */
|
||||
background: -moz-linear-gradient(top, rgba(153, 153, 153, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* FF3.6 */
|
||||
background: -ms-linear-gradient(top, rgba(153, 153, 153, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* IE10 */
|
||||
background: -o-linear-gradient(top, rgba(153, 153, 153, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
/* Opera 11.10+ */
|
||||
background: linear-gradient(to bottom, rgba(153, 153, 153, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="rgba(153, 153, 153, 0.1)", EndColorStr="rgba(0, 0, 0, 0.1)");
|
||||
}
|
||||
button.dt-button:focus:not(.disabled),
|
||||
div.dt-button:focus:not(.disabled),
|
||||
a.dt-button:focus:not(.disabled),
|
||||
input.dt-button:focus:not(.disabled) {
|
||||
border: 1px solid #426c9e;
|
||||
text-shadow: 0 1px 0 #c4def1;
|
||||
outline: none;
|
||||
background-color: #79ace9;
|
||||
/* Fallback */
|
||||
background: -webkit-linear-gradient(top, #d1e2f7 0%, #79ace9 100%);
|
||||
/* Chrome 10+, Saf5.1+, iOS 5+ */
|
||||
background: -moz-linear-gradient(top, #d1e2f7 0%, #79ace9 100%);
|
||||
/* FF3.6 */
|
||||
background: -ms-linear-gradient(top, #d1e2f7 0%, #79ace9 100%);
|
||||
/* IE10 */
|
||||
background: -o-linear-gradient(top, #d1e2f7 0%, #79ace9 100%);
|
||||
/* Opera 11.10+ */
|
||||
background: linear-gradient(to bottom, #d1e2f7 0%, #79ace9 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="#d1e2f7", EndColorStr="#79ace9");
|
||||
}
|
||||
button.dt-button span.dt-down-arrow,
|
||||
div.dt-button span.dt-down-arrow,
|
||||
a.dt-button span.dt-down-arrow,
|
||||
input.dt-button span.dt-down-arrow {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
color: rgba(70, 70, 70, 0.75);
|
||||
font-size: 8px;
|
||||
padding-left: 10px;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
.dt-button embed {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
div.dt-buttons {
|
||||
float: left;
|
||||
}
|
||||
div.dt-buttons.buttons-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.dataTables_layout_cell div.dt-buttons {
|
||||
float: none;
|
||||
}
|
||||
div.dataTables_layout_cell div.dt-buttons.buttons-right {
|
||||
float: none;
|
||||
}
|
||||
|
||||
div.dt-btn-split-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
div.dt-button-collection {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 200px;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
padding: 4px 4px 2px 4px;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid rgba(0, 0, 0, 0.4);
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
z-index: 2002;
|
||||
border-radius: 5px;
|
||||
box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.3);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
div.dt-button-collection button.dt-button,
|
||||
div.dt-button-collection div.dt-button,
|
||||
div.dt-button-collection a.dt-button {
|
||||
position: relative;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
display: block;
|
||||
float: none;
|
||||
margin: 4px 0 2px 0;
|
||||
}
|
||||
div.dt-button-collection button.dt-button:active:not(.disabled), div.dt-button-collection button.dt-button.active:not(.disabled),
|
||||
div.dt-button-collection div.dt-button:active:not(.disabled),
|
||||
div.dt-button-collection div.dt-button.active:not(.disabled),
|
||||
div.dt-button-collection a.dt-button:active:not(.disabled),
|
||||
div.dt-button-collection a.dt-button.active:not(.disabled) {
|
||||
background-color: #dadada;
|
||||
/* Fallback */
|
||||
background: -webkit-linear-gradient(top, #f0f0f0 0%, #dadada 100%);
|
||||
/* Chrome 10+, Saf5.1+, iOS 5+ */
|
||||
background: -moz-linear-gradient(top, #f0f0f0 0%, #dadada 100%);
|
||||
/* FF3.6 */
|
||||
background: -ms-linear-gradient(top, #f0f0f0 0%, #dadada 100%);
|
||||
/* IE10 */
|
||||
background: -o-linear-gradient(top, #f0f0f0 0%, #dadada 100%);
|
||||
/* Opera 11.10+ */
|
||||
background: linear-gradient(to bottom, #f0f0f0 0%, #dadada 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="#f0f0f0", EndColorStr="#dadada");
|
||||
box-shadow: inset 1px 1px 3px #666;
|
||||
}
|
||||
div.dt-button-collection button.dt-button:first-child,
|
||||
div.dt-button-collection div.dt-button:first-child,
|
||||
div.dt-button-collection a.dt-button:first-child {
|
||||
margin-top: 0;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
div.dt-button-collection button.dt-button:last-child,
|
||||
div.dt-button-collection div.dt-button:last-child,
|
||||
div.dt-button-collection a.dt-button:last-child {
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-content: flex-start;
|
||||
align-items: stretch;
|
||||
margin: 4px 0 2px 0;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper button.dt-button {
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
flex-basis: 50px;
|
||||
border-radius: 0;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper button.dt-btn-split-drop {
|
||||
min-width: 20px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: 0;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper:first-child button.dt-button {
|
||||
border-top-left-radius: 3px;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper:first-child button.dt-btn-split-drop {
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper:last-child button.dt-button {
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper:last-child button.dt-btn-split-drop {
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper:active:not(.disabled) button.dt-button, div.dt-button-collection div.dt-btn-split-wrapper.active:not(.disabled) button.dt-button {
|
||||
background-color: #dadada;
|
||||
/* Fallback */
|
||||
background: -webkit-linear-gradient(top, #f0f0f0 0%, #dadada 100%);
|
||||
/* Chrome 10+, Saf5.1+, iOS 5+ */
|
||||
background: -moz-linear-gradient(top, #f0f0f0 0%, #dadada 100%);
|
||||
/* FF3.6 */
|
||||
background: -ms-linear-gradient(top, #f0f0f0 0%, #dadada 100%);
|
||||
/* IE10 */
|
||||
background: -o-linear-gradient(top, #f0f0f0 0%, #dadada 100%);
|
||||
/* Opera 11.10+ */
|
||||
background: linear-gradient(to bottom, #f0f0f0 0%, #dadada 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr="#f0f0f0", EndColorStr="#dadada");
|
||||
box-shadow: inset 0px 0px 4px #666;
|
||||
}
|
||||
div.dt-button-collection div.dt-btn-split-wrapper:active:not(.disabled) button.dt-btn-split-drop, div.dt-button-collection div.dt-btn-split-wrapper.active:not(.disabled) button.dt-btn-split-drop {
|
||||
box-shadow: none;
|
||||
}
|
||||
div.dt-button-collection.fixed .dt-button:first-child {
|
||||
margin-top: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
div.dt-button-collection.fixed .dt-button:last-child {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
div.dt-button-collection.fixed {
|
||||
position: fixed;
|
||||
display: block;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -75px;
|
||||
border-radius: 5px;
|
||||
background-color: white;
|
||||
}
|
||||
div.dt-button-collection.fixed.two-column {
|
||||
margin-left: -200px;
|
||||
}
|
||||
div.dt-button-collection.fixed.three-column {
|
||||
margin-left: -225px;
|
||||
}
|
||||
div.dt-button-collection.fixed.four-column {
|
||||
margin-left: -300px;
|
||||
}
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -409px;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -308px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 640px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -203px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 460px) {
|
||||
div.dt-button-collection.fixed.columns {
|
||||
margin-left: -100px;
|
||||
}
|
||||
}
|
||||
div.dt-button-collection.fixed > :last-child {
|
||||
max-height: 100vh;
|
||||
overflow: auto;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child, div.dt-button-collection.three-column > :last-child, div.dt-button-collection.four-column > :last-child {
|
||||
display: block !important;
|
||||
-webkit-column-gap: 8px;
|
||||
-moz-column-gap: 8px;
|
||||
-ms-column-gap: 8px;
|
||||
-o-column-gap: 8px;
|
||||
column-gap: 8px;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child > *, div.dt-button-collection.three-column > :last-child > *, div.dt-button-collection.four-column > :last-child > * {
|
||||
-webkit-column-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
div.dt-button-collection.two-column {
|
||||
width: 400px;
|
||||
}
|
||||
div.dt-button-collection.two-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 2;
|
||||
}
|
||||
div.dt-button-collection.three-column {
|
||||
width: 450px;
|
||||
}
|
||||
div.dt-button-collection.three-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 3;
|
||||
}
|
||||
div.dt-button-collection.four-column {
|
||||
width: 600px;
|
||||
}
|
||||
div.dt-button-collection.four-column > :last-child {
|
||||
padding-bottom: 1px;
|
||||
column-count: 4;
|
||||
}
|
||||
div.dt-button-collection .dt-button {
|
||||
border-radius: 0;
|
||||
}
|
||||
div.dt-button-collection.columns {
|
||||
width: auto;
|
||||
}
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 818px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
div.dt-button-collection.columns > :last-child .dt-button {
|
||||
min-width: 200px;
|
||||
flex: 0 1;
|
||||
margin: 0;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 > :last-child, div.dt-button-collection.columns.dtb-b2 > :last-child, div.dt-button-collection.columns.dtb-b1 > :last-child {
|
||||
justify-content: space-between;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 .dt-button {
|
||||
flex: 1 1 32%;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b2 .dt-button {
|
||||
flex: 1 1 48%;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b1 .dt-button {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 612px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 640px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 406px;
|
||||
}
|
||||
div.dt-button-collection.columns.dtb-b3 .dt-button {
|
||||
flex: 0 1 32%;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 460px) {
|
||||
div.dt-button-collection.columns > :last-child {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
div.dt-button-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
/* Fallback */
|
||||
background: -ms-radial-gradient(center, ellipse farthest-corner, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%);
|
||||
/* IE10 Consumer Preview */
|
||||
background: -moz-radial-gradient(center, ellipse farthest-corner, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%);
|
||||
/* Firefox */
|
||||
background: -o-radial-gradient(center, ellipse farthest-corner, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%);
|
||||
/* Opera */
|
||||
background: -webkit-gradient(radial, center center, 0, center center, 497, color-stop(0, rgba(0, 0, 0, 0.3)), color-stop(1, rgba(0, 0, 0, 0.7)));
|
||||
/* Webkit (Safari/Chrome 10) */
|
||||
background: -webkit-radial-gradient(center, ellipse farthest-corner, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%);
|
||||
/* Webkit (Chrome 11+) */
|
||||
background: radial-gradient(ellipse farthest-corner at center, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%);
|
||||
/* W3C Markup, IE10 Release Preview */
|
||||
z-index: 2001;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
div.dt-buttons {
|
||||
float: none !important;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
button.dt-button.processing,
|
||||
div.dt-button.processing,
|
||||
a.dt-button.processing {
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
button.dt-button.processing:after,
|
||||
div.dt-button.processing:after,
|
||||
a.dt-button.processing:after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: -8px 0 0 -8px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
content: " ";
|
||||
border: 2px solid #282828;
|
||||
border-radius: 50%;
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
animation: dtb-spinner 1500ms infinite linear;
|
||||
-o-animation: dtb-spinner 1500ms infinite linear;
|
||||
-ms-animation: dtb-spinner 1500ms infinite linear;
|
||||
-webkit-animation: dtb-spinner 1500ms infinite linear;
|
||||
-moz-animation: dtb-spinner 1500ms infinite linear;
|
||||
}
|
||||
|
||||
button.dt-btn-split-drop {
|
||||
margin-left: calc(-1px - 0.333em);
|
||||
padding-bottom: calc(0.5em - 1px);
|
||||
border-radius: 0px 1px 1px 0px;
|
||||
color: rgba(70, 70, 70, 0.9);
|
||||
border-left: none;
|
||||
}
|
||||
button.dt-btn-split-drop span.dt-btn-split-drop-arrow {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
left: -2px;
|
||||
font-size: 8px;
|
||||
}
|
||||
button.dt-btn-split-drop:hover {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
button.buttons-split {
|
||||
border-right: 1px solid rgba(70, 70, 70, 0);
|
||||
border-radius: 1px 0px 0px 1px;
|
||||
}
|
||||
|
||||
button.dt-btn-split-drop-button {
|
||||
background-color: white;
|
||||
}
|
||||
button.dt-btn-split-drop-button:hover {
|
||||
background-color: white;
|
||||
}
|
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user