Handling of neutral genders.

Closes #65
This commit is contained in:
Emmanuel Viennet 2021-02-13 17:28:55 +01:00
parent 8d7ec2faea
commit 8f02f16442
33 changed files with 281 additions and 199 deletions

View File

@ -368,16 +368,16 @@ def scolars_import_excel_file(
# xxx Ad-hoc checks (should be in format description)
if scu.strlower(titleslist[i]) == "sexe":
try:
val = scolars.normalize_sexe(val)
val = scolars.input_civilite(val)
except:
raise ScoValueError(
"valeur invalide pour 'SEXE' (doit etre 'M' ou 'MME' ou 'H' ou 'F', pas '%s') ligne %d, colonne %s"
"valeur invalide pour 'SEXE' (doit etre 'M', 'F', ou 'MME', 'H', 'X' ou vide, mais pas '%s') ligne %d, colonne %s"
% (val, linenum, titleslist[i])
)
# Excel date conversion:
if scu.strlower(titleslist[i]) == "date_naissance":
if val:
if re.match("^[0-9]*\.?[0-9]*$", str(val)):
if re.match(r"^[0-9]*\.?[0-9]*$", str(val)):
val = sco_excel.xldate_as_datetime(float(val))
# INE
if (
@ -515,11 +515,11 @@ def _import_one_student(
# Admissions
args["etudid"] = etudid
args["annee"] = annee_courante
adm_id = scolars.admission_create(cnx, args)
_ = scolars.admission_create(cnx, args)
# Adresse
args["typeadresse"] = "domicile"
args["description"] = "(infos admission)"
adresse_id = scolars.adresse_create(cnx, args)
_ = scolars.adresse_create(cnx, args)
# Inscription au semestre
args["etat"] = "I" # etat insc. semestre
if formsemestre_id:

View File

@ -99,7 +99,7 @@ import sco_formations
from scolars import (
format_nom,
format_prenom,
format_sexe,
format_civilite,
format_lycee,
format_lycee_from_code,
)
@ -916,7 +916,8 @@ REQUEST.URL0=%s<br/>
"nom": "?",
"nom_usuel": "",
"prenom": "?",
"sexe": "?",
"civilite": "?",
"sexe": "?", # for backward compat
"email": "?",
"emailperso": "",
"error": "code etudiant inconnu",
@ -935,7 +936,6 @@ REQUEST.URL0=%s<br/>
"nom",
"nom_usuel",
"prenom",
"sexe",
"nomprenom",
"email",
"emailperso",
@ -956,6 +956,10 @@ REQUEST.URL0=%s<br/>
"date_naissance_iso",
):
d[a] = scu.quote_xml_attr(etud[a])
d["civilite"] = scu.quote_xml_attr(
etud["civilite_str"]
) # exception: ne sort pas la civilite brute
d["sexe"] = d["civilite"] # backward compat pour anciens clients
d["photo_url"] = scu.quote_xml_attr(sco_photos.etud_photo_url(self, etud))
sem = etud["cursem"]
@ -1750,12 +1754,12 @@ REQUEST.URL0=%s<br/>
},
),
(
"sexe",
"civilite",
{
"input_type": "menu",
"labels": ["H", "F"],
"allowed_values": ["MR", "MME"],
"title": "Genre",
"labels": ["Homme", "Femme", "Autre/neutre"],
"allowed_values": ["M", "F", "X"],
"title": "Civilité",
},
),
(

View File

@ -1070,6 +1070,21 @@ for dept in get_depts():
"etape",
["alter table notes_formsemestre_inscription add column etape text"],
)
# Migre identite.sexe vers identite.civilite: normalise valeurs: M, F, X
check_field(
cnx,
"identite",
"civilite",
[
"UPDATE identite set sexe='M' where upper(sexe) = 'M' or upper(sexe) = 'M.' or upper(sexe) = 'MR' or upper(sexe) = 'H'",
"UPDATE identite set sexe='F' where upper(sexe) = 'F' or upper(sexe) = 'MME' or upper(sexe) = 'MLLE' or upper(sexe) = 'MELLE' or upper(sexe) = 'MLLE.' or upper(sexe) = 'F'",
"UPDATE identite set sexe='X' where sexe is NULL",
"ALTER TABLE identite RENAME COLUMN sexe TO civilite",
"ALTER TABLE identite ALTER COLUMN civilite SET NOT NULL",
"ALTER TABLE identite ADD CONSTRAINT civchk CHECK (civilite IN ('M', 'F', 'X'))",
],
)
# Add here actions to performs after upgrades:
cnx.commit()
cnx.close()

View File

