API: create/edit etudiant, admission, adresse

This commit is contained in:
Emmanuel Viennet 2023-11-23 17:08:18 +01:00
parent 2377918b54
commit e634b50d56
5 changed files with 152 additions and 12 deletions

View File

@ -25,6 +25,7 @@ from app.but import bulletin_but_court
from app.decorators import scodoc, permission_required from app.decorators import scodoc, permission_required
from app.models import ( from app.models import (
Admission, Admission,
Adresse,
Departement, Departement,
FormSemestreInscription, FormSemestreInscription,
FormSemestre, FormSemestre,
@ -513,6 +514,20 @@ def etudiant_create(force=False):
400, f"{len(homonyms)} homonymes détectés. Vous pouvez utiliser /force." 400, f"{len(homonyms)} homonymes détectés. Vous pouvez utiliser /force."
) )
etud = Identite.create_etud(**args) etud = Identite.create_etud(**args)
db.session.flush()
# --- Données admission
admission_args = args.get("admission", None)
if admission_args:
etud.admission.from_dict(admission_args)
# --- Adresse
adresses = args.get("adresses", [])
if adresses:
# ne prend en compte que la première adresse
# car si la base est concue pour avoir plusieurs adresses par étudiant,
# l'application n'en gère plus qu'une seule.
adresse = etud.adresses.first()
adresse.from_dict(adresses[0])
# Poste une nouvelle dans le département concerné: # Poste une nouvelle dans le département concerné:
ScolarNews.add( ScolarNews.add(
typ=ScolarNews.NEWS_INSCR, typ=ScolarNews.NEWS_INSCR,
@ -522,4 +537,54 @@ def etudiant_create(force=False):
dept_id=dept_o.id, dept_id=dept_o.id,
) )
db.session.commit() db.session.commit()
return etud.to_dict_short() # Note: je ne comprends pas pourquoi un refresh est nécessaire ici
# sans ce refresh, etud.__dict__ est incomplet (pas de 'nom').
db.session.refresh(etud)
r = etud.to_dict_api()
return r
@bp.route("/etudiant/<string:code_type>/<string:code>/edit", methods=["POST"])
@scodoc
@permission_required(Permission.EtudInscrit)
def etudiant_edit(
code_type: str = "etudid",
code: str = None,
):
"""Edition des données étudiant (identité, admission, adresses)"""
if code_type == "nip":
query = Identite.query.filter_by(code_nip=code)
elif code_type == "etudid":
try:
etudid = int(code)
except ValueError:
return json_error(404, "invalid etudid type")
query = Identite.query.filter_by(id=etudid)
elif code_type == "ine":
query = Identite.query.filter_by(code_ine=code)
else:
return json_error(404, "invalid code_type")
if g.scodoc_dept:
query = query.filter_by(dept_id=g.scodoc_dept_id)
etud: Identite = query.first()
#
args = request.get_json(force=True) # may raise 400 Bad Request
etud.from_dict(args)
admission_args = args.get("admission", None)
if admission_args:
etud.admission.from_dict(admission_args)
# --- Adresse
adresses = args.get("adresses", [])
if adresses:
# ne prend en compte que la première adresse
# car si la base est concue pour avoir plusieurs adresses par étudiant,
# l'application n'en gère plus qu'une seule.
adresse = etud.adresses.first()
adresse.from_dict(adresses[0])
db.session.commit()
# Note: je ne comprends pas pourquoi un refresh est nécessaire ici
# sans ce refresh, etud.__dict__ est incomplet (pas de 'nom').
db.session.refresh(etud)
r = etud.to_dict_api()
return r

View File

