forked from ScoDoc/ScoDoc
Ajout de l'option "Générer les moyennes par RCUEs (compétences)"
This commit is contained in:
parent
c2a66b607f
commit
b9b9a172c7
@ -45,10 +45,15 @@ class ParametrageClasseurPE(FlaskForm):
|
|||||||
default=True,
|
default=True,
|
||||||
render_kw={"checked": ""},
|
render_kw={"checked": ""},
|
||||||
)
|
)
|
||||||
moyennes_ues_rcues = BooleanField("Générer les moyennes par RCUEs (compétences)")
|
moyennes_ues_rcues = BooleanField(
|
||||||
|
"Générer les moyennes par RCUEs (compétences)",
|
||||||
|
default=True,
|
||||||
|
render_kw={"checked": ""},
|
||||||
|
)
|
||||||
|
|
||||||
min_max_moy = BooleanField("Afficher les colonnes min/max/moy")
|
min_max_moy = BooleanField("Afficher les colonnes min/max/moy")
|
||||||
synthese_individuelle_etud = BooleanField(
|
synthese_individuelle_etud = BooleanField(
|
||||||
"Générer la feuille synthèse avec un onglet par étudiant"
|
"Générer les synthèses HTML étudiant par étudiant"
|
||||||
)
|
)
|
||||||
|
|
||||||
submit = SubmitField("Générer les classeurs poursuites d'études")
|
submit = SubmitField("Générer les classeurs poursuites d'études")
|
||||||
|
@ -207,3 +207,28 @@ def repr_comp_et_ues(acronymes_ues_to_competences):
|
|||||||
liste += ["📍" + acro]
|
liste += ["📍" + acro]
|
||||||
aff_comp += [f" 💡{comp} (⇔ {', '.join(liste)})"]
|
aff_comp += [f" 💡{comp} (⇔ {', '.join(liste)})"]
|
||||||
return "\n".join(aff_comp)
|
return "\n".join(aff_comp)
|
||||||
|
|
||||||
|
|
||||||
|
def aff_rcsemxs_suivis_par_etudiants(etudiants):
|
||||||
|
"""Affiche les RCSemX (regroupement de SemX)
|
||||||
|
amenant un étudiant du S1 à un Sx"""
|
||||||
|
etudiants_ids = etudiants.etudiants_ids
|
||||||
|
jeunes = list(enumerate(etudiants_ids))
|
||||||
|
|
||||||
|
for no_etud, etudid in jeunes:
|
||||||
|
etat = "⛔" if etudid in etudiants.abandons_ids else "✅"
|
||||||
|
pe_print(f"-> {etat} {etudiants.identites[etudid].nomprenom} :")
|
||||||
|
for nom_rcs, rcs in etudiants.rcsemXs[etudid].items():
|
||||||
|
if rcs:
|
||||||
|
pe_print(f" > RCSemX ⏯️{nom_rcs}: {rcs.get_repr()}")
|
||||||
|
|
||||||
|
vides = []
|
||||||
|
for nom_rcs in pe_rcs.TOUS_LES_RCS:
|
||||||
|
les_rcssemX_suivis = []
|
||||||
|
for no_etud, etudid in jeunes:
|
||||||
|
if etudiants.rcsemXs[etudid][nom_rcs]:
|
||||||
|
les_rcssemX_suivis.append(etudiants.rcsemXs[etudid][nom_rcs])
|
||||||
|
if not les_rcssemX_suivis:
|
||||||
|
vides += [nom_rcs]
|
||||||
|
vides = sorted(list(set(vides)))
|
||||||
|
pe_print(f"⚠️ RCSemX vides : {', '.join(vides)}")
|
||||||
|
@ -69,6 +69,9 @@ class EtudiantsJuryPE:
|
|||||||
self.semXs: dict[int:dict] = {}
|
self.semXs: dict[int:dict] = {}
|
||||||
"""Les semXs (RCS de type Sx) suivis par chaque étudiant"""
|
"""Les semXs (RCS de type Sx) suivis par chaque étudiant"""
|
||||||
|
|
||||||
|
self.rcsemXs: dict[int:dict] = {}
|
||||||
|
"""Les RC de SemXs (RCS de type Sx, xA, xS) suivis par chaque étudiant"""
|
||||||
|
|
||||||
self.etudiants_diplomes = {}
|
self.etudiants_diplomes = {}
|
||||||
"""Les identités des étudiants à considérer au jury (ceux qui seront effectivement
|
"""Les identités des étudiants à considérer au jury (ceux qui seront effectivement
|
||||||
diplômés)"""
|
diplômés)"""
|
||||||
@ -274,6 +277,7 @@ class EtudiantsJuryPE:
|
|||||||
# Initialise ses trajectoires/SemX/RCSemX
|
# Initialise ses trajectoires/SemX/RCSemX
|
||||||
self.trajectoires[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_RCS}
|
self.trajectoires[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_RCS}
|
||||||
self.semXs[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_SEMESTRES}
|
self.semXs[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_SEMESTRES}
|
||||||
|
self.rcsemXs[etudid] = {aggregat: None for aggregat in pe_rcs.TOUS_LES_RCS}
|
||||||
|
|
||||||
def structure_cursus_etudiant(self, etudid: int):
|
def structure_cursus_etudiant(self, etudid: int):
|
||||||
"""Structure les informations sur les semestres suivis par un
|
"""Structure les informations sur les semestres suivis par un
|
||||||
|
@ -102,9 +102,8 @@ class JuryPE(object):
|
|||||||
"Nom du zip où ranger les fichiers générés"
|
"Nom du zip où ranger les fichiers générés"
|
||||||
|
|
||||||
# Les options
|
# Les options
|
||||||
|
|
||||||
self.options = options
|
self.options = options
|
||||||
"""Options de configuration"""
|
"""Options de configuration (cf. pe_sem_recap)"""
|
||||||
|
|
||||||
pe_affichage.pe_print(
|
pe_affichage.pe_print(
|
||||||
f"Données de poursuite d'étude générées le {time.strftime('%d/%m/%Y à %H:%M')}\n",
|
f"Données de poursuite d'étude générées le {time.strftime('%d/%m/%Y à %H:%M')}\n",
|
||||||
@ -350,8 +349,9 @@ class JuryPE(object):
|
|||||||
pe_affichage.pe_print(
|
pe_affichage.pe_print(
|
||||||
"""******************************************************************************"""
|
"""******************************************************************************"""
|
||||||
)
|
)
|
||||||
self.rcss_jury.cree_rcsemxs(self.etudiants)
|
self.rcss_jury.cree_rcsemxs(options=self.options)
|
||||||
self.rcss_jury._aff_rcsemxs_suivis(self.etudiants)
|
if "moyennes_ues_rcues" in self.options and self.options["moyennes_ues_rcues"]:
|
||||||
|
pe_affichage.aff_rcsemxs_suivis_par_etudiants(self.etudiants)
|
||||||
|
|
||||||
def _gen_xls_rcstags(self, zipfile: ZipFile):
|
def _gen_xls_rcstags(self, zipfile: ZipFile):
|
||||||
"""Génère les RCS taggués traduisant les moyennes (orientées compétences)
|
"""Génère les RCS taggués traduisant les moyennes (orientées compétences)
|
||||||
@ -384,6 +384,20 @@ class JuryPE(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
pe_affichage.pe_print("1) Calcul des moyennes des RCSTag", info=True)
|
pe_affichage.pe_print("1) Calcul des moyennes des RCSTag", info=True)
|
||||||
|
if not self.rcss_jury.rcsemxs:
|
||||||
|
if (
|
||||||
|
"moyennes_ues_rcues" in self.options
|
||||||
|
and not self.options["moyennes_ues_rcues"]
|
||||||
|
):
|
||||||
|
pe_affichage.pe_print(" -> Pas de RCSemX à calculer (cf. options)")
|
||||||
|
else:
|
||||||
|
pe_affichage.pe_print(
|
||||||
|
" -> Pas de RCSemX à calculer (alors qu'aucune option ne les limite) => problème"
|
||||||
|
)
|
||||||
|
self.rcsstags = {}
|
||||||
|
return
|
||||||
|
|
||||||
|
# Calcul des RCSTags sur la base des RCSemX
|
||||||
self.rcsstags = {}
|
self.rcsstags = {}
|
||||||
for rcs_id, rcsemx in self.rcss_jury.rcsemxs.items():
|
for rcs_id, rcsemx in self.rcss_jury.rcsemxs.items():
|
||||||
self.rcsstags[rcs_id] = pe_rcstag.RCSemXTag(
|
self.rcsstags[rcs_id] = pe_rcstag.RCSemXTag(
|
||||||
@ -431,20 +445,26 @@ class JuryPE(object):
|
|||||||
"""******************************************************************"""
|
"""******************************************************************"""
|
||||||
)
|
)
|
||||||
pe_affichage.pe_print(
|
pe_affichage.pe_print(
|
||||||
"""*** Génère les interclassements sur chaque type de RCS/agrgégat"""
|
"""*** Génère les interclassements sur chaque type de RCS/agrégat"""
|
||||||
)
|
)
|
||||||
pe_affichage.pe_print(
|
pe_affichage.pe_print(
|
||||||
"""******************************************************************"""
|
"""******************************************************************"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
"moyennes_ues_rcues" not in self.options
|
||||||
|
or self.options["moyennes_ues_rcues"]
|
||||||
|
):
|
||||||
self.interclasstags = {
|
self.interclasstags = {
|
||||||
pe_moytag.CODE_MOY_UE: {},
|
pe_moytag.CODE_MOY_UE: {},
|
||||||
pe_moytag.CODE_MOY_COMPETENCES: {},
|
pe_moytag.CODE_MOY_COMPETENCES: {},
|
||||||
}
|
}
|
||||||
|
else:
|
||||||
|
self.interclasstags = {pe_moytag.CODE_MOY_UE: {}}
|
||||||
|
|
||||||
etudiants_diplomes = self.etudiants.etudiants_diplomes
|
etudiants_diplomes = self.etudiants.etudiants_diplomes
|
||||||
|
|
||||||
# Les interclassements par UE
|
# Les interclassements par UE (toujours présents par défaut)
|
||||||
for Sx in pe_rcs.TOUS_LES_SEMESTRES:
|
for Sx in pe_rcs.TOUS_LES_SEMESTRES:
|
||||||
interclass = pe_interclasstag.InterClassTag(
|
interclass = pe_interclasstag.InterClassTag(
|
||||||
Sx,
|
Sx,
|
||||||
@ -457,6 +477,10 @@ class JuryPE(object):
|
|||||||
self.interclasstags[pe_moytag.CODE_MOY_UE][Sx] = interclass
|
self.interclasstags[pe_moytag.CODE_MOY_UE][Sx] = interclass
|
||||||
|
|
||||||
# Les interclassements par compétences
|
# Les interclassements par compétences
|
||||||
|
if (
|
||||||
|
"moyennes_ues_rcues" not in self.options
|
||||||
|
or self.options["moyennes_ues_rcues"]
|
||||||
|
):
|
||||||
for nom_rcs in pe_rcs.TOUS_LES_RCS:
|
for nom_rcs in pe_rcs.TOUS_LES_RCS:
|
||||||
interclass = pe_interclasstag.InterClassTag(
|
interclass = pe_interclasstag.InterClassTag(
|
||||||
nom_rcs,
|
nom_rcs,
|
||||||
@ -466,7 +490,9 @@ class JuryPE(object):
|
|||||||
self.rcsstags,
|
self.rcsstags,
|
||||||
self.rcss_jury.rcsemxs_suivis,
|
self.rcss_jury.rcsemxs_suivis,
|
||||||
)
|
)
|
||||||
self.interclasstags[pe_moytag.CODE_MOY_COMPETENCES][nom_rcs] = interclass
|
self.interclasstags[pe_moytag.CODE_MOY_COMPETENCES][
|
||||||
|
nom_rcs
|
||||||
|
] = interclass
|
||||||
|
|
||||||
# Intègre le bilan des aggrégats (interclassé par promo) au zip final
|
# Intègre le bilan des aggrégats (interclassé par promo) au zip final
|
||||||
output = io.BytesIO()
|
output = io.BytesIO()
|
||||||
@ -474,10 +500,9 @@ class JuryPE(object):
|
|||||||
output, engine="openpyxl"
|
output, engine="openpyxl"
|
||||||
) as writer:
|
) as writer:
|
||||||
onglets = []
|
onglets = []
|
||||||
for type_interclass in [
|
for (
|
||||||
pe_moytag.CODE_MOY_UE,
|
type_interclass
|
||||||
pe_moytag.CODE_MOY_COMPETENCES,
|
) in self.interclasstags: # Pour les types d'interclassements prévus
|
||||||
]:
|
|
||||||
interclasstag = self.interclasstags[type_interclass]
|
interclasstag = self.interclasstags[type_interclass]
|
||||||
for nom_rcs, interclass in interclasstag.items():
|
for nom_rcs, interclass in interclasstag.items():
|
||||||
if interclass.is_significatif():
|
if interclass.is_significatif():
|
||||||
@ -522,7 +547,7 @@ class JuryPE(object):
|
|||||||
|
|
||||||
tags = self._do_tags_list(self.interclasstags)
|
tags = self._do_tags_list(self.interclasstags)
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
for type_moy in [pe_moytag.CODE_MOY_UE, pe_moytag.CODE_MOY_COMPETENCES]:
|
for type_moy in self.interclasstags:
|
||||||
self.synthese[(tag, type_moy)] = self.df_tag_type(tag, type_moy)
|
self.synthese[(tag, type_moy)] = self.df_tag_type(tag, type_moy)
|
||||||
|
|
||||||
# Export des données => mode 1 seule feuille -> supprimé
|
# Export des données => mode 1 seule feuille -> supprimé
|
||||||
@ -555,7 +580,7 @@ class JuryPE(object):
|
|||||||
|
|
||||||
if onglets:
|
if onglets:
|
||||||
self.add_file_to_zip(
|
self.add_file_to_zip(
|
||||||
zipfile, f"synthese_jury_{self.diplome}_par_tag.xlsx", output.read()
|
zipfile, f"synthese_jury_{self.diplome}.xlsx", output.read()
|
||||||
)
|
)
|
||||||
|
|
||||||
def _gen_html_synthese_par_etudiant(self, zipfile: ZipFile):
|
def _gen_html_synthese_par_etudiant(self, zipfile: ZipFile):
|
||||||
@ -565,12 +590,18 @@ class JuryPE(object):
|
|||||||
pe_affichage.pe_print("*** Synthèse finale étudiant par étudiant", info=True)
|
pe_affichage.pe_print("*** Synthèse finale étudiant par étudiant", info=True)
|
||||||
pe_affichage.pe_print("**************************************************")
|
pe_affichage.pe_print("**************************************************")
|
||||||
|
|
||||||
|
if (
|
||||||
|
"moyennes_ues_rcues" not in self.options
|
||||||
|
or self.options["moyennes_ues_rcues"]
|
||||||
|
):
|
||||||
etudids = list(self.diplomes_ids)
|
etudids = list(self.diplomes_ids)
|
||||||
for etudid in etudids:
|
for etudid in etudids:
|
||||||
nom, prenom, html = self.synthetise_jury_etudiant(etudid)
|
nom, prenom, html = self.synthetise_jury_etudiant(etudid)
|
||||||
self.add_file_to_zip(
|
self.add_file_to_zip(
|
||||||
zipfile, f"{nom}_{prenom}.html", html, path="etudiants"
|
zipfile, f"{nom}_{prenom}.html", html, path="etudiants"
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
pe_affichage.pe_print(" > Pas de synthèse étudiant/étudiant possible/prévu")
|
||||||
|
|
||||||
def _add_log_to_zip(self, zipfile):
|
def _add_log_to_zip(self, zipfile):
|
||||||
"""Add a text file with the log messages"""
|
"""Add a text file with the log messages"""
|
||||||
|
@ -117,7 +117,7 @@ class RCSsJuryPE:
|
|||||||
self.semXs_suivis[etudid][agregat] = semX
|
self.semXs_suivis[etudid][agregat] = semX
|
||||||
self.etudiants.semXs[etudid][agregat] = semX
|
self.etudiants.semXs[etudid][agregat] = semX
|
||||||
|
|
||||||
def cree_rcsemxs(self, etudiants: pe_etudiant.EtudiantsJuryPE):
|
def cree_rcsemxs(self, options={"moyennes_ues_rcues": True}):
|
||||||
"""Créé tous les RCSemXs, au regard du cursus des étudiants
|
"""Créé tous les RCSemXs, au regard du cursus des étudiants
|
||||||
analysés (trajectoires traduisant son parcours dans les
|
analysés (trajectoires traduisant son parcours dans les
|
||||||
différents semestres) + les mémorise dans les données de l'étudiant
|
différents semestres) + les mémorise dans les données de l'étudiant
|
||||||
@ -125,6 +125,11 @@ class RCSsJuryPE:
|
|||||||
self.rcsemxs_suivis = {}
|
self.rcsemxs_suivis = {}
|
||||||
self.rcsemxs = {}
|
self.rcsemxs = {}
|
||||||
|
|
||||||
|
if "moyennes_ues_rcues" in options and options["moyennes_ues_rcues"] == False:
|
||||||
|
# Pas de RCSemX généré
|
||||||
|
pe_affichage.pe_print("⚠️ Pas de RCSemX générés")
|
||||||
|
return
|
||||||
|
|
||||||
# Pour tous les étudiants du jury
|
# Pour tous les étudiants du jury
|
||||||
pas_de_semestres = []
|
pas_de_semestres = []
|
||||||
for etudid in self.trajectoires_suivies:
|
for etudid in self.trajectoires_suivies:
|
||||||
@ -132,20 +137,11 @@ class RCSsJuryPE:
|
|||||||
nom_rcs: None for nom_rcs in pe_rcs.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
|
nom_rcs: None for nom_rcs in pe_rcs.TOUS_LES_RCS_AVEC_PLUSIEURS_SEM
|
||||||
}
|
}
|
||||||
|
|
||||||
# Recopie des SemX & des suivis associés => est-ce utile ?
|
# Pour chaque aggréggat de type xA ou Sx ou xS
|
||||||
# for nom_rcs in pe_rcs.TOUS_LES_SEMESTRES:
|
for agregat in pe_rcs.TOUS_LES_RCS:
|
||||||
# trajectoire = self.semXs_suivis[etudid][nom_rcs]
|
trajectoire = self.trajectoires_suivies[etudid][agregat]
|
||||||
# if trajectoire:
|
|
||||||
# self.rcsemxs[trajectoire.rcs_id] = trajectoire
|
|
||||||
# self.rcsemxs_suivis[etudid][nom_rcs] = trajectoire
|
|
||||||
|
|
||||||
# Pour chaque aggréggat de type xA ou Sx
|
|
||||||
tous_les_agregats = pe_rcs.TOUS_LES_RCS
|
|
||||||
|
|
||||||
for nom_rcs in tous_les_agregats:
|
|
||||||
trajectoire = self.trajectoires_suivies[etudid][nom_rcs]
|
|
||||||
if not trajectoire:
|
if not trajectoire:
|
||||||
self.rcsemxs_suivis[etudid][nom_rcs] = None
|
self.rcsemxs_suivis[etudid][agregat] = None
|
||||||
else:
|
else:
|
||||||
# Identifiant de la trajectoire => donnera ceux du RCSemX
|
# Identifiant de la trajectoire => donnera ceux du RCSemX
|
||||||
tid = trajectoire.rcs_id
|
tid = trajectoire.rcs_id
|
||||||
@ -159,14 +155,14 @@ class RCSsJuryPE:
|
|||||||
# Par ex: dans S1+S2+S1+S2+S3 => les 2 S1 devient le SemX('S1'), les 2 S2 le SemX('S2'), etc..
|
# Par ex: dans S1+S2+S1+S2+S3 => les 2 S1 devient le SemX('S1'), les 2 S2 le SemX('S2'), etc..
|
||||||
|
|
||||||
# Les Sx pris en compte dans l'aggrégat
|
# Les Sx pris en compte dans l'aggrégat
|
||||||
noms_sems_aggregat = pe_rcs.TYPES_RCS[nom_rcs]["aggregat"]
|
noms_sems_aggregat = pe_rcs.TYPES_RCS[agregat]["aggregat"]
|
||||||
|
|
||||||
semxs_a_aggreger = {}
|
semxs_a_aggreger = {}
|
||||||
for Sx in noms_sems_aggregat:
|
for Sx in noms_sems_aggregat:
|
||||||
semestres_etudiants = etudiants.cursus[etudid][Sx]
|
semestres_etudiants = self.etudiants.cursus[etudid][Sx]
|
||||||
if not semestres_etudiants:
|
if not semestres_etudiants:
|
||||||
pas_de_semestres += [
|
pas_de_semestres += [
|
||||||
f"{Sx} pour {etudiants.identites[etudid].nomprenom}"
|
f"{Sx} pour {self.etudiants.identites[etudid].nomprenom}"
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
semx_id = get_semx_from_semestres_aggreges(
|
semx_id = get_semx_from_semestres_aggreges(
|
||||||
@ -184,7 +180,8 @@ class RCSsJuryPE:
|
|||||||
rcsemx.add_semXs(semxs_a_aggreger)
|
rcsemx.add_semXs(semxs_a_aggreger)
|
||||||
|
|
||||||
# Mémoire du RCSemX aux informations de suivi de l'étudiant
|
# Mémoire du RCSemX aux informations de suivi de l'étudiant
|
||||||
self.rcsemxs_suivis[etudid][nom_rcs] = rcsemx
|
self.rcsemxs_suivis[etudid][agregat] = rcsemx
|
||||||
|
self.etudiants.rcsemXs[etudid][agregat] = rcsemx
|
||||||
|
|
||||||
# Affichage des étudiants pour lesquels il manque un semestre
|
# Affichage des étudiants pour lesquels il manque un semestre
|
||||||
pas_de_semestres = sorted(set(pas_de_semestres))
|
pas_de_semestres = sorted(set(pas_de_semestres))
|
||||||
@ -194,30 +191,6 @@ class RCSsJuryPE:
|
|||||||
"\n".join([" " * 10 + psd for psd in pas_de_semestres])
|
"\n".join([" " * 10 + psd for psd in pas_de_semestres])
|
||||||
)
|
)
|
||||||
|
|
||||||
def _aff_rcsemxs_suivis(self, etudiants):
|
|
||||||
"""Affiche les RCSemX suivis par les étudiants"""
|
|
||||||
# Affichage pour debug
|
|
||||||
jeunes = list(enumerate(self.rcsemxs_suivis.keys()))
|
|
||||||
for no_etud, etudid in jeunes:
|
|
||||||
etat = "⛔" if etudid in etudiants.abandons_ids else "✅"
|
|
||||||
pe_affichage.pe_print(
|
|
||||||
f"-> {etat} {etudiants.identites[etudid].nomprenom} :"
|
|
||||||
)
|
|
||||||
for nom_rcs, rcs in self.rcsemxs_suivis[etudid].items():
|
|
||||||
if rcs:
|
|
||||||
pe_affichage.pe_print(f" > RCSemX ⏯️{nom_rcs}: {rcs.get_repr()}")
|
|
||||||
|
|
||||||
vides = []
|
|
||||||
for nom_rcs in pe_rcs.TOUS_LES_RCS:
|
|
||||||
les_rcssemX_suivis = []
|
|
||||||
for no_etud, etudid in jeunes:
|
|
||||||
if self.rcsemxs_suivis[etudid][nom_rcs]:
|
|
||||||
les_rcssemX_suivis.append(self.rcsemxs_suivis[etudid][nom_rcs])
|
|
||||||
if not les_rcssemX_suivis:
|
|
||||||
vides += [nom_rcs]
|
|
||||||
vides = sorted(list(set(vides)))
|
|
||||||
pe_affichage.pe_print(f"⚠️ RCSemX vides : {', '.join(vides)}")
|
|
||||||
|
|
||||||
|
|
||||||
def get_rcs_etudiant(
|
def get_rcs_etudiant(
|
||||||
semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str
|
semestres: dict[int:FormSemestre], formsemestre_final: FormSemestre, nom_rcs: str
|
||||||
|
Loading…
Reference in New Issue
Block a user