@ -103,7 +103,7 @@ def sidebar(context, REQUEST=None):
# compte les absences du semestre en cours
H.append(
"""<h2 id="insidebar-etud"><a href="%(ScoURL)s/ficheEtud?etudid=%(etudid)s" class="sidebar">
<font color="#FF0000">%(sexe)s %(nom_disp)s</font></a>
<font color="#FF0000">%(civilite_str)s %(nom_disp)s</font></a>
</h2>
<b>Absences</b>"""
% params

View File

@ -49,7 +49,7 @@ CREATE TABLE identite (
etudid text DEFAULT notes_newid_etud('EID'::text) UNIQUE NOT NULL,
nom text,
prenom text,
sexe text,
civilite text NOT NULL CHECK (civilite IN ('M', 'F', 'X')),
date_naissance date, -- new: date en texte
lieu_naissance text,
dept_naissance text,

View File

@ -8,7 +8,7 @@ Code_INE; text; identite; 1; code INE;INE
nom; text; identite; 0; nom de l'etudiant;
nom_usuel; text; identite; 1; nom usuel (si different);
prenom; text; identite; 0; prenom de l'etudiant
sexe (H ou F); text; identite; 0; sexe ('H' ou 'F');sexe;genre
civilite; text; identite; 1; sexe ('M', 'F', 'X');sexe;genre
date_naissance;text;identite; 1; date de naissance (jj/mm/aaaa)
lieu_naissance;text;identite; 1; lieu de naissance
nationalite; text; identite; 1; nationalite

View File

@ -372,7 +372,9 @@ class NotesTable:
def get_sexnom(self, etudid):
"M. DUPONT"
etud = self.identdict[etudid]
return etud["sexe"] + " " + scu.strupper(etud["nom_usuel"] or etud["nom"])
return (
etud["civilite_str"] + " " + scu.strupper(etud["nom_usuel"] or etud["nom"])
)
def get_nom_short(self, etudid):
"formatte nom d'un etud (pour table recap)"
@ -391,13 +393,7 @@ class NotesTable:
def get_nom_long(self, etudid):
"formatte nom d'un etud: M. Pierre DUPONT"
etud = self.identdict[etudid]
return " ".join(
[
scolars.format_sexe(etud["sexe"]),
scolars.format_prenom(etud["prenom"]),
scolars.format_nom(etud["nom_usuel"] or etud["nom"]),
]
)
return scolars.format_nomprenom(etud)
def get_displayed_etud_code(self, etudid):
'code à afficher sur les listings "anonymes"'

View File

@ -213,7 +213,7 @@ def get_code_latex_avis_etudiant(
elif tag_latex == u"bilanParTag":
valeur = get_bilanParTag(donnees_etudiant)
# Les tags "simples": par ex. nom, prenom, sexe, ...
# Les tags "simples": par ex. nom, prenom, civilite, ...
else:
if tag_latex in donnees_etudiant:
valeur = donnees_etudiant[tag_latex].decode(scu.SCO_ENCODING)
@ -410,7 +410,7 @@ def get_avis_poursuite_par_etudiant(
if pe_tools.PE_DEBUG:
pe_tools.pe_print(jury.syntheseJury[etudid]["nom"] + " " + etudid)
sexe = jury.syntheseJury[etudid]["sexe"].decode(scu.SCO_ENCODING)
civilite_str = jury.syntheseJury[etudid]["civilite_str"].decode(scu.SCO_ENCODING)
nom = jury.syntheseJury[etudid]["nom"].replace(" ", "-").decode(scu.SCO_ENCODING)
prenom = (
jury.syntheseJury[etudid]["prenom"].replace(" ", "-").decode(scu.SCO_ENCODING)
@ -429,7 +429,9 @@ def get_avis_poursuite_par_etudiant(
# Entete (commentaire)
contenu_latex = u"%% ---- Etudiant: " + sexe + " " + nom + " " + prenom + u"\n"
contenu_latex = (
u"%% ---- Etudiant: " + civilite_str + " " + nom + " " + prenom + u"\n"
)
# les annnotations
annotationPE = get_annotation_PE(
@ -502,7 +504,7 @@ def table_syntheseAnnotationPE(context, syntheseJury, tag_annotation_pe):
[syntheseJury[etudid]["nbSemestres"] for etudid in etudids]
) # le nombre de semestre le + grand
infos = ["sexe", "nom", "prenom", "age", "nbSemestres"]
infos = ["civilite", "nom", "prenom", "age", "nbSemestres"]
entete = ["etudid"]
entete.extend(infos)
entete.extend(["P%d" % i for i in range(1, maxParcours + 1)]) # ajout du parcours
@ -518,7 +520,7 @@ def table_syntheseAnnotationPE(context, syntheseJury, tag_annotation_pe):
# Les info générales:
row = {
"etudid": etudid,
"sexe": e["sexe"],
"civilite": e["civilite"],
"nom": e["nom"],
"prenom": e["prenom"],
"age": e["age"],

View File

@ -85,7 +85,7 @@ class JuryPE:
- context : le contexte Zope
- juryEtudDict : dictionnaire récapitulant les étudiants participant au jury PE (données administratives +
celles des semestres valides à prendre en compte permettant le calcul des moyennes ...
{'etudid : { 'nom', 'prenom', 'sexe', 'diplome', '', }}
{'etudid : { 'nom', 'prenom', 'civilite', 'diplome', '', }}
Rq: il contient à la fois les étudiants qui vont être diplomés à la date prévue
et ceux qui sont éliminés (abandon, redoublement, ...) pour affichage alternatif
@ -812,7 +812,7 @@ class JuryPE:
self.syntheseJury[etudid] = {
"nom": etudinfo["nom"],
"prenom": etudinfo["prenom"],
"sexe": etudinfo["sexe"],
"civilite": etudinfo["civilite"],
"age": str(pe_tools.calcul_age(etudinfo["date_naissance"])),
"lycee": etudinfo["nomlycee"]
+ (
@ -985,7 +985,7 @@ class JuryPE:
[self.syntheseJury[etudid]["nbSemestres"] for etudid in etudids]
)
infos = ["sexe", "nom", "prenom", "age", "nbSemestres"]
infos = ["civilite", "nom", "prenom", "age", "nbSemestres"]
entete = ["etudid"]
entete.extend(infos)
entete.extend(["P%d" % i for i in range(1, maxParcours + 1)])
@ -1036,7 +1036,7 @@ class JuryPE:
# Les info générales:
row = {
"etudid": etudid,
"sexe": e["sexe"],
"civilite": e["civilite"],
"nom": e["nom"],
"prenom": e["prenom"],
"age": e["age"],

View File

@ -217,7 +217,7 @@ JURY_SYNTHESE_POUR_DEBUG = {
"EID1810": {
"nom": "ROUX",
"entree": "2016",
"sexe": "M.",
"civilite_str": "M.",
"promo": 2016,
"S2": {
"groupe": {

View File

@ -124,14 +124,14 @@ def formsemestre_bulletinetud_published_dict(
code_ine=etudinfo["code_ine"],
nom=scu.quote_xml_attr(etudinfo["nom"]),
prenom=scu.quote_xml_attr(etudinfo["prenom"]),
sexe=scu.quote_xml_attr(etudinfo["sexe"]),
civilite=scu.quote_xml_attr(etudinfo["civilite_str"]),
photo_url=scu.quote_xml_attr(
sco_photos.etud_photo_url(context, etudinfo, fast=True)
),
email=scu.quote_xml_attr(etudinfo["email"]),
emailperso=scu.quote_xml_attr(etudinfo["emailperso"]),
)
d["etudiant"]["sexe"] = d["etudiant"]["civilite"] # backward compat for our clients
# Disponible pour publication ?
if not published:
return d # stop !

View File

@ -107,7 +107,8 @@ def make_xml_formsemestre_bulletinetud(
code_ine=etudinfo["code_ine"],
nom=scu.quote_xml_attr(etudinfo["nom"]),
prenom=scu.quote_xml_attr(etudinfo["prenom"]),
sexe=scu.quote_xml_attr(etudinfo["sexe"]),
civilite=scu.quote_xml_attr(etudinfo["civilite_str"]),
sexe=scu.quote_xml_attr(etudinfo["civilite_str"]), # compat
photo_url=scu.quote_xml_attr(sco_photos.etud_photo_url(context, etudinfo)),
email=scu.quote_xml_attr(etudinfo["email"]),
emailperso=scu.quote_xml_attr(etudinfo["emailperso"]),

View File

@ -115,7 +115,7 @@ def table_debouche_etudids(context, etudids, keep_numeric=True):
)
row = {
"etudid": etudid,
"sexe": etud["sexe"],
"civilite": etud["civilite"],
"nom": etud["nom"],
"prenom": etud["prenom"],
"_nom_target": "ficheEtud?etudid=" + etud["etudid"],
@ -154,7 +154,7 @@ def table_debouche_etudids(context, etudids, keep_numeric=True):
L.sort(key=lambda x: x["sem_ident"])
titles = {
"sexe": "",
"civilite": "",
"nom": "Nom",
"prenom": "Prénom",
"semestre": "Dernier semestre",
@ -170,7 +170,7 @@ def table_debouche_etudids(context, etudids, keep_numeric=True):
"semestre",
"semestre_id",
"periode",
"sexe",
"civilite",
"nom",
"prenom",
"moy",

View File

@ -616,7 +616,7 @@ def Excel_feuille_listeappel(
li += 1
ws0.write(li, 0, n, style1b)
nomprenom = (
t["sexe"]
t["civilite_str"]
+ " "
+ t["nom"]
+ " "

View File

@ -122,7 +122,7 @@ def _build_results_list(context, dpv_by_sem, etuds_infos):
"code_nip": "NIP",
"nom": "Nom",
"prenom": "Prénom",
"sexe": "Civ.",
"civilite_str": "Civ.",
"nom_usuel": "Nom usuel",
"bac": "Bac",
"parcours": "Parcours",
@ -133,7 +133,7 @@ def _build_results_list(context, dpv_by_sem, etuds_infos):
"periode",
"sid",
"code_nip",
"sexe",
"civilite_str",
"nom",
# 'nom_usuel', # inutile ?
"prenom",
@ -171,7 +171,7 @@ def _build_results_list(context, dpv_by_sem, etuds_infos):
"nom": etud["nom"],
"nom_usuel": etud["nom_usuel"],
"prenom": etud["prenom"],
"sexe": etud["sexe"],
"civilite_str": etud["civilite_str"],
"_nom_target": "%s/ficheEtud?etudid=%s" % (context.ScoURL(), etudid),
"_nom_td_attrs": 'id="%s" class="etudinfo"' % etudid,
"bac": bac.abbrev(),

View File

@ -384,7 +384,7 @@ def search_inscr_etud_by_nip(context, code_nip, REQUEST=None, format="json"):
Seuls les départements accessibles par l'utilisateur sont cherchés.
Renvoie une liste des inscriptions de l'étudiants dans tout ScoDoc:
code_nip, nom, prenom, sexe, dept, formsemestre_id, date_debut_sem, date_fin_sem
code_nip, nom, prenom, civilite_str, dept, formsemestre_id, date_debut_sem, date_fin_sem
"""
result, _ = search_etud_in_accessible_depts(
context, code_nip=code_nip, REQUEST=REQUEST
@ -401,7 +401,7 @@ def search_inscr_etud_by_nip(context, code_nip, REQUEST=None, format="json"):
"dept": DeptId,
"etudid": e["etudid"],
"code_nip": e["code_nip"],
"sexe": e["sexe"],
"civilite_str": e["civilite_str"],
"nom": e["nom"],
"prenom": e["prenom"],
"formsemestre_id": sem["formsemestre_id"],
@ -414,7 +414,7 @@ def search_inscr_etud_by_nip(context, code_nip, REQUEST=None, format="json"):
"dept",
"etudid",
"code_nip",
"sexe",
"civilite_str",
"nom",
"prenom",
"formsemestre_id",

View File

@ -473,7 +473,8 @@ def XMLgetGroupsInPartition(context, partition_id, REQUEST=None): # was XMLgetG
doc._push()
doc.etud(
etudid=e["etudid"],
sexe=scolars.format_sexe(etud["sexe"]),
civilite=etud["civilite_str"],
sexe=etud["civilite_str"], # compat
nom=scolars.format_nom(etud["nom"]),
prenom=scolars.format_prenom(etud["prenom"]),
origin=comp_origin(etud, sem),
@ -497,7 +498,7 @@ def XMLgetGroupsInPartition(context, partition_id, REQUEST=None): # was XMLgetG
doc._push()
doc.etud(
etudid=etud["etudid"],
sexe=scolars.format_sexe(etud["sexe"]),
sexe=etud["civilite_str"],
nom=scolars.format_nom(etud["nom"]),
prenom=scolars.format_prenom(etud["prenom"]),
origin=comp_origin(etud, sem),
@ -1255,26 +1256,26 @@ def groups_auto_repartition(context, partition_id=None, REQUEST=None):
context.Notes, formsemestre_id
) # > identdict
identdict = nt.identdict
# build: { sexe : liste etudids trie par niveau croissant }
sexes = sets.Set([x["sexe"] for x in identdict.values()])
# build: { civilite : liste etudids trie par niveau croissant }
civilites = sets.Set([x["civilite"] for x in identdict.values()])
listes = {}
for sexe in sexes:
listes[sexe] = [
for civilite in civilites:
listes[civilite] = [
(get_prev_moy(context.Notes, x["etudid"], formsemestre_id), x["etudid"])
for x in identdict.values()
if x["sexe"] == sexe
if x["civilite"] == civilite
]
listes[sexe].sort()
log("listes[%s] = %s" % (sexe, listes[sexe]))
listes[civilite].sort()
log("listes[%s] = %s" % (civilite, listes[civilite]))
# affect aux groupes:
n = len(identdict)
igroup = 0
nbgroups = len(group_ids)
while n > 0:
for sexe in sexes:
if len(listes[sexe]):
for civilite in civilites:
if len(listes[civilite]):
n -= 1
etudid = listes[sexe].pop()[1]
etudid = listes[civilite].pop()[1]
group_id = group_ids[igroup]
igroup = (igroup + 1) % nbgroups
change_etud_group_in_partition(

View File

@ -464,8 +464,9 @@ def groups_table(
% (with_paiement, with_archives, with_annotations)
)
#
columns_ids = ["nom_disp", "prenom"] # colonnes a inclure
columns_ids = ["civilite_str", "nom_disp", "prenom"] # colonnes a inclure
titles = {
"civilite_str": "Civ.",
"nom_disp": "Nom",
"prenom": "Prénom",
"email": "Mail",
@ -733,7 +734,7 @@ def groups_table(
"etudid",
"code_nip",
"etat",
"sexe",
"civilite_str",
"nom",
"nom_usuel",
"prenom",

View File

@ -611,7 +611,7 @@ def etuds_select_boxes(
def etuds_select_box_xls(context, src_cat):
"export a box to excel"
etuds = src_cat["etuds"]
columns_ids = ["etudid", "sexe", "nom", "prenom", "etape"]
columns_ids = ["etudid", "civilite_str", "nom", "prenom", "etape"]
titles = {x: x for x in columns_ids}
# Ajoute colonne paiement inscription

View File

@ -128,7 +128,7 @@ def _table_etuds_lycees(
else:
L = etuds
columns_ids = (
"sexe",
"civilite_str",
"nom",
"prenom",
"codelycee",
@ -148,7 +148,7 @@ def _table_etuds_lycees(
rows=L,
titles={
"nbetuds": "Nb d'étudiants",
"sexe": "",
"civilite_str": "",
"nom": "Nom",
"prenom": "Prénom",
"etudid": "etudid",

View File

@ -44,7 +44,7 @@ import VERSION
def etud_get_poursuite_info(context, sem, etud):
"""{ 'nom' : ..., 'semlist' : [ { 'semestre_id': , 'moy' : ... }, {}, ...] }"""
I = {}
I.update(etud) # copie nom, prenom, sexe, ...
I.update(etud) # copie nom, prenom, civilite, ...
# Now add each semester, starting from the first one
semlist = []
@ -174,7 +174,7 @@ def formsemestre_poursuite_report(
infos.append(info)
#
column_ids = (
("sexe", "nom", "prenom", "annee", "date_naissance")
("civilite_str", "nom", "prenom", "annee", "date_naissance")
+ tuple(ids)
+ ("debouche",)
)

View File

@ -37,7 +37,7 @@ import sco_excel
import sco_formsemestre
import sco_parcours_dut
import sco_codes_parcours
from scolars import format_nom, format_prenom, format_sexe, format_lycee
from scolars import format_nom, format_prenom, format_civilite, format_lycee
from sco_abs import getAbsSemEtud
import VERSION
@ -229,7 +229,7 @@ def feuille_preparation_jury(context, formsemestre_id, REQUEST):
l.append(etud["code_ine"])
l += [
etudid,
format_sexe(etud["sexe"]),
etud["civilite_str"],
format_nom(etud["nom"]),
format_prenom(etud["prenom"]),
etud["date_naissance"],

View File

@ -844,7 +844,6 @@ def formsemestre_lettres_individuelles(
else:
# submit
sf = tf[2]["signature"]
# pdb.set_trace()
signature = sf.read() # image of signature
try:
PDFLOCK.acquire()

View File

@ -270,7 +270,7 @@ def make_formsemestre_recapcomplet(
)[0]
nt = context._getNotesCache().get_NotesTable(
context, formsemestre_id
) # > get_modimpls, get_ues, get_table_moyennes_triees, get_etud_decision_sem, get_etud_etat, get_etud_rang, get_nom_short, get_mod_stats, nt.moy_moy, get_nom_long, get_etud_decision_sem,
) # > get_modimpls, get_ues, get_table_moyennes_triees, get_etud_decision_sem, get_etud_etat, get_etud_rang, get_nom_short, get_mod_stats, nt.moy_moy, get_etud_decision_sem,
modimpls = nt.get_modimpls()
ues = nt.get_ues() # incluant le(s) UE de sport
#
@ -628,7 +628,6 @@ def make_formsemestre_recapcomplet(
"formsemestre_id": formsemestre_id,
"etudid": etudid,
"name": l[1],
"nomprenom": nt.get_nom_long(etudid),
}
if ir % 2 == 0:
cells = '<tr class="recap_row_even" id="etudid%s">' % etudid

View File

@ -293,7 +293,7 @@ def formsemestre_report_counts(
"codedecision",
"devenir",
"etat",
"sexe",
"civilite",
"qualite",
"villelycee",
"statut",
@ -365,7 +365,7 @@ def table_suivi_cohorte(
bac="", # selection sur type de bac
bacspecialite="",
annee_bac="",
sexe="",
civilite=None,
statut="",
only_primo=False,
):
@ -404,7 +404,7 @@ def table_suivi_cohorte(
bacs = set()
bacspecialites = set()
annee_bacs = set()
sexes = set()
civilites = set()
statuts = set()
for etudid in etudids:
etud = context.getEtudInfo(etudid=etudid, filled=True)[0]
@ -414,7 +414,7 @@ def table_suivi_cohorte(
(not bac or (bac == etud["bac"]))
and (not bacspecialite or (bacspecialite == bacspe))
and (not annee_bac or (annee_bac == str(etud["annee_bac"])))
and (not sexe or (sexe == etud["sexe"]))
and (not civilite or (civilite == etud["civilite"]))
and (not statut or (statut == etud["statut"]))
and (not only_primo or context.isPrimoEtud(etud, sem))
):
@ -426,7 +426,7 @@ def table_suivi_cohorte(
bacs.add(etud["bac"])
bacspecialites.add(bacspe)
annee_bacs.add(etud["annee_bac"])
sexes.add(etud["sexe"])
civilites.add(etud["civilite"])
if etud["statut"]: # ne montre pas les statuts non renseignés
statuts.add(etud["statut"])
sems = S.values()
@ -617,8 +617,8 @@ def table_suivi_cohorte(
dbac += " (spécialité %s)" % bacspecialite
if annee_bac:
dbac += " (année bac %s)" % annee_bac
if sexe:
dbac += " genre: %s" % sexe
if civilite:
dbac += " civilité: %s" % civilite
if statut:
dbac += " statut: %s" % statut
tab = GenTable(
@ -652,7 +652,15 @@ def table_suivi_cohorte(
expl.append(", ".join(ls) + "</li>")
expl.append("</ul>")
logt("Z: table_suivi_cohorte done")
return tab, "\n".join(expl), bacs, bacspecialites, annee_bacs, sexes, statuts
return (
tab,
"\n".join(expl),
bacs,
bacspecialites,
annee_bacs,
civilites,
statuts,
)
def formsemestre_suivi_cohorte(
@ -663,27 +671,35 @@ def formsemestre_suivi_cohorte(
bac="",
bacspecialite="",
annee_bac="",
sexe="",
civilite=None,
statut="",
only_primo=False,
REQUEST=None,
):
"""Affiche suivi cohortes par numero de semestre"""
percent = int(percent)
tab, expl, bacs, bacspecialites, annee_bacs, sexes, statuts = table_suivi_cohorte(
(
tab,
expl,
bacs,
bacspecialites,
annee_bacs,
civilites,
statuts,
) = table_suivi_cohorte(
context,
formsemestre_id,
percent=percent,
bac=bac,
bacspecialite=bacspecialite,
annee_bac=annee_bac,
sexe=sexe,
civilite=civilite,
statut=statut,
only_primo=only_primo,
)
tab.base_url = (
"%s?formsemestre_id=%s&amp;percent=%s&amp;bac=%s&amp;bacspecialite=%s&amp;sexe=%s"
% (REQUEST.URL0, formsemestre_id, percent, bac, bacspecialite, sexe)
"%s?formsemestre_id=%s&amp;percent=%s&amp;bac=%s&amp;bacspecialite=%s&amp;civilite=%s"
% (REQUEST.URL0, formsemestre_id, percent, bac, bacspecialite, civilite)
)
if only_primo:
tab.base_url += "&amp;only_primo=on"
@ -693,8 +709,8 @@ def formsemestre_suivi_cohorte(
base_url = REQUEST.URL0
burl = (
"%s?formsemestre_id=%s&amp;bac=%s&amp;bacspecialite=%s&amp;sexe=%s&amp;statut=%s"
% (base_url, formsemestre_id, bac, bacspecialite, sexe, statut)
"%s?formsemestre_id=%s&amp;bac=%s&amp;bacspecialite=%s&amp;civilite=%s&amp;statut=%s"
% (base_url, formsemestre_id, bac, bacspecialite, civilite, statut)
)
if percent:
pplink = (
@ -724,12 +740,12 @@ def formsemestre_suivi_cohorte(
bac=bac,
bacspecialite=bacspecialite,
annee_bac=annee_bac,
sexe=sexe,
civilite=civilite,
statut=statut,
bacs=bacs,
bacspecialites=bacspecialites,
annee_bacs=annee_bacs,
sexes=sexes,
civilites=civilites,
statuts=statuts,
percent=percent,
),
@ -749,12 +765,12 @@ def _gen_form_selectetuds(
bac=None,
bacspecialite=None,
annee_bac=None,
sexe=None,
civilite=None,
statut=None,
bacs=None,
bacspecialites=None,
annee_bacs=None,
sexes=None,
civilites=None,
statuts=None,
):
"""HTML form pour choix criteres selection etudiants"""
@ -764,8 +780,8 @@ def _gen_form_selectetuds(
bacspecialites.sort()
annee_bacs = list(annee_bacs)
annee_bacs.sort()
sexes = list(sexes)
sexes.sort()
civilites = list(civilites)
civilites.sort()
statuts = list(statuts)
statuts.sort()
#
@ -824,13 +840,13 @@ def _gen_form_selectetuds(
F.append("</select>")
#
F.append(
"""&nbsp; Genre: <select name="sexe" onchange="javascript: submit(this);">
"""&nbsp; Genre: <select name="civilite" onchange="javascript: submit(this);">
<option value="" %s>tous</option>
"""
% selected
)
for b in sexes:
if sexe == b:
for b in civilites:
if civilite == b:
selected = 'selected="selected"'
else:
selected = ""
@ -984,7 +1000,7 @@ def tsp_etud_list(
bac="", # selection sur type de bac
bacspecialite="",
annee_bac="",
sexe="",
civilite="",
statut="",
):
"""Liste des etuds a considerer dans table suivi parcours
@ -1000,7 +1016,7 @@ def tsp_etud_list(
bacs = set()
bacspecialites = set()
annee_bacs = set()
sexes = set()
civilites = set()
statuts = set()
for etudid in etudids:
etud = context.getEtudInfo(etudid=etudid, filled=True)[0]
@ -1010,7 +1026,7 @@ def tsp_etud_list(
(not bac or (bac == etud["bac"]))
and (not bacspecialite or (bacspecialite == bacspe))
and (not annee_bac or (annee_bac == str(etud["annee_bac"])))
and (not sexe or (sexe == etud["sexe"]))
and (not civilite or (civilite == etud["civilite"]))
and (not statut or (statut == etud["statut"]))
and (not only_primo or context.isPrimoEtud(etud, sem))
):
@ -1019,11 +1035,11 @@ def tsp_etud_list(
bacs.add(etud["bac"])
bacspecialites.add(bacspe)
annee_bacs.add(etud["annee_bac"])
sexes.add(etud["sexe"])
civilites.add(etud["civilite"])
if etud["statut"]: # ne montre pas les statuts non renseignés
statuts.add(etud["statut"])
# log('tsp_etud_list: %s etuds' % len(etuds))
return etuds, bacs, bacspecialites, annee_bacs, sexes, statuts
return etuds, bacs, bacspecialites, annee_bacs, civilites, statuts
def tsp_grouped_list(context, codes_etuds):
@ -1049,7 +1065,7 @@ def table_suivi_parcours(
):
"""Tableau recapitulant tous les parcours"""
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = tsp_etud_list(
etuds, bacs, bacspecialites, annee_bacs, civilites, statuts = tsp_etud_list(
context, formsemestre_id, only_primo=only_primo
)
codes_etuds = scu.DictDefault(defaultvalue=[])
@ -1065,7 +1081,7 @@ def table_suivi_parcours(
titles = {
"parcours": "Code parcours",
"nb": "Nombre d'étudiants",
"sexe": "",
"civilite": "",
"nom": "Nom",
"prenom": "Prénom",
"etudid": "etudid",
@ -1082,7 +1098,7 @@ def table_suivi_parcours(
L = etuds
columns_ids = (
"etudid",
"sexe",
"civilite",
"nom",
"prenom",
"bac",
@ -1209,25 +1225,25 @@ def graph_parcours(
bac="", # selection sur type de bac
bacspecialite="",
annee_bac="",
sexe="",
civilite="",
statut="",
):
""""""
if not scu.WITH_PYDOT:
raise ScoValueError("pydot module is not installed")
etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = tsp_etud_list(
etuds, bacs, bacspecialites, annee_bacs, civilites, statuts = tsp_etud_list(
context,
formsemestre_id,
only_primo=only_primo,
bac=bac,
bacspecialite=bacspecialite,
annee_bac=annee_bac,
sexe=sexe,
civilite=civilite,
statut=statut,
)
# log('graph_parcours: %s etuds (only_primo=%s)' % (len(etuds), only_primo))
if not etuds:
return "", etuds, bacs, bacspecialites, annee_bacs, sexes, statuts
return "", etuds, bacs, bacspecialites, annee_bacs, civilites, statuts
edges = scu.DictDefault(
defaultvalue=set()
) # {(formsemestre_id_origin, formsemestre_id_dest) : etud_set}
@ -1417,7 +1433,7 @@ def graph_parcours(
# cf http://groups.google.com/group/pydot/browse_thread/thread/b3704c53e331e2ec
data = data.replace("font-family:Arial", "font-family:Helvetica")
return data, etuds, bacs, bacspecialites, annee_bacs, sexes, statuts
return data, etuds, bacs, bacspecialites, annee_bacs, civilites, statuts
def formsemestre_graph_parcours(
@ -1428,7 +1444,7 @@ def formsemestre_graph_parcours(
bac="", # selection sur type de bac
bacspecialite="",
annee_bac="",
sexe="",
civilite="",
statut="",
allkeys=False, # unused
REQUEST=None,
@ -1437,7 +1453,15 @@ def formsemestre_graph_parcours(
# log("formsemestre_graph_parcours")
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
if format == "pdf":
doc, etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = graph_parcours(
(
doc,
etuds,
bacs,
bacspecialites,
annee_bacs,
civilites,
statuts,
) = graph_parcours(
context,
formsemestre_id,
format="pdf",
@ -1445,14 +1469,22 @@ def formsemestre_graph_parcours(
bac=bac,
bacspecialite=bacspecialite,
annee_bac=annee_bac,
sexe=sexe,
civilite=civilite,
statut=statut,
)
filename = scu.make_filename("flux " + sem["titreannee"])
return scu.sendPDFFile(REQUEST, doc, filename + ".pdf")
elif format == "png":
#
doc, etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = graph_parcours(
(
doc,
etuds,
bacs,
bacspecialites,
annee_bacs,
civilites,
statuts,
) = graph_parcours(
context,
formsemestre_id,
format="png",
@ -1460,7 +1492,7 @@ def formsemestre_graph_parcours(
bac=bac,
bacspecialite=bacspecialite,
annee_bac=annee_bac,
sexe=sexe,
civilite=civilite,
statut=statut,
)
filename = scu.make_filename("flux " + sem["titreannee"])
@ -1475,17 +1507,25 @@ def formsemestre_graph_parcours(
else:
op = ""
url = urllib.quote(
"formsemestre_graph_parcours?formsemestre_id=%s&amp;%sbac=%s&amp;bacspecialite=%s&amp;sexe=%s&amp;statut=%s&amp;format="
% (formsemestre_id, op, bac, bacspecialite, sexe, statut)
"formsemestre_graph_parcours?formsemestre_id=%s&amp;%sbac=%s&amp;bacspecialite=%s&amp;civilite=%s&amp;statut=%s&amp;format="
% (formsemestre_id, op, bac, bacspecialite, civilite, statut)
)
doc, etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = graph_parcours(
(
doc,
etuds,
bacs,
bacspecialites,
annee_bacs,
civilites,
statuts,
) = graph_parcours(
context,
formsemestre_id,
only_primo=only_primo,
bac=bac,
bacspecialite=bacspecialite,
annee_bac=annee_bac,
sexe=sexe,
civilite=civilite,
statut=statut,
)
@ -1505,12 +1545,12 @@ def formsemestre_graph_parcours(
bac=bac,
bacspecialite=bacspecialite,
annee_bac=annee_bac,
sexe=sexe,
civilite=civilite,
statut=statut,
bacs=bacs,
bacspecialites=bacspecialites,
annee_bacs=annee_bacs,
sexes=sexes,
civilites=civilites,
statuts=statuts,
percent=0,
),

View File

@ -408,7 +408,7 @@ def list_synch(context, sem, anneeapogee=None):
etud = etudsapo_ident[key]
etud["etudid"] = ""
etud["sexe"] = etud.get(
etud["civilite"] = etud.get(
"sexe", etud.get("gender", "")
) # la cle 'sexe' est prioritaire sur 'gender'
etud["inscrit"] = is_inscrit # checkbox state
@ -545,16 +545,14 @@ def formsemestre_synchro_etuds_help(context, sem):
)
def gender2sex(gender):
"""Le portail code en 'M', 'F', et ScoDoc en 'MR', 'MME'
Les F sont ici codées en MME
"""
if gender == "M":
return "MR"
elif gender == "F":
return "MME"
log('gender2sex: invalid value "%s", defaulting to "M"' % gender)
return "MR"
def gender2civilite(gender):
"""Le portail code en 'M', 'F', et ScoDoc en 'M', 'F', 'X'"""
if gender == "M" or gender == "F" or gender == "X":
return gender
elif not gender:
return "X"
log('gender2civilite: invalid value "%s", defaulting to "X"' % gender)
return "X" # "X" en général n'est pas affiché, donc bon choix si invalide
def get_opt_str(etud, k):
@ -598,7 +596,7 @@ def do_import_etuds_from_portal(context, sem, a_importer, etudsapo_ident, REQUES
"prenom": etud["prenom"].strip(),
# Les champs suivants sont facultatifs (pas toujours renvoyés par le portail)
"code_ine": etud.get("ine", "").strip(),
"sexe": gender2sex(etud["gender"].strip()),
"civilite": gender2civilite(etud["gender"].strip()),
"etape": etud.get("etape", None),
"email": etud.get("mail", "").strip(),
"emailperso": etud.get("mailperso", "").strip(),
@ -738,9 +736,9 @@ def do_import_etud_admission(
if x:
args[sco_field] = x
# Champs spécifiques:
sexe = gender2sex(etud["gender"].strip())
if sexe:
args["sexe"] = sexe
civilite = gender2civilite(etud["gender"].strip())
if civilite:
args["civilite"] = civilite
scolars.identite_edit_nocheck(cnx, args)

View File

@ -337,13 +337,7 @@ def _trombino_pdf(context, groups_infos, REQUEST):
[img],
[
Paragraph(
SU(
scolars.format_sexe(t["sexe"])
+ " "
+ scolars.format_prenom(t["prenom"])
+ " "
+ scolars.format_nom(t["nom"])
),
SU(scolars.format_nomprenom(t)),
StyleSheet["Normal"],
)
],
@ -422,13 +416,7 @@ def _listeappel_photos_pdf(context, groups_infos, REQUEST):
t = groups_infos.members[i]
img = _get_etud_platypus_image(context, t, image_width=PHOTOWIDTH)
txt = Paragraph(
SU(
scolars.format_sexe(t["sexe"])
+ " "
+ scolars.format_prenom(t["prenom"])
+ " "
+ scolars.format_nom(t["nom"])
),
SU(scolars.format_nomprenom(t)),
StyleSheet["Normal"],
)
if currow:

View File

@ -704,7 +704,7 @@ if WITH_PYDOT:
junk_graph = pydot.Dot("junk")
junk_graph.add_node(pydot.Node("a"))
n = junk_graph.get_node("a")
if type(n) == type([]):
if type(n) == type([]): # "modern" pydot
def pydot_get_node(g, name):
r = g.get_node(name)
@ -713,7 +713,7 @@ if WITH_PYDOT:
else:
return r[0]
else:
else: # very old pydot
def pydot_get_node(g, name):
return g.get_node(name)

View File

@ -101,7 +101,7 @@ def format_etud_ident(etud):
else:
etud["nom_usuel"] = ""
etud["prenom"] = format_prenom(etud["prenom"])
etud["sexe"] = format_sexe(etud["sexe"])
etud["civilite_str"] = format_civilite(etud["civilite"])
# Nom à afficher:
if etud["nom_usuel"]:
etud["nom_disp"] = etud["nom_usuel"]
@ -111,10 +111,12 @@ def format_etud_ident(etud):
etud["nom_disp"] = etud["nom"]
etud["nomprenom"] = format_nomprenom(etud) # M. Pierre DUPONT
if etud["sexe"] == "M.":
if etud["civilite"] == "M":
etud["ne"] = ""
else:
elif etud["civilite"] == "F":
etud["ne"] = "e"
else: # 'X'
etud["ne"] = "(e)"
# Mail à utiliser pour les envois vers l'étudiant:
# choix qui pourrait être controé par une preference
# ici priorité au mail institutionnel:
@ -128,14 +130,20 @@ def force_uppercase(s):
def format_nomprenom(etud):
"formatte sexe/nom/prenom pour affichages"
return " ".join(
[format_sexe(etud["sexe"]), format_prenom(etud["prenom"]), etud["nom_disp"]]
)
"""Formatte civilité/nom/prenom pour affichages: "M. Pierre Dupont" """
l = []
for x in [
format_civilite(etud["civilite"]),
format_prenom(etud["prenom"]),
etud.get("nom_disp", "") or etud.get("nom_usuel", "") or etud["nom"],
]:
if x:
l.append(x)
return " ".join(l)
def format_prenom(s):
"formatte prenom etudiant pour affichage"
"Formatte prenom etudiant pour affichage"
if not s:
return ""
frags = s.split()
@ -162,22 +170,33 @@ def format_nom(s, uppercase=True):
return format_prenom(s)
def format_sexe(sexe):
sexe = scu.strlower(sexe)
if sexe == "mr" or sexe == "m." or sexe == "m":
return "M."
else:
return "Mme"
def input_civilite(s):
"""Converts external representation of civilite to internal:
'M', 'F', or 'X' (and nothing else).
Raises valueError if conversion fails.
"""
s = scu.strupper(s).strip()
if s in ("M", "M.", "MR", "H"):
return "M"
elif s in ("F", "MLLE", "MLLE.", "MELLE", "MME"):
return "F"
elif s == "X" or not s:
return "X"
raise ValueError("valeur invalide pour la civilité: %s" % s)
def normalize_sexe(sexe):
"returns 'MR' ou 'MME'"
sexe = scu.strupper(sexe).strip()
if sexe in ("M.", "M", "MR", "H"):
return "MR"
elif sexe in ("MLLE", "MLLE.", "MELLE", "MME", "F"):
return "MME"
raise ValueError("valeur invalide pour le sexe: %s" % sexe)
def format_civilite(civilite):
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
personne ne souhaitant pas d'affichage)
"""
try:
return {
"M": "M.",
"F": "Mme",
"X": "",
}[civilite]
except KeyError:
raise ValueError("valeur invalide pour la civilité: %s" % civilite)
def format_lycee(nomlycee):
@ -241,7 +260,7 @@ _identiteEditor = EditableTable(
"nom",
"nom_usuel",
"prenom",
"sexe",
"civilite", # 'M", "F", or "X"
"date_naissance",
"lieu_naissance",
"dept_naissance",
@ -257,10 +276,10 @@ _identiteEditor = EditableTable(
input_formators={
"nom": force_uppercase,
"prenom": force_uppercase,
"sexe": force_uppercase,
"civilite": input_civilite,
"date_naissance": DateDMYtoISO,
},
output_formators={"date_naissance": DateISOtoDMY, "sexe": normalize_sexe},
output_formators={"date_naissance": DateISOtoDMY},
convert_null_outputs_to_empty=True,
allow_set_id=True, # car on specifie le code Apogee a la creation
)
@ -269,13 +288,14 @@ identite_delete = _identiteEditor.delete
def identite_list(cnx, *a, **kw):
"list, add 'annee_naissance'"
"""List, adding on the fly 'annee_naissance' and 'civilite_str' (M., Mme, "")."""
objs = _identiteEditor.list(cnx, *a, **kw)
for o in objs:
if o["date_naissance"]:
o["annee_naissance"] = int(o["date_naissance"].split("/")[2])
else:
o["annee_naissance"] = o["date_naissance"]
o["civilite_str"] = format_civilite(o["civilite"])
return objs
@ -372,7 +392,6 @@ def identite_edit(cnx, args, context=None, REQUEST=None):
if notify_to:
# etat AVANT edition pour envoyer diffs
before = identite_list(cnx, {"etudid": args["etudid"]})[0]
before["sexe"] = format_sexe(before["sexe"])
identite_edit_nocheck(cnx, args)
@ -380,7 +399,6 @@ def identite_edit(cnx, args, context=None, REQUEST=None):
if notify_to:
etud = context.getEtudInfo(etudid=args["etudid"], filled=True)[0]
after = identite_list(cnx, {"etudid": args["etudid"]})[0]
after["sexe"] = format_sexe(after["sexe"])
notify_etud_change(
context,
notify_to,
@ -412,7 +430,7 @@ def notify_etud_change(context, email_addr, etud, before, after, subject):
"""
txt = [
"Code NIP:" + etud["code_nip"],
"Genre: " + etud["sexe"],
"Civilité: " + etud["civilite_str"],
"Nom: " + etud["nom"],
"Prénom: " + etud["prenom"],
"Etudid: " + etud["etudid"],

View File

@ -3,12 +3,12 @@
"""Outils pour environnements de démo.
Change aléatoirement les identites (nip, sexe, nom, prenom) des étudiants d'un semestre.
Change aléatoirement les identites (nip, civilite, nom, prenom) des étudiants d'un semestre.
Le NIP est choisi aléatoirement (nombre entier à 8 chiffres).
Les noms et prénoms sont issus des fichiers noms.txt, prenoms-h.txt, prenoms-f.txt
(ce sont simlement les plus fréquemment rencontrés en France).
Le sexe est choisi aléatoirement 50-50.
La civilité est choisie aléatoirement 50-50 Homme/Femme.
"""
import sys
@ -54,17 +54,17 @@ cursor.execute(
wcursor = cnx.cursor()
n = 0
for (etudid,) in cursor:
sexe = random.choice(("M.", "MME"))
nom, prenom = nomprenom(sexe)
civilite = random.choice(("M", "F")) # pas de neutre, on pourrait ajouter 'X'
nom, prenom = nomprenom(civilite)
print(f"{etudid}: {nom}\t{prenom}")
args = {
"nom": nom,
"prenom": prenom,
"sexe": sexe,
"civilite": civilite,
"etudid": etudid,
"code_nip": random.randrange(10000000, 99999999),
}
req = "update identite set nom=%(nom)s, prenom=%(prenom)s, sexe=%(sexe)s where etudid=%(etudid)s"
req = "update identite set nom=%(nom)s, prenom=%(prenom)s, civilite=%(civilite)s where etudid=%(etudid)s"
# print( req % args)
wcursor.execute(req, args)
n += 1

View File

@ -10,14 +10,19 @@ cur_dir = Path(os.path.abspath(__file__)).parent
NOMS = [x.strip() for x in open(cur_dir / "noms.txt").readlines()]
PRENOMS_H = [x.strip() for x in open(cur_dir / "prenoms-h.txt").readlines()]
PRENOMS_F = [x.strip() for x in open(cur_dir / "prenoms-f.txt").readlines()]
PRENOMS_X = [x.strip() for x in open(cur_dir / "prenoms-x.txt").readlines()]
def nomprenom(sexe):
"""un nom et un prenom au hasard,
toujours en majuscules.
def nomprenom(civilite):
"""Un nom et un prenom au hasard,
toujours en majuscules. Pour tests et démos.
"""
if "e" in sexe.lower() or "f" in sexe.lower():
if civilite == "F":
prenom = random.choice(PRENOMS_F)
else:
elif civilite == "M":
prenom = random.choice(PRENOMS_H)
elif civilite == "X":
prenom = random.choice(PRENOMS_X)
else:
raise ValueError("civilite must be M, F or X")
return random.choice(NOMS).upper(), prenom.upper()

View File

@ -0,0 +1,10 @@
Camille
Sacha
Eden
Maxime
Morgan
Charlie
Alix
Claude
Ufuk
Lou

View File

@ -38,6 +38,7 @@ DEMODIR = sco_utils.SCO_SRCDIR + "/scotests/demo/"
NOMS = [x.strip() for x in open(DEMODIR + "/noms.txt").readlines()]
PRENOMS_H = [x.strip() for x in open(DEMODIR + "/prenoms-h.txt").readlines()]
PRENOMS_F = [x.strip() for x in open(DEMODIR + "/prenoms-f.txt").readlines()]
PRENOMS_X = [x.strip() for x in open(DEMODIR + "/prenoms-x.txt").readlines()]
# nb: en python2, les chaines ci-dessus sont en utf8
@ -66,16 +67,20 @@ class ScoFake:
sys.stderr.flush()
log("ScoFake: " + str(msg))
def sexenomprenom(self):
def civilitenomprenom(self):
"""un nom et un prenom au hasard,
toujours en majuscules.
"""
sexe = random.choice(("M", "F"))
if "e" in sexe.lower() or "f" in sexe.lower():
civilite = random.choice(("M", "M", "M", "F", "F", "F", "X"))
if civilite == "F":
prenom = random.choice(PRENOMS_F)
else:
elif civilite == "M":
prenom = random.choice(PRENOMS_H)
return sexe, random.choice(NOMS).upper(), prenom.upper()
elif civilite == "X":
prenom = random.choice(PRENOMS_X)
else:
raise ValueError("invalid civilite value")
return civilite, random.choice(NOMS).upper(), prenom.upper()
@logging_meth
def create_etud(
@ -85,7 +90,7 @@ class ScoFake:
nom="",
prenom="",
code_ine="",
sexe="",
civilite="",
etape="TST1",
email="test@localhost",
emailperso="perso@localhost",
@ -106,10 +111,10 @@ class ScoFake:
cnx = self.context.GetDBConnexion()
if code_nip == "":
code_nip = str(random.randint(10000, 99999))
if not sexe or not nom or not prenom:
r_sexe, r_nom, r_prenom = self.sexenomprenom()
if not sexe:
sexe = r_sexe
if not civilite or not nom or not prenom:
r_civilite, r_nom, r_prenom = self.civilitenomprenom()
if not civilite:
civilite = r_civilite
if not nom:
nom = r_nom
if not prenom: