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.models import (
Admission,
Adresse,
Departement,
FormSemestreInscription,
FormSemestre,
@ -513,6 +514,20 @@ def etudiant_create(force=False):
400, f"{len(homonyms)} homonymes détectés. Vous pouvez utiliser /force."
)
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é:
ScolarNews.add(
typ=ScolarNews.NEWS_INSCR,
@ -522,4 +537,54 @@ def etudiant_create(force=False):
dept_id=dept_o.id,
)
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 models
from app.models.departements import Departement
from app.models.scolar_event import ScolarEvent
from app.scodoc import notesdb as ndb
from app.scodoc.sco_bac import Baccalaureat
@ -204,14 +205,22 @@ class Identite(db.Model, models.ScoDocModel):
return cls.create_from_dict(args)
@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.
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)
"""
etud: Identite = super(cls, cls).create_from_dict(data)
if (data.get("admission_id", None) is None) and (
data.get("admission", None) is None
):
if not "dept_id" in args:
if "dept" in args:
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.adresses.append(Adresse(typeadresse="domicile"))
db.session.flush()
@ -221,6 +230,14 @@ class Identite(db.Model, models.ScoDocModel):
log(f"Identite.create {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
def civilite_str(self) -> str:
"""returns civilité usuelle: 'M.' ou 'Mme' ou '' (pour le genre neutre,
@ -329,8 +346,6 @@ class Identite(db.Model, models.ScoDocModel):
@classmethod
def convert_dict_fields(cls, args: dict) -> dict:
"""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.
"""
# Les champs qui sont toujours stockés en majuscules:
@ -349,8 +364,6 @@ class Identite(db.Model, models.ScoDocModel):
"code_ine",
}
args_dict = {}
if not "dept_id" in args:
args["dept_id"] = g.scodoc_dept_id
for key, value in args.items():
if hasattr(cls, key) and not isinstance(getattr(cls, key, None), property):
# compat scodoc7 (mauvaise idée de l'époque)

View File

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

View File

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

View File

@ -29,6 +29,7 @@ from tests.api.setup_test_api import (
API_USER_ADMIN,
CHECK_CERTIFICATE,
DEPT_ACRONYM,
GET,
POST_JSON,
get_auth_headers,
)
@ -934,6 +935,16 @@ def test_etudiant_create(api_headers):
"nom": "Bach",
"dept": DEPT_ACRONYM,
"civilite": "M",
"admission": {
"commentaire": "test",
"annee_bac": 2024,
},
"adresses": [
{
"villedomicile": "Santa Teresa",
"emailperso": "XXX@2666.mx",
}
],
}
etud = POST_JSON(
"/etudiant/create",
@ -941,3 +952,54 @@ def test_etudiant_create(api_headers):
headers=admin_header,
)
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"