API: etudiant/create (WIP), refactoring.
This commit is contained in:
parent
532fb3e701
commit
2377918b54
@ -18,6 +18,7 @@ from sqlalchemy import desc, func, or_
|
||||
from sqlalchemy.dialects.postgresql import VARCHAR
|
||||
|
||||
import app
|
||||
from app import db
|
||||
from app.api import api_bp as bp, api_web_bp
|
||||
from app.api import tools
|
||||
from app.but import bulletin_but_court
|
||||
@ -28,10 +29,12 @@ from app.models import (
|
||||
FormSemestreInscription,
|
||||
FormSemestre,
|
||||
Identite,
|
||||
ScolarNews,
|
||||
)
|
||||
from app.scodoc import sco_bulletins
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc.sco_bulletins import do_formsemestre_bulletinetud
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_utils import json_error, suppress_accents
|
||||
|
||||
@ -475,3 +478,48 @@ def etudiant_groups(formsemestre_id: int, etudid: int = None):
|
||||
data = sco_groups.get_etud_groups(etud.id, formsemestre.id)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@bp.route("/etudiant/create", methods=["POST"], defaults={"force": False})
|
||||
@bp.route("/etudiant/create/force", methods=["POST"], defaults={"force": True})
|
||||
@scodoc
|
||||
@permission_required(Permission.EtudInscrit)
|
||||
@as_json
|
||||
def etudiant_create(force=False):
|
||||
"""Création d'un nouvel étudiant
|
||||
Si force, crée même si homonymie détectée.
|
||||
L'étudiant créé n'est pas inscrit à un semestre.
|
||||
Champs requis: nom, prenom (sauf si config sans prénom), dept (string:acronyme)
|
||||
"""
|
||||
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||
dept = args.get("dept", None)
|
||||
if not dept:
|
||||
return scu.json_error(400, "dept requis")
|
||||
dept_o = Departement.query.filter_by(acronym=dept).first()
|
||||
if not dept_o:
|
||||
return scu.json_error(400, "dept invalide")
|
||||
app.set_sco_dept(dept)
|
||||
args["dept_id"] = dept_o.id
|
||||
# vérifie que le département de création est bien autorisé
|
||||
if not current_user.has_permission(Permission.EtudInscrit, dept):
|
||||
return json_error(403, "departement non autorisé")
|
||||
nom = args.get("nom", None)
|
||||
prenom = args.get("prenom", None)
|
||||
ok, homonyms = sco_etud.check_nom_prenom_homonyms(nom=nom, prenom=prenom)
|
||||
if not ok:
|
||||
return scu.json_error(400, "nom ou prénom invalide")
|
||||
if len(homonyms) > 0 and not force:
|
||||
return scu.json_error(
|
||||
400, f"{len(homonyms)} homonymes détectés. Vous pouvez utiliser /force."
|
||||
)
|
||||
etud = Identite.create_etud(**args)
|
||||
# Poste une nouvelle dans le département concerné:
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_INSCR,
|
||||
text=f"Nouvel étudiant {etud.html_link_fiche()}",
|
||||
url=etud.url_fiche(),
|
||||
max_frequency=0,
|
||||
dept_id=dept_o.id,
|
||||
)
|
||||
db.session.commit()
|
||||
return etud.to_dict_short()
|
||||
|
@ -27,7 +27,6 @@ from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_etud # a deplacer dans scu
|
||||
|
||||
VALID_LOGIN_EXP = re.compile(r"^[a-zA-Z0-9@\\\-_\.]+$")
|
||||
|
||||
@ -462,8 +461,8 @@ class User(UserMixin, db.Model, ScoDocModel):
|
||||
"""nomplogin est le nom en majuscules suivi du prénom et du login
|
||||
e.g. Dupont Pierre (dupont)
|
||||
"""
|
||||
nom = sco_etud.format_nom(self.nom) if self.nom else self.user_name.upper()
|
||||
return f"{nom} {sco_etud.format_prenom(self.prenom)} ({self.user_name})"
|
||||
nom = scu.format_nom(self.nom) if self.nom else self.user_name.upper()
|
||||
return f"{nom} {scu.format_prenom(self.prenom)} ({self.user_name})"
|
||||
|
||||
@staticmethod
|
||||
def get_user_id_from_nomplogin(nomplogin: str) -> Optional[int]:
|
||||
@ -481,29 +480,29 @@ class User(UserMixin, db.Model, ScoDocModel):
|
||||
def get_nom_fmt(self):
|
||||
"""Nom formaté: "Martin" """
|
||||
if self.nom:
|
||||
return sco_etud.format_nom(self.nom, uppercase=False)
|
||||
return scu.format_nom(self.nom, uppercase=False)
|
||||
else:
|
||||
return self.user_name
|
||||
|
||||
def get_prenom_fmt(self):
|
||||
"""Prénom formaté (minuscule capitalisées)"""
|
||||
return sco_etud.format_prenom(self.prenom)
|
||||
return scu.format_prenom(self.prenom)
|
||||
|
||||
def get_nomprenom(self):
|
||||
"""Nom capitalisé suivi de l'initiale du prénom:
|
||||
Viennet E.
|
||||
"""
|
||||
prenom_abbrv = scu.abbrev_prenom(sco_etud.format_prenom(self.prenom))
|
||||
prenom_abbrv = scu.abbrev_prenom(scu.format_prenom(self.prenom))
|
||||
return (self.get_nom_fmt() + " " + prenom_abbrv).strip()
|
||||
|
||||
def get_prenomnom(self):
|
||||
"""L'initiale du prénom suivie du nom: "J.-C. Dupont" """
|
||||
prenom_abbrv = scu.abbrev_prenom(sco_etud.format_prenom(self.prenom))
|
||||
prenom_abbrv = scu.abbrev_prenom(scu.format_prenom(self.prenom))
|
||||
return (prenom_abbrv + " " + self.get_nom_fmt()).strip()
|
||||
|
||||
def get_nomcomplet(self):
|
||||
"Prénom et nom complets"
|
||||
return sco_etud.format_prenom(self.prenom) + " " + self.get_nom_fmt()
|
||||
return scu.format_prenom(self.prenom) + " " + self.get_nom_fmt()
|
||||
|
||||
# nomnoacc était le nom en minuscules sans accents (inutile)
|
||||
|
||||
|
@ -6,6 +6,7 @@ from flask import Blueprint
|
||||
from app.scodoc import sco_etud
|
||||
from app.auth.models import User
|
||||
from app.models import Departement
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
bp = Blueprint("entreprises", __name__)
|
||||
|
||||
@ -15,12 +16,12 @@ SIRET_PROVISOIRE_START = "xx"
|
||||
|
||||
@bp.app_template_filter()
|
||||
def format_prenom(s):
|
||||
return sco_etud.format_prenom(s)
|
||||
return scu.format_prenom(s)
|
||||
|
||||
|
||||
@bp.app_template_filter()
|
||||
def format_nom(s):
|
||||
return sco_etud.format_nom(s)
|
||||
return scu.format_nom(s)
|
||||
|
||||
|
||||
@bp.app_template_filter()
|
||||
|
@ -1580,8 +1580,8 @@ def edit_stage_apprentissage(entreprise_id, stage_apprentissage_id):
|
||||
)
|
||||
)
|
||||
elif request.method == "GET":
|
||||
form.etudiant.data = f"""{sco_etud.format_nom(etudiant.nom)} {
|
||||
sco_etud.format_prenom(etudiant.prenom)}"""
|
||||
form.etudiant.data = f"""{scu.format_nom(etudiant.nom)} {
|
||||
scu.format_prenom(etudiant.prenom)}"""
|
||||
form.etudid.data = etudiant.id
|
||||
form.type_offre.data = stage_apprentissage.type_offre
|
||||
form.date_debut.data = stage_apprentissage.date_debut
|
||||
@ -1699,7 +1699,7 @@ def json_etudiants():
|
||||
list = []
|
||||
for etudiant in etudiants:
|
||||
content = {}
|
||||
value = f"{sco_etud.format_nom(etudiant.nom)} {sco_etud.format_prenom(etudiant.prenom)}"
|
||||
value = f"{scu.format_nom(etudiant.nom)} {scu.format_prenom(etudiant.prenom)}"
|
||||
if etudiant.inscription_courante() is not None:
|
||||
content = {
|
||||
"id": f"{etudiant.id}",
|
||||
|
@ -71,7 +71,7 @@ class ScoDocModel:
|
||||
|
||||
@classmethod
|
||||
def convert_dict_fields(cls, args: dict) -> dict:
|
||||
"""Convert fields in the given dict. No side effect.
|
||||
"""Convert fields from the given dict to model's attributes values. No side effect.
|
||||
By default, do nothing, but is overloaded by some subclasses.
|
||||
args: dict with args in application.
|
||||
returns: dict to store in model's db.
|
||||
@ -133,7 +133,6 @@ from app.models.notes import (
|
||||
NotesNotesLog,
|
||||
)
|
||||
from app.models.validations import (
|
||||
ScolarEvent,
|
||||
ScolarFormSemestreValidation,
|
||||
ScolarAutorisationInscription,
|
||||
)
|
||||
@ -152,3 +151,4 @@ from app.models.but_validations import ApcValidationAnnee, ApcValidationRCUE
|
||||
from app.models.config import ScoDocSiteConfig
|
||||
|
||||
from app.models.assiduites import Assiduite, Justificatif
|
||||
from app.models.scolar_event import ScolarEvent
|
||||
|
@ -15,7 +15,7 @@ from sqlalchemy import desc, text
|
||||
|
||||
from app import db, log
|
||||
from app import models
|
||||
|
||||
from app.models.scolar_event import ScolarEvent
|
||||
from app.scodoc import notesdb as ndb
|
||||
from app.scodoc.sco_bac import Baccalaureat
|
||||
from app.scodoc.sco_exceptions import ScoInvalidParamError, ScoValueError
|
||||
@ -170,9 +170,13 @@ class Identite(db.Model, models.ScoDocModel):
|
||||
|
||||
def html_link_fiche(self) -> str:
|
||||
"lien vers la fiche"
|
||||
return f"""<a class="stdlink" href="{
|
||||
url_for("scolar.ficheEtud", scodoc_dept=self.departement.acronym, etudid=self.id)
|
||||
}">{self.nomprenom}</a>"""
|
||||
return f"""<a class="stdlink" href="{self.url_fiche()}">{self.nomprenom}</a>"""
|
||||
|
||||
def url_fiche(self) -> str:
|
||||
"url de la fiche étudiant"
|
||||
return url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=self.departement.acronym, etudid=self.id
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_request(cls, etudid=None, code_nip=None) -> "Identite":
|
||||
@ -211,6 +215,10 @@ class Identite(db.Model, models.ScoDocModel):
|
||||
etud.admission = Admission()
|
||||
etud.adresses.append(Adresse(typeadresse="domicile"))
|
||||
db.session.flush()
|
||||
|
||||
event = ScolarEvent(etud=etud, event_type="CREATION")
|
||||
db.session.add(event)
|
||||
log(f"Identite.create {etud}")
|
||||
return etud
|
||||
|
||||
@property
|
||||
|
@ -133,7 +133,7 @@ class ScolarNews(db.Model):
|
||||
return query.order_by(cls.date.desc()).limit(n).all()
|
||||
|
||||
@classmethod
|
||||
def add(cls, typ, obj=None, text="", url=None, max_frequency=600):
|
||||
def add(cls, typ, obj=None, text="", url=None, max_frequency=600, dept_id=None):
|
||||
"""Enregistre une nouvelle
|
||||
Si max_frequency, ne génère pas 2 nouvelles "identiques"
|
||||
à moins de max_frequency secondes d'intervalle (10 minutes par défaut).
|
||||
@ -141,10 +141,11 @@ class ScolarNews(db.Model):
|
||||
même (obj, typ, user).
|
||||
La nouvelle enregistrée est aussi envoyée par mail.
|
||||
"""
|
||||
dept_id = dept_id if dept_id is not None else g.scodoc_dept_id
|
||||
if max_frequency:
|
||||
last_news = (
|
||||
cls.query.filter_by(
|
||||
dept_id=g.scodoc_dept_id,
|
||||
dept_id=dept_id,
|
||||
authenticated_user=current_user.user_name,
|
||||
type=typ,
|
||||
object=obj,
|
||||
@ -163,7 +164,7 @@ class ScolarNews(db.Model):
|
||||
return
|
||||
|
||||
news = ScolarNews(
|
||||
dept_id=g.scodoc_dept_id,
|
||||
dept_id=dept_id,
|
||||
authenticated_user=current_user.user_name,
|
||||
type=typ,
|
||||
object=obj,
|
||||
|
48
app/models/scolar_event.py
Normal file
48
app/models/scolar_event.py
Normal file
@ -0,0 +1,48 @@
|
||||
"""évènements scolaires dans la vie d'un étudiant(inscription, ...)
|
||||
"""
|
||||
from app import db
|
||||
from app.models import SHORT_STR_LEN
|
||||
|
||||
|
||||
class ScolarEvent(db.Model):
|
||||
"""Evenement dans le parcours scolaire d'un étudiant"""
|
||||
|
||||
__tablename__ = "scolar_events"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
event_id = db.synonym("id")
|
||||
etudid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("identite.id", ondelete="CASCADE"),
|
||||
)
|
||||
event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id", ondelete="SET NULL"),
|
||||
)
|
||||
ue_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_ue.id", ondelete="SET NULL"),
|
||||
)
|
||||
# 'CREATION', 'INSCRIPTION', 'DEMISSION',
|
||||
# 'AUT_RED', 'EXCLUS', 'VALID_UE', 'VALID_SEM'
|
||||
# 'ECHEC_SEM'
|
||||
# 'UTIL_COMPENSATION'
|
||||
event_type = db.Column(db.String(SHORT_STR_LEN))
|
||||
# Semestre compensé par formsemestre_id:
|
||||
comp_formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
)
|
||||
etud = db.relationship("Identite", lazy="select", backref="events", uselist=False)
|
||||
formsemestre = db.relationship(
|
||||
"FormSemestre", lazy="select", uselist=False, foreign_keys=[formsemestre_id]
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"as a dict"
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
return d
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({self.event_type}, {self.event_date.isoformat()}, {self.formsemestre})"
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: UTF-8 -*
|
||||
|
||||
"""Notes, décisions de jury, évènements scolaires
|
||||
"""Notes, décisions de jury
|
||||
"""
|
||||
|
||||
from app import db
|
||||
@ -218,47 +218,3 @@ class ScolarAutorisationInscription(db.Model):
|
||||
msg=f"Passage vers S{autorisation.semestre_id}: effacé",
|
||||
)
|
||||
db.session.flush()
|
||||
|
||||
|
||||
class ScolarEvent(db.Model):
|
||||
"""Evenement dans le parcours scolaire d'un étudiant"""
|
||||
|
||||
__tablename__ = "scolar_events"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
event_id = db.synonym("id")
|
||||
etudid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("identite.id", ondelete="CASCADE"),
|
||||
)
|
||||
event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||
formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id", ondelete="SET NULL"),
|
||||
)
|
||||
ue_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_ue.id", ondelete="SET NULL"),
|
||||
)
|
||||
# 'CREATION', 'INSCRIPTION', 'DEMISSION',
|
||||
# 'AUT_RED', 'EXCLUS', 'VALID_UE', 'VALID_SEM'
|
||||
# 'ECHEC_SEM'
|
||||
# 'UTIL_COMPENSATION'
|
||||
event_type = db.Column(db.String(SHORT_STR_LEN))
|
||||
# Semestre compensé par formsemestre_id:
|
||||
comp_formsemestre_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formsemestre.id"),
|
||||
)
|
||||
etud = db.relationship("Identite", lazy="select", backref="events", uselist=False)
|
||||
formsemestre = db.relationship(
|
||||
"FormSemestre", lazy="select", uselist=False, foreign_keys=[formsemestre_id]
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"as a dict"
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
return d
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({self.event_type}, {self.event_date.isoformat()}, {self.formsemestre})"
|
||||
|
@ -45,6 +45,12 @@ from app.models.etudiants import (
|
||||
pivot_year,
|
||||
)
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import (
|
||||
format_civilite,
|
||||
format_nom,
|
||||
format_nomprenom,
|
||||
format_prenom,
|
||||
)
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
||||
from app.scodoc import safehtml
|
||||
@ -102,60 +108,6 @@ def force_uppercase(s):
|
||||
return s.upper() if s else s
|
||||
|
||||
|
||||
def format_nomprenom(etud, reverse=False):
|
||||
"""Formatte civilité/nom/prenom pour affichages: "M. Pierre Dupont"
|
||||
Si reverse, "Dupont Pierre", sans civilité.
|
||||
|
||||
DEPRECATED: utiliser Identite.nomprenom
|
||||
"""
|
||||
nom = etud.get("nom_disp", "") or etud.get("nom_usuel", "") or etud["nom"]
|
||||
prenom = format_prenom(etud["prenom"])
|
||||
civilite = format_civilite(etud["civilite"])
|
||||
if reverse:
|
||||
fs = [nom, prenom]
|
||||
else:
|
||||
fs = [civilite, prenom, nom]
|
||||
return " ".join([x for x in fs if x])
|
||||
|
||||
|
||||
def format_prenom(s):
|
||||
"""Formatte prenom etudiant pour affichage
|
||||
DEPRECATED: utiliser Identite.prenom_str
|
||||
"""
|
||||
if not s:
|
||||
return ""
|
||||
frags = s.split()
|
||||
r = []
|
||||
for frag in frags:
|
||||
fs = frag.split("-")
|
||||
r.append("-".join([x.lower().capitalize() for x in fs]))
|
||||
return " ".join(r)
|
||||
|
||||
|
||||
def format_nom(s, uppercase=True):
|
||||
if not s:
|
||||
return ""
|
||||
if uppercase:
|
||||
return s.upper()
|
||||
else:
|
||||
return format_prenom(s)
|
||||
|
||||
|
||||
def format_civilite(civilite):
|
||||
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
|
||||
personne ne souhaitant pas d'affichage).
|
||||
Raises ScoValueError if conversion fails.
|
||||
"""
|
||||
try:
|
||||
return {
|
||||
"M": "M.",
|
||||
"F": "Mme",
|
||||
"X": "",
|
||||
}[civilite]
|
||||
except KeyError as exc:
|
||||
raise ScoValueError(f"valeur invalide pour la civilité: {civilite}") from exc
|
||||
|
||||
|
||||
def _format_etat_civil(etud: dict) -> str:
|
||||
"Mme Béatrice DUPONT, en utilisant les données d'état civil si indiquées."
|
||||
if etud["prenom_etat_civil"] or etud["civilite_etat_civil"]:
|
||||
@ -657,16 +609,6 @@ def create_etud(cnx, args: dict = None):
|
||||
db.session.commit()
|
||||
etudid = etud.id
|
||||
|
||||
# event
|
||||
scolar_events_create(
|
||||
cnx,
|
||||
args={
|
||||
"etudid": etudid,
|
||||
"event_date": time.strftime("%d/%m/%Y"),
|
||||
"formsemestre_id": None,
|
||||
"event_type": "CREATION",
|
||||
},
|
||||
)
|
||||
# log
|
||||
logdb(
|
||||
cnx,
|
||||
@ -674,16 +616,18 @@ def create_etud(cnx, args: dict = None):
|
||||
etudid=etudid,
|
||||
msg="creation initiale",
|
||||
)
|
||||
etud = etudident_list(cnx, {"etudid": etudid})[0]
|
||||
fill_etuds_info([etud])
|
||||
etud["url"] = url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
etud_dict = etudident_list(cnx, {"etudid": etudid})[0]
|
||||
fill_etuds_info([etud_dict])
|
||||
etud_dict["url"] = url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
||||
)
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_INSCR,
|
||||
text='Nouvel étudiant <a href="%(url)s">%(nomprenom)s</a>' % etud,
|
||||
text=f"Nouvel étudiant {etud.html_link_fiche()}",
|
||||
url=etud["url"],
|
||||
max_frequency=0,
|
||||
)
|
||||
return etud
|
||||
return etud_dict
|
||||
|
||||
|
||||
# ---------- "EVENTS"
|
||||
|
@ -42,6 +42,7 @@ from app.scodoc import sco_groups
|
||||
from app.scodoc.sco_exceptions import ScoException
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
def form_search_etud(
|
||||
@ -271,7 +272,7 @@ def search_etud_by_name(term: str) -> list:
|
||||
data = [
|
||||
{
|
||||
"label": "%s %s %s"
|
||||
% (x["code_nip"], x["nom"], sco_etud.format_prenom(x["prenom"])),
|
||||
% (x["code_nip"], x["nom"], scu.format_prenom(x["prenom"])),
|
||||
"value": x["code_nip"],
|
||||
}
|
||||
for x in r
|
||||
@ -290,7 +291,7 @@ def search_etud_by_name(term: str) -> list:
|
||||
|
||||
data = [
|
||||
{
|
||||
"label": "%s %s" % (x["nom"], sco_etud.format_prenom(x["prenom"])),
|
||||
"label": "%s %s" % (x["nom"], scu.format_prenom(x["prenom"])),
|
||||
"value": x["etudid"],
|
||||
}
|
||||
for x in r
|
||||
|
@ -39,7 +39,7 @@ from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import Formation, FormSemestre, FormSemestreInscription, Scolog
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.groups import Partition, GroupDescr
|
||||
from app.models.validations import ScolarEvent
|
||||
from app.models.scolar_event import ScolarEvent
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc.scolog import logdb
|
||||
@ -222,10 +222,10 @@ def do_formsemestre_desinscription(etudid, formsemestre_id):
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
cursor.execute(
|
||||
"""SELECT Im.id AS moduleimpl_inscription_id
|
||||
"""SELECT Im.id AS moduleimpl_inscription_id
|
||||
FROM notes_moduleimpl_inscription Im, notes_moduleimpl M
|
||||
WHERE Im.etudid=%(etudid)s
|
||||
and Im.moduleimpl_id = M.id
|
||||
and Im.moduleimpl_id = M.id
|
||||
and M.formsemestre_id = %(formsemestre_id)s
|
||||
""",
|
||||
{"etudid": etudid, "formsemestre_id": formsemestre_id},
|
||||
@ -253,7 +253,7 @@ def do_formsemestre_desinscription(etudid, formsemestre_id):
|
||||
nbinscrits = len(inscrits)
|
||||
if nbinscrits == 0:
|
||||
log(
|
||||
f"""do_formsemestre_desinscription:
|
||||
f"""do_formsemestre_desinscription:
|
||||
suppression du semestre extérieur {formsemestre}"""
|
||||
)
|
||||
flash("Semestre exterieur supprimé")
|
||||
@ -436,7 +436,7 @@ def formsemestre_inscription_with_modules(
|
||||
if inscr is not None:
|
||||
H.append(
|
||||
f"""
|
||||
<p class="warning">{etud.nomprenom} est déjà inscrit
|
||||
<p class="warning">{etud.nomprenom} est déjà inscrit
|
||||
dans le semestre {formsemestre.titre_mois()}
|
||||
</p>
|
||||
<ul>
|
||||
@ -482,8 +482,8 @@ def formsemestre_inscription_with_modules(
|
||||
H.append("</ul>")
|
||||
H.append(
|
||||
f"""<p><a href="{ url_for( "notes.formsemestre_inscription_with_modules",
|
||||
scodoc_dept=g.scodoc_dept, etudid=etudid, formsemestre_id=formsemestre_id,
|
||||
multiple_ok=1,
|
||||
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>"""
|
||||
@ -644,7 +644,7 @@ function chkbx_select(field_id, state) {
|
||||
"""
|
||||
<p>Voici la liste des modules du semestre choisi.</p>
|
||||
<p>
|
||||
Les modules cochés sont ceux dans lesquels l'étudiant est inscrit.
|
||||
Les modules cochés sont ceux dans lesquels l'étudiant est inscrit.
|
||||
Vous pouvez l'inscrire ou le désincrire d'un ou plusieurs modules.
|
||||
</p>
|
||||
<p>Attention: cette méthode ne devrait être utilisée que pour les modules
|
||||
|
@ -53,6 +53,7 @@ from app.scodoc import codes_cursus
|
||||
from app.scodoc import sco_cursus
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc.sco_etud import etud_sort_key
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_xml
|
||||
from app.scodoc.sco_exceptions import ScoException, AccessDenied, ScoValueError
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
@ -573,8 +574,8 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD
|
||||
etudid=str(e["etudid"]),
|
||||
civilite=etud["civilite_str"] or "",
|
||||
sexe=etud["civilite_str"] or "", # compat
|
||||
nom=sco_etud.format_nom(etud["nom"] or ""),
|
||||
prenom=sco_etud.format_prenom(etud["prenom"] or ""),
|
||||
nom=scu.format_nom(etud["nom"] or ""),
|
||||
prenom=scu.format_prenom(etud["prenom"] or ""),
|
||||
origin=_comp_etud_origin(etud, formsemestre),
|
||||
)
|
||||
)
|
||||
@ -599,8 +600,8 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD
|
||||
"etud",
|
||||
etudid=str(etud["etudid"]),
|
||||
sexe=etud["civilite_str"] or "",
|
||||
nom=sco_etud.format_nom(etud["nom"] or ""),
|
||||
prenom=sco_etud.format_prenom(etud["prenom"] or ""),
|
||||
nom=scu.format_nom(etud["nom"] or ""),
|
||||
prenom=scu.format_prenom(etud["prenom"] or ""),
|
||||
origin=_comp_etud_origin(etud, formsemestre),
|
||||
)
|
||||
)
|
||||
|
@ -244,8 +244,8 @@ def feuille_preparation_jury(formsemestre_id):
|
||||
[
|
||||
etud.id,
|
||||
etud.civilite_str,
|
||||
sco_etud.format_nom(etud.nom),
|
||||
sco_etud.format_prenom(etud.prenom),
|
||||
scu.format_nom(etud.nom),
|
||||
scu.format_prenom(etud.prenom),
|
||||
etud.date_naissance,
|
||||
etud.admission.bac if etud.admission else "",
|
||||
etud.admission.specialite if etud.admission else "",
|
||||
|
@ -1244,7 +1244,7 @@ def _form_saisie_notes(
|
||||
'<span class="%s">' % classdem
|
||||
+ e["civilite_str"]
|
||||
+ " "
|
||||
+ sco_etud.format_nomprenom(e, reverse=True)
|
||||
+ scu.format_nomprenom(e, reverse=True)
|
||||
+ "</span>"
|
||||
)
|
||||
|
||||
|
@ -129,7 +129,7 @@ def trombino_html(groups_infos):
|
||||
H = [
|
||||
f"""<table style="padding-top: 10px; padding-bottom: 10px;">
|
||||
<tr>
|
||||
<td><span
|
||||
<td><span
|
||||
style="font-style: bold; font-size: 150%%; padding-right: 20px;"
|
||||
>{group_txt}</span></td>"""
|
||||
]
|
||||
@ -164,9 +164,9 @@ def trombino_html(groups_infos):
|
||||
H.append("</span>")
|
||||
H.append(
|
||||
'<span class="trombi_legend"><span class="trombi_prenom">'
|
||||
+ sco_etud.format_prenom(t["prenom"])
|
||||
+ scu.format_prenom(t["prenom"])
|
||||
+ '</span><span class="trombi_nom">'
|
||||
+ sco_etud.format_nom(t["nom"])
|
||||
+ scu.format_nom(t["nom"])
|
||||
+ (" <i>(dem.)</i>" if t["etat"] == "D" else "")
|
||||
)
|
||||
H.append("</span></span></span>")
|
||||
@ -175,10 +175,10 @@ def trombino_html(groups_infos):
|
||||
H.append("</div>")
|
||||
H.append(
|
||||
f"""<div style="margin-bottom:15px;">
|
||||
<a class="stdlink" href="{url_for('scolar.trombino', scodoc_dept=g.scodoc_dept,
|
||||
<a class="stdlink" href="{url_for('scolar.trombino', scodoc_dept=g.scodoc_dept,
|
||||
fmt='pdf', group_ids=groups_infos.group_ids)}">Version PDF</a>
|
||||
|
||||
<a class="stdlink" href="{url_for('scolar.trombino', scodoc_dept=g.scodoc_dept,
|
||||
<a class="stdlink" href="{url_for('scolar.trombino', scodoc_dept=g.scodoc_dept,
|
||||
fmt='doc', group_ids=groups_infos.group_ids)}">Version doc</a>
|
||||
</div>"""
|
||||
)
|
||||
@ -202,9 +202,9 @@ def check_local_photos_availability(groups_infos, fmt=""):
|
||||
return (
|
||||
False,
|
||||
scu.confirm_dialog(
|
||||
f"""<p>Attention: {nb_missing} photos ne sont pas disponibles
|
||||
f"""<p>Attention: {nb_missing} photos ne sont pas disponibles
|
||||
et ne peuvent pas être exportées.</p>
|
||||
<p>Vous pouvez <a class="stdlink"
|
||||
<p>Vous pouvez <a class="stdlink"
|
||||
href="{groups_infos.base_url}&dialog_confirmed=1&fmt={fmt}"
|
||||
>exporter seulement les photos existantes</a>""",
|
||||
dest_url="trombino",
|
||||
@ -263,11 +263,11 @@ def trombino_copy_photos(group_ids=[], dialog_confirmed=False):
|
||||
if not dialog_confirmed:
|
||||
return scu.confirm_dialog(
|
||||
f"""<h2>Copier les photos du portail vers ScoDoc ?</h2>
|
||||
<p>Les photos du groupe {groups_infos.groups_titles} présentes
|
||||
<p>Les photos du groupe {groups_infos.groups_titles} présentes
|
||||
dans ScoDoc seront remplacées par celles du portail (si elles existent).
|
||||
</p>
|
||||
<p>(les photos sont normalement automatiquement copiées
|
||||
lors de leur première utilisation, l'usage de cette fonction
|
||||
<p>(les photos sont normalement automatiquement copiées
|
||||
lors de leur première utilisation, l'usage de cette fonction
|
||||
n'est nécessaire que si les photos du portail ont été modifiées)
|
||||
</p>
|
||||
""",
|
||||
@ -349,7 +349,7 @@ def _trombino_pdf(groups_infos):
|
||||
[img],
|
||||
[
|
||||
Paragraph(
|
||||
SU(sco_etud.format_nomprenom(t)),
|
||||
SU(scu.format_nomprenom(t)),
|
||||
style_sheet["Normal"],
|
||||
)
|
||||
],
|
||||
@ -428,7 +428,7 @@ def _listeappel_photos_pdf(groups_infos):
|
||||
t = groups_infos.members[i]
|
||||
img = _get_etud_platypus_image(t, image_width=PHOTO_WIDTH)
|
||||
txt = Paragraph(
|
||||
SU(sco_etud.format_nomprenom(t)),
|
||||
SU(scu.format_nomprenom(t)),
|
||||
style_sheet["Normal"],
|
||||
)
|
||||
if currow:
|
||||
|
@ -55,7 +55,7 @@ def trombino_doc(groups_infos):
|
||||
cell = table.rows[2 * li + 1].cells[co]
|
||||
cell.vertical_alignment = WD_ALIGN_VERTICAL.TOP
|
||||
cell_p, cell_f, cell_r = _paragraph_format_run(cell)
|
||||
cell_r.add_text(sco_etud.format_nomprenom(t))
|
||||
cell_r.add_text(scu.format_nomprenom(t))
|
||||
cell_f.space_after = Mm(8)
|
||||
|
||||
return scu.send_docx(document, filename)
|
||||
|
@ -196,9 +196,9 @@ def pdf_trombino_tours(
|
||||
Paragraph(
|
||||
SU(
|
||||
"<para align=center><font size=8>"
|
||||
+ sco_etud.format_prenom(m["prenom"])
|
||||
+ scu.format_prenom(m["prenom"])
|
||||
+ " "
|
||||
+ sco_etud.format_nom(m["nom"])
|
||||
+ scu.format_nom(m["nom"])
|
||||
+ text_group
|
||||
+ "</font></para>"
|
||||
),
|
||||
@ -413,11 +413,7 @@ def pdf_feuille_releve_absences(
|
||||
for m in members:
|
||||
currow = [
|
||||
Paragraph(
|
||||
SU(
|
||||
sco_etud.format_nom(m["nom"])
|
||||
+ " "
|
||||
+ sco_etud.format_prenom(m["prenom"])
|
||||
),
|
||||
SU(scu.format_nom(m["nom"]) + " " + scu.format_prenom(m["prenom"])),
|
||||
StyleSheet["Normal"],
|
||||
)
|
||||
]
|
||||
|
@ -64,6 +64,7 @@ from config import Config
|
||||
from app import log, ScoDocJSONEncoder
|
||||
|
||||
from app.scodoc.codes_cursus import NOTES_TOLERANCE, CODES_EXPL
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc import sco_xml
|
||||
import sco_version
|
||||
|
||||
@ -1139,6 +1140,61 @@ def abbrev_prenom(prenom):
|
||||
return abrv
|
||||
|
||||
|
||||
def format_civilite(civilite):
|
||||
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
|
||||
personne ne souhaitant pas d'affichage).
|
||||
Raises ScoValueError if conversion fails.
|
||||
"""
|
||||
try:
|
||||
return {
|
||||
"M": "M.",
|
||||
"F": "Mme",
|
||||
"X": "",
|
||||
}[civilite]
|
||||
except KeyError as exc:
|
||||
raise ScoValueError(f"valeur invalide pour la civilité: {civilite}") from exc
|
||||
|
||||
|
||||
def format_nomprenom(etud, reverse=False):
|
||||
"""Formatte civilité/nom/prenom pour affichages: "M. Pierre Dupont"
|
||||
Si reverse, "Dupont Pierre", sans civilité.
|
||||
|
||||
DEPRECATED: utiliser Identite.nomprenom
|
||||
"""
|
||||
nom = etud.get("nom_disp", "") or etud.get("nom_usuel", "") or etud["nom"]
|
||||
prenom = format_prenom(etud["prenom"])
|
||||
civilite = format_civilite(etud["civilite"])
|
||||
if reverse:
|
||||
fs = [nom, prenom]
|
||||
else:
|
||||
fs = [civilite, prenom, nom]
|
||||
return " ".join([x for x in fs if x])
|
||||
|
||||
|
||||
def format_nom(s, uppercase=True):
|
||||
"Formatte le nom"
|
||||
if not s:
|
||||
return ""
|
||||
if uppercase:
|
||||
return s.upper()
|
||||
else:
|
||||
return format_prenom(s)
|
||||
|
||||
|
||||
def format_prenom(s):
|
||||
"""Formatte prenom etudiant pour affichage
|
||||
DEPRECATED: utiliser Identite.prenom_str
|
||||
"""
|
||||
if not s:
|
||||
return ""
|
||||
frags = s.split()
|
||||
r = []
|
||||
for frag in frags:
|
||||
fs = frag.split("-")
|
||||
r.append("-".join([x.lower().capitalize() for x in fs]))
|
||||
return " ".join(r)
|
||||
|
||||
|
||||
#
|
||||
def timedate_human_repr():
|
||||
"representation du temps courant pour utilisateur"
|
||||
@ -1480,6 +1536,7 @@ def is_assiduites_module_forced(
|
||||
|
||||
def get_assiduites_time_config(config_type: str) -> str:
|
||||
from app.models import ScoDocSiteConfig
|
||||
|
||||
match config_type:
|
||||
case "matin":
|
||||
return ScoDocSiteConfig.get("assi_morning_time", "08:00:00")
|
||||
|
@ -28,10 +28,11 @@ from tests.api.setup_test_api import (
|
||||
API_URL,
|
||||
API_USER_ADMIN,
|
||||
CHECK_CERTIFICATE,
|
||||
DEPT_ACRONYM,
|
||||
POST_JSON,
|
||||
api_headers,
|
||||
get_auth_headers,
|
||||
)
|
||||
from tests.api.setup_test_api import api_headers # pylint: disable=unused-import
|
||||
from tests.api.tools_test_api import (
|
||||
BULLETIN_ETUDIANT_FIELDS,
|
||||
BULLETIN_FIELDS,
|
||||
@ -923,3 +924,20 @@ def test_etudiant_groups(api_headers):
|
||||
group = groups[0]
|
||||
fields_ok = verify_fields(group, fields)
|
||||
assert fields_ok is True
|
||||
|
||||
|
||||
def test_etudiant_create(api_headers):
|
||||
"""/etudiant/create"""
|
||||
admin_header = get_auth_headers(API_USER_ADMIN, API_PASSWORD_ADMIN)
|
||||
args = {
|
||||
"prenom": "Carl Philipp Emanuel",
|
||||
"nom": "Bach",
|
||||
"dept": DEPT_ACRONYM,
|
||||
"civilite": "M",
|
||||
}
|
||||
etud = POST_JSON(
|
||||
"/etudiant/create",
|
||||
args,
|
||||
headers=admin_header,
|
||||
)
|
||||
assert etud["nom"] == args["nom"].upper()
|
||||
|
Loading…
Reference in New Issue
Block a user