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(
|
||||
data = render_template(
|
||||
"sco_page.j2",
|
||||
no_sidebar=True,
|
||||
title=f"Moyennes archivées le {date}",
|
||||
content="\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); }
|
||||
</style>""",
|
||||
table_html,
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
),
|
||||
)
|
||||
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",
|
||||
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)
|
||||
|
||||
def formsemestre_editwithmodules(formsemestre_id):
|
||||
"""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",
|
||||
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"],
|
||||
bodyOnLoad="init_tf_form('')",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def formsemestre_editwithmodules(formsemestre_id: int):
|
||||
"""Page modification semestre"""
|
||||
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>")
|
||||
else:
|
||||
message = ["""<h3>Aucune modification</h3>"""]
|
||||
sco_cache.invalidate_formsemestre(
|
||||
formsemestre_id=formsemestre_id
|
||||
) # > modif coef UE cap (modifs notes de _certains_ etudiants)
|
||||
else:
|
||||
message = ["""<h3>Aucune modification</h3>"""]
|
||||
|
||||
return f"""{html_sco_header.html_sem_header("Coefficients des UE du semestre")}
|
||||
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",
|
||||
<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",
|
||||
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(
|
||||
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,
|
||||
)
|
||||
}
|
||||
</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() }
|
||||
|
||||
|
||||
# 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>"""
|
||||
@ -748,12 +776,17 @@ def groups_table(
|
||||
tab.html(),
|
||||
f"""
|
||||
<ul>
|
||||
<li><a class="stdlink" href="{tab.base_url}&fmt=xls">Table Excel</a>
|
||||
<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électionné)</a>
|
||||
<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="export_groups_as_moodle_csv?formsemestre_id={groups_infos.formsemestre_id}">
|
||||
<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>""",
|
||||
@ -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(
|
||||
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,
|
||||
)
|
||||
+ "?"
|
||||
+ 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)
|
||||
@ -137,7 +130,8 @@ def moduleimpl_inscriptions_edit(
|
||||
}
|
||||
} else {
|
||||
for (var i =nb_inputs_to_skip; i < elems.length; i++) {
|
||||
var cells = elems[i].parentNode.parentNode.getElementsByTagName("td")[partitionIdx].childNodes;
|
||||
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}>"""
|
||||
)
|
||||
H.append(
|
||||
f"""<a class="discretelink etudinfo" href="{
|
||||
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>"""
|
||||
}" id="{etud['etudid']}">{etud['nomprenom']}</a>
|
||||
</input>
|
||||
</td>
|
||||
"""
|
||||
)
|
||||
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 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"""
|
||||
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="{
|
||||
url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=moduleimpl_id)
|
||||
}">Continuer</a></p>
|
||||
"""
|
||||
+ html_sco_header.sco_footer()
|
||||
<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,8 +293,9 @@ 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}">
|
||||
@ -308,8 +309,8 @@ def do_evaluation_set_missing(
|
||||
)}">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,10 +540,13 @@ 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 render_template(
|
||||
"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)
|
||||
else:
|
||||
|
||||
def callback(etud: Identite, data, filename):
|
||||
return sco_photos.store_photo(etud, data, filename)
|
||||
@ -567,10 +569,9 @@ def photos_import_files_form(group_ids=()):
|
||||
unmatched_files=unmatched_files,
|
||||
stored_etud_filename=stored_etud_filename,
|
||||
next_page=url_for(
|
||||
"scolar.groups_view",
|
||||
"scolar.groups_photos",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=groups_infos.formsemestre_id,
|
||||
curtab="tab-photos",
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -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…
x
Reference in New Issue
Block a user