@ -15,6 +15,7 @@ from sqlalchemy import desc, text
from app import db, log from app import db, log
from app import models from app import models
from app.models.departements import Departement
from app.models.scolar_event import ScolarEvent from app.models.scolar_event import ScolarEvent
from app.scodoc import notesdb as ndb from app.scodoc import notesdb as ndb
from app.scodoc.sco_bac import Baccalaureat from app.scodoc.sco_bac import Baccalaureat
@ -204,14 +205,22 @@ class Identite(db.Model, models.ScoDocModel):
return cls.create_from_dict(args) return cls.create_from_dict(args)
@classmethod @classmethod
def create_from_dict(cls, data) -> "Identite": def create_from_dict(cls, args) -> "Identite":
"""Crée un étudiant à partir d'un dict, avec admission et adresse vides. """Crée un étudiant à partir d'un dict, avec admission et adresse vides.
If required dept_id or dept are not specified, set it to the current dept.
args: dict with args in application.
Les clés adresses et admission ne SONT PAS utilisées.
(added to session but not flushed nor commited) (added to session but not flushed nor commited)
""" """
etud: Identite = super(cls, cls).create_from_dict(data) if not "dept_id" in args:
if (data.get("admission_id", None) is None) and ( if "dept" in args:
data.get("admission", None) is None departement = Departement.query.filter_by(acronym=args["dept"]).first()
): if departement:
args["dept_id"] = departement.id
if not "dept_id" in args:
args["dept_id"] = g.scodoc_dept_id
etud: Identite = super().create_from_dict(args)
if args.get("admission_id", None) is None:
etud.admission = Admission() etud.admission = Admission()
etud.adresses.append(Adresse(typeadresse="domicile")) etud.adresses.append(Adresse(typeadresse="domicile"))
db.session.flush() db.session.flush()
@ -221,6 +230,14 @@ class Identite(db.Model, models.ScoDocModel):
log(f"Identite.create {etud}") log(f"Identite.create {etud}")
return etud return etud
@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."""
return super().filter_model_attributes(
data,
excluded=(excluded or set()) | {"adresses", "admission", "departement"},
)
@property @property
def civilite_str(self) -> str: def civilite_str(self) -> str:
"""returns civilité usuelle: 'M.' ou 'Mme' ou '' (pour le genre neutre, """returns civilité usuelle: 'M.' ou 'Mme' ou '' (pour le genre neutre,
@ -329,8 +346,6 @@ class Identite(db.Model, models.ScoDocModel):
@classmethod @classmethod
def convert_dict_fields(cls, args: dict) -> dict: def convert_dict_fields(cls, args: dict) -> dict:
"""Convert fields in the given dict. No other side effect. """Convert fields in the given dict. No other side effect.
If required dept_id is not specified, set it to the current dept.
args: dict with args in application.
returns: dict to store in model's db. returns: dict to store in model's db.
""" """
# Les champs qui sont toujours stockés en majuscules: # Les champs qui sont toujours stockés en majuscules:
@ -349,8 +364,6 @@ class Identite(db.Model, models.ScoDocModel):
"code_ine", "code_ine",
} }
args_dict = {} args_dict = {}
if not "dept_id" in args:
args["dept_id"] = g.scodoc_dept_id
for key, value in args.items(): for key, value in args.items():
if hasattr(cls, key) and not isinstance(getattr(cls, key, None), property): if hasattr(cls, key) and not isinstance(getattr(cls, key, None), property):
# compat scodoc7 (mauvaise idée de l'époque) # compat scodoc7 (mauvaise idée de l'époque)

View File

@ -624,7 +624,7 @@ def create_etud(cnx, args: dict = None):
ScolarNews.add( ScolarNews.add(
typ=ScolarNews.NEWS_INSCR, typ=ScolarNews.NEWS_INSCR,
text=f"Nouvel étudiant {etud.html_link_fiche()}", text=f"Nouvel étudiant {etud.html_link_fiche()}",
url=etud["url"], url=etud_dict["url"],
max_frequency=0, max_frequency=0,
) )
return etud_dict return etud_dict

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.6.60" SCOVERSION = "9.6.61"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"

View File

@ -29,6 +29,7 @@ from tests.api.setup_test_api import (
API_USER_ADMIN, API_USER_ADMIN,
CHECK_CERTIFICATE, CHECK_CERTIFICATE,
DEPT_ACRONYM, DEPT_ACRONYM,
GET,
POST_JSON, POST_JSON,
get_auth_headers, get_auth_headers,
) )
@ -934,6 +935,16 @@ def test_etudiant_create(api_headers):
"nom": "Bach", "nom": "Bach",
"dept": DEPT_ACRONYM, "dept": DEPT_ACRONYM,
"civilite": "M", "civilite": "M",
"admission": {
"commentaire": "test",
"annee_bac": 2024,
},
"adresses": [
{
"villedomicile": "Santa Teresa",
"emailperso": "XXX@2666.mx",
}
],
} }
etud = POST_JSON( etud = POST_JSON(
"/etudiant/create", "/etudiant/create",
@ -941,3 +952,54 @@ def test_etudiant_create(api_headers):
headers=admin_header, headers=admin_header,
) )
assert etud["nom"] == args["nom"].upper() assert etud["nom"] == args["nom"].upper()
assert etud["admission"]["commentaire"] == args["admission"]["commentaire"]
assert etud["admission"]["annee_bac"] == args["admission"]["annee_bac"]
assert len(etud["adresses"]) == 1
assert etud["adresses"][0]["villedomicile"] == args["adresses"][0]["villedomicile"]
assert etud["adresses"][0]["emailperso"] == args["adresses"][0]["emailperso"]
etudid = etud["id"]
# On recommence avec une nouvelle requête:
etud = GET(f"/etudiant/etudid/{etudid}", headers=api_headers)
assert etud["nom"] == args["nom"].upper()
assert etud["admission"]["commentaire"] == args["admission"]["commentaire"]
assert etud["admission"]["annee_bac"] == args["admission"]["annee_bac"]
assert len(etud["adresses"]) == 1
assert etud["adresses"][0]["villedomicile"] == args["adresses"][0]["villedomicile"]
assert etud["adresses"][0]["emailperso"] == args["adresses"][0]["emailperso"]
# Edition
etud = POST_JSON(
f"/etudiant/etudid/{etudid}/edit",
{
"civilite": "F",
},
headers=admin_header,
)
assert etud["civilite"] == "F"
assert etud["nom"] == args["nom"].upper()
assert etud["admission"]["commentaire"] == args["admission"]["commentaire"]
assert etud["admission"]["annee_bac"] == args["admission"]["annee_bac"]
assert len(etud["adresses"]) == 1
assert etud["adresses"][0]["villedomicile"] == args["adresses"][0]["villedomicile"]
assert etud["adresses"][0]["emailperso"] == args["adresses"][0]["emailperso"]
etud = POST_JSON(
f"/etudiant/etudid/{etudid}/edit",
{
"adresses": [
{
"villedomicile": "Barcelona",
},
],
},
headers=admin_header,
)
assert etud["adresses"][0]["villedomicile"] == "Barcelona"
etud = POST_JSON(
f"/etudiant/etudid/{etudid}/edit",
{
"admission": {
"commentaire": "un nouveau commentaire",
},
},
headers=admin_header,
)
assert etud["admission"]["commentaire"] == "un nouveau commentaire"