forked from ScoDoc/ScoDoc
Compare commits
6 Commits
fd597b87d4
...
4c28d140a6
Author | SHA1 | Date | |
---|---|---|---|
|
4c28d140a6 | ||
|
06844380ad | ||
d000e7b5a7 | |||
5379e4b93a | |||
7b0eae1970 | |||
6d06242cbb |
@ -8,51 +8,12 @@ from app.api import bp
|
|||||||
from app.api.errors import error_response
|
from app.api.errors import error_response
|
||||||
from app.api.auth import token_auth, token_permission_required
|
from app.api.auth import token_auth, token_permission_required
|
||||||
from app.api.tools import get_etu_from_request
|
from app.api.tools import get_etu_from_request
|
||||||
from app.scodoc.sco_bulletins_json import make_json_formsemestre_bulletinetud
|
from app.models import FormSemestreInscription, FormSemestre, Identite
|
||||||
|
from app.scodoc.sco_bulletins import get_formsemestre_bulletin_etud_json
|
||||||
from app.scodoc.sco_groups import get_etud_groups
|
from app.scodoc.sco_groups import get_etud_groups
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/etudiants", methods=["GET"])
|
|
||||||
@token_permission_required(Permission.APIView)
|
|
||||||
def etudiants():
|
|
||||||
"""
|
|
||||||
Retourne la liste de tous les étudiants
|
|
||||||
|
|
||||||
Exemple de résultat :
|
|
||||||
{
|
|
||||||
"civilite": "X",
|
|
||||||
"code_ine": null,
|
|
||||||
"code_nip": null,
|
|
||||||
"date_naissance": null,
|
|
||||||
"email": null,
|
|
||||||
"emailperso": null,
|
|
||||||
"etudid": 18,
|
|
||||||
"nom": "MOREL",
|
|
||||||
"prenom": "JACQUES"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"civilite": "X",
|
|
||||||
"code_ine": null,
|
|
||||||
"code_nip": null,
|
|
||||||
"date_naissance": null,
|
|
||||||
"email": null,
|
|
||||||
"emailperso": null,
|
|
||||||
"etudid": 19,
|
|
||||||
"nom": "FOURNIER",
|
|
||||||
"prenom": "ANNE"
|
|
||||||
},
|
|
||||||
...
|
|
||||||
"""
|
|
||||||
# Récupération de tous les étudiants
|
|
||||||
etu = models.Identite.query.all()
|
|
||||||
|
|
||||||
# Mise en forme des données
|
|
||||||
data = [d.to_dict_bul(include_urls=False) for d in etu]
|
|
||||||
|
|
||||||
return jsonify(data)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/etudiants/courant", methods=["GET"])
|
@bp.route("/etudiants/courant", methods=["GET"])
|
||||||
@token_permission_required(Permission.APIView)
|
@token_permission_required(Permission.APIView)
|
||||||
def etudiants_courant():
|
def etudiants_courant():
|
||||||
@ -85,13 +46,13 @@ def etudiants_courant():
|
|||||||
...
|
...
|
||||||
"""
|
"""
|
||||||
# Récupération de tous les étudiants
|
# Récupération de tous les étudiants
|
||||||
etus = models.Identite.query.all()
|
etus = Identite.query.filter(
|
||||||
|
Identite.id == FormSemestreInscription.etudid,
|
||||||
|
FormSemestreInscription.formsemestre_id == FormSemestre.id,
|
||||||
|
FormSemestre.date_debut <= app.db.func.now(),
|
||||||
|
FormSemestre.date_fin >= app.db.func.now())
|
||||||
|
|
||||||
data = []
|
data = [etu.to_dict_bul(include_urls=False) for etu in etus]
|
||||||
# Récupère uniquement les étudiants courant
|
|
||||||
for etu in etus:
|
|
||||||
if etu.inscription_courante() is not None:
|
|
||||||
data.append(etu.to_dict_bul(include_urls=False))
|
|
||||||
|
|
||||||
return jsonify(data)
|
return jsonify(data)
|
||||||
|
|
||||||
@ -190,7 +151,7 @@ def etudiant_formsemestres(etudid: int = None, nip: int = None, ine: int = None)
|
|||||||
for formsemestre_inscription in formsemestres_inscriptions:
|
for formsemestre_inscription in formsemestres_inscriptions:
|
||||||
res = models.FormSemestre.query.filter_by(
|
res = models.FormSemestre.query.filter_by(
|
||||||
id=formsemestre_inscription.formsemestre_id
|
id=formsemestre_inscription.formsemestre_id
|
||||||
).first()
|
).first_or_404()
|
||||||
formsemestres.append(res)
|
formsemestres.append(res)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
@ -227,25 +188,23 @@ def etudiant_bulletin_semestre(
|
|||||||
"""
|
"""
|
||||||
# Fonction utilisée : app.scodoc.sco_bulletins_json.make_json_formsemestre_bulletinetud()
|
# Fonction utilisée : app.scodoc.sco_bulletins_json.make_json_formsemestre_bulletinetud()
|
||||||
|
|
||||||
formsemestre = models.FormSemestre.query.filter_by(id=formsemestre_id).first()
|
formsemestre = models.FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
|
||||||
|
|
||||||
dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first()
|
dept = models.Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
|
||||||
|
|
||||||
app.set_sco_dept(dept.acronym)
|
app.set_sco_dept(dept.acronym)
|
||||||
|
|
||||||
if etudid is None:
|
# Récupération de l'étudiant
|
||||||
# Récupération de l'étudiant
|
try:
|
||||||
try:
|
etu = get_etu_from_request(etudid, nip, ine)
|
||||||
etu = get_etu_from_request(etudid, nip, ine)
|
except AttributeError:
|
||||||
etudid = etu.etudid
|
return error_response(
|
||||||
except AttributeError:
|
409,
|
||||||
return error_response(
|
message="La requête ne peut être traitée en l’état actuel.\n "
|
||||||
409,
|
"Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
|
||||||
message="La requête ne peut être traitée en l’état actuel.\n "
|
)
|
||||||
"Veilliez vérifier que l'id de l'étudiant (etudid, nip, ine) est valide",
|
|
||||||
)
|
|
||||||
|
|
||||||
return make_json_formsemestre_bulletinetud(formsemestre_id, etudid)
|
return get_formsemestre_bulletin_etud_json(formsemestre, etu)
|
||||||
|
|
||||||
|
|
||||||
@bp.route(
|
@bp.route(
|
||||||
@ -311,7 +270,7 @@ def etudiant_groups(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Récupération du formsemestre
|
# Récupération du formsemestre
|
||||||
sem = models.FormSemestre.query.filter_by(id=formsemestre_id).first()
|
sem = models.FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
|
||||||
try:
|
try:
|
||||||
# Utilisation de la fonction get_etud_groups
|
# Utilisation de la fonction get_etud_groups
|
||||||
data = get_etud_groups(etudid, sem.id)
|
data = get_etud_groups(etudid, sem.id)
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
"""ScoDoc 9 models : Référentiel Compétence BUT 2021
|
"""ScoDoc 9 models : Référentiel Compétence BUT 2021
|
||||||
"""
|
"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import unique
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from sqlalchemy.orm import class_mapper
|
from sqlalchemy.orm import class_mapper
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
@ -28,6 +26,8 @@ def attribute_names(cls):
|
|||||||
|
|
||||||
|
|
||||||
class XMLModel:
|
class XMLModel:
|
||||||
|
"""Mixin class, to ease loading Orebut XMLs"""
|
||||||
|
|
||||||
_xml_attribs = {} # to be overloaded
|
_xml_attribs = {} # to be overloaded
|
||||||
id = "_"
|
id = "_"
|
||||||
|
|
||||||
@ -162,6 +162,7 @@ class ApcCompetence(db.Model, XMLModel):
|
|||||||
|
|
||||||
|
|
||||||
class ApcSituationPro(db.Model, XMLModel):
|
class ApcSituationPro(db.Model, XMLModel):
|
||||||
|
"Situation professionnelle"
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
competence_id = db.Column(
|
competence_id = db.Column(
|
||||||
db.Integer, db.ForeignKey("apc_competence.id"), nullable=False
|
db.Integer, db.ForeignKey("apc_competence.id"), nullable=False
|
||||||
@ -173,6 +174,7 @@ class ApcSituationPro(db.Model, XMLModel):
|
|||||||
|
|
||||||
|
|
||||||
class ApcComposanteEssentielle(db.Model, XMLModel):
|
class ApcComposanteEssentielle(db.Model, XMLModel):
|
||||||
|
"Composante essentielle"
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
competence_id = db.Column(
|
competence_id = db.Column(
|
||||||
db.Integer, db.ForeignKey("apc_competence.id"), nullable=False
|
db.Integer, db.ForeignKey("apc_competence.id"), nullable=False
|
||||||
@ -199,6 +201,9 @@ class ApcNiveau(db.Model, XMLModel):
|
|||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__} ordre={self.ordre}>"
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
"libelle": self.libelle,
|
"libelle": self.libelle,
|
||||||
@ -222,14 +227,14 @@ class ApcAppCritique(db.Model, XMLModel):
|
|||||||
backref=db.backref("app_critiques", lazy="dynamic"),
|
backref=db.backref("app_critiques", lazy="dynamic"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self) -> dict:
|
||||||
return {"libelle": self.libelle}
|
return {"libelle": self.libelle}
|
||||||
|
|
||||||
def get_label(self):
|
def get_label(self) -> str:
|
||||||
return self.code + " - " + self.titre
|
return self.code + " - " + self.titre
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<AppCritique {}>".format(self.code)
|
return f"<{self.__class__.__name__} {self.code}>"
|
||||||
|
|
||||||
def get_saes(self):
|
def get_saes(self):
|
||||||
"""Liste des SAE associées"""
|
"""Liste des SAE associées"""
|
||||||
@ -258,6 +263,9 @@ class ApcParcours(db.Model, XMLModel):
|
|||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__} {self.code}>"
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
"code": self.code,
|
"code": self.code,
|
||||||
@ -274,6 +282,9 @@ class ApcAnneeParcours(db.Model, XMLModel):
|
|||||||
)
|
)
|
||||||
ordre = db.Column(db.Integer)
|
ordre = db.Column(db.Integer)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__} ordre={self.ordre}>"
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
"ordre": self.ordre,
|
"ordre": self.ordre,
|
||||||
@ -320,3 +331,6 @@ class ApcParcoursNiveauCompetence(db.Model):
|
|||||||
cascade="save-update, merge, delete, delete-orphan",
|
cascade="save-update, merge, delete, delete-orphan",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__} {self.competence} {self.annee_parcours}>"
|
||||||
|
@ -7,7 +7,6 @@ from app.comp import df_cache
|
|||||||
from app.models import SHORT_STR_LEN
|
from app.models import SHORT_STR_LEN
|
||||||
from app.models.modules import Module
|
from app.models.modules import Module
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import UniteEns
|
||||||
from app.scodoc import notesdb as ndb
|
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_codes_parcours
|
from app.scodoc import sco_codes_parcours
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
@ -32,7 +32,7 @@ import email
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask import flash, render_template, url_for
|
from flask import flash, jsonify, render_template, url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from app import email
|
from app import email
|
||||||
@ -76,6 +76,34 @@ from app.scodoc import sco_bulletins_legacy
|
|||||||
from app.scodoc import sco_bulletins_ucac # format expérimental UCAC Cameroun
|
from app.scodoc import sco_bulletins_ucac # format expérimental UCAC Cameroun
|
||||||
|
|
||||||
|
|
||||||
|
def get_formsemestre_bulletin_etud_json(
|
||||||
|
formsemestre: FormSemestre,
|
||||||
|
etud: Identite,
|
||||||
|
force_publishing=False,
|
||||||
|
version="long",
|
||||||
|
) -> str:
|
||||||
|
"""Le JSON du bulletin d'un étudiant, quel que soit le type de formation."""
|
||||||
|
if formsemestre.formation.is_apc():
|
||||||
|
r = bulletin_but.BulletinBUT(formsemestre)
|
||||||
|
return jsonify(
|
||||||
|
r.bulletin_etud(
|
||||||
|
etud,
|
||||||
|
formsemestre,
|
||||||
|
force_publishing=force_publishing,
|
||||||
|
version=version,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return formsemestre_bulletinetud(
|
||||||
|
etud,
|
||||||
|
formsemestre_id=formsemestre.id,
|
||||||
|
format="json",
|
||||||
|
version=version,
|
||||||
|
xml_with_decisions=True,
|
||||||
|
force_publishing=force_publishing,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# -------------
|
||||||
def make_context_dict(formsemestre: FormSemestre, etud: dict) -> dict:
|
def make_context_dict(formsemestre: FormSemestre, etud: dict) -> dict:
|
||||||
"""Construit dictionnaire avec valeurs pour substitution des textes
|
"""Construit dictionnaire avec valeurs pour substitution des textes
|
||||||
(preferences bul_pdf_*)
|
(preferences bul_pdf_*)
|
||||||
@ -799,7 +827,10 @@ def formsemestre_bulletinetud(
|
|||||||
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
||||||
prefer_mail_perso=False,
|
prefer_mail_perso=False,
|
||||||
):
|
):
|
||||||
"page bulletin de notes"
|
"""Page bulletin de notes
|
||||||
|
pour les formations classiques hors BUT (page HTML)
|
||||||
|
ou le format "oldjson".
|
||||||
|
"""
|
||||||
format = format or "html"
|
format = format or "html"
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||||
if not formsemestre:
|
if not formsemestre:
|
||||||
|
@ -34,14 +34,14 @@
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
let div = document.createElement("div");
|
let div = document.createElement("div");
|
||||||
div.innerText = "Une erreur s'est produite lors du transfère des données.";
|
div.innerText = "Une erreur s'est produite lors du transfert des données.";
|
||||||
div.style.fontSize = "24px";
|
div.style.fontSize = "24px";
|
||||||
div.style.color = "#d93030";
|
div.style.color = "#d93030";
|
||||||
|
|
||||||
let releve = document.querySelector("releve-but");
|
let releve = document.querySelector("releve-but");
|
||||||
releve.after(div);
|
releve.after(div);
|
||||||
releve.remove();
|
releve.remove();
|
||||||
|
|
||||||
throw 'Fin du script - données invalides';
|
throw 'Fin du script - données invalides';
|
||||||
});
|
});
|
||||||
document.querySelector("html").style.scrollBehavior = "smooth";
|
document.querySelector("html").style.scrollBehavior = "smooth";
|
||||||
|
@ -13,14 +13,13 @@
|
|||||||
<script src="/ScoDoc/static/js/ref_competences.js"></script>
|
<script src="/ScoDoc/static/js/ref_competences.js"></script>
|
||||||
|
|
||||||
<div class="help">
|
<div class="help">
|
||||||
Référentiel chargé le {{ref.scodoc_date_loaded.strftime("%d/%m/%Y à %H:%M") if ref.scodoc_date_loaded else ""}} à partir du fichier <tt>{{ref.scodoc_orig_filename or "(inconnu)"}}</tt>.
|
Référentiel chargé le {{ref.scodoc_date_loaded.strftime("%d/%m/%Y à %H:%M") if ref.scodoc_date_loaded else ""}} à
|
||||||
|
partir du fichier <tt>{{ref.scodoc_orig_filename or "(inconnu)"}}</tt>.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="part2">
|
<div class="part2">
|
||||||
<a class="stdlink"
|
<a class="stdlink" href="{{url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept)}}">liste des référentiels</a>
|
||||||
href="{{url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept)}}"
|
|
||||||
>revenir à la liste des référentiels</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -312,53 +312,42 @@ def formsemestre_bulletinetud(
|
|||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"Paramètre manquant: spécifier etudid, code_nip ou code_ine"
|
"Paramètre manquant: spécifier etudid, code_nip ou code_ine"
|
||||||
)
|
)
|
||||||
if formsemestre.formation.is_apc() and format != "oldjson":
|
if format == "json":
|
||||||
if format == "json":
|
return sco_bulletins.get_formsemestre_bulletin_etud_json(
|
||||||
r = bulletin_but.BulletinBUT(formsemestre)
|
formsemestre, etud, version=version
|
||||||
return jsonify(
|
|
||||||
r.bulletin_etud(
|
|
||||||
etud,
|
|
||||||
formsemestre,
|
|
||||||
force_publishing=force_publishing,
|
|
||||||
version=version,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif format == "html":
|
|
||||||
return render_template(
|
|
||||||
"but/bulletin.html",
|
|
||||||
appreciations=models.BulAppreciations.query.filter_by(
|
|
||||||
etudid=etudid, formsemestre_id=formsemestre.id
|
|
||||||
).order_by(models.BulAppreciations.date),
|
|
||||||
bul_url=url_for(
|
|
||||||
"notes.formsemestre_bulletinetud",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
formsemestre_id=formsemestre_id,
|
|
||||||
etudid=etudid,
|
|
||||||
format="json",
|
|
||||||
force_publishing=1, # pour ScoDoc lui même
|
|
||||||
version=version,
|
|
||||||
),
|
|
||||||
can_edit_appreciations=formsemestre.est_responsable(current_user)
|
|
||||||
or (current_user.has_permission(Permission.ScoEtudInscrit)),
|
|
||||||
etud=etud,
|
|
||||||
formsemestre=formsemestre,
|
|
||||||
inscription_courante=etud.inscription_courante(),
|
|
||||||
inscription_str=etud.inscription_descr()["inscription_str"],
|
|
||||||
is_apc=formsemestre.formation.is_apc(),
|
|
||||||
menu_autres_operations=sco_bulletins.make_menu_autres_operations(
|
|
||||||
formsemestre, etud, "notes.formsemestre_bulletinetud", version
|
|
||||||
),
|
|
||||||
sco=ScoData(etud=etud),
|
|
||||||
scu=scu,
|
|
||||||
time=time,
|
|
||||||
title=f"Bul. {etud.nom} - BUT",
|
|
||||||
version=version,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not (etudid or code_nip or code_ine):
|
|
||||||
raise ScoValueError(
|
|
||||||
"Paramètre manquant: spécifier code_nip ou etudid ou code_ine"
|
|
||||||
)
|
)
|
||||||
|
if formsemestre.formation.is_apc() and format == "html":
|
||||||
|
return render_template(
|
||||||
|
"but/bulletin.html",
|
||||||
|
appreciations=models.BulAppreciations.query.filter_by(
|
||||||
|
etudid=etudid, formsemestre_id=formsemestre.id
|
||||||
|
).order_by(models.BulAppreciations.date),
|
||||||
|
bul_url=url_for(
|
||||||
|
"notes.formsemestre_bulletinetud",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
etudid=etudid,
|
||||||
|
format="json",
|
||||||
|
force_publishing=1, # pour ScoDoc lui même
|
||||||
|
version=version,
|
||||||
|
),
|
||||||
|
can_edit_appreciations=formsemestre.est_responsable(current_user)
|
||||||
|
or (current_user.has_permission(Permission.ScoEtudInscrit)),
|
||||||
|
etud=etud,
|
||||||
|
formsemestre=formsemestre,
|
||||||
|
inscription_courante=etud.inscription_courante(),
|
||||||
|
inscription_str=etud.inscription_descr()["inscription_str"],
|
||||||
|
is_apc=formsemestre.formation.is_apc(),
|
||||||
|
menu_autres_operations=sco_bulletins.make_menu_autres_operations(
|
||||||
|
formsemestre, etud, "notes.formsemestre_bulletinetud", version
|
||||||
|
),
|
||||||
|
sco=ScoData(etud=etud),
|
||||||
|
scu=scu,
|
||||||
|
time=time,
|
||||||
|
title=f"Bul. {etud.nom} - BUT",
|
||||||
|
version=version,
|
||||||
|
)
|
||||||
|
|
||||||
if format == "oldjson":
|
if format == "oldjson":
|
||||||
format = "json"
|
format = "json"
|
||||||
r = sco_bulletins.formsemestre_bulletinetud(
|
r = sco_bulletins.formsemestre_bulletinetud(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.2.11"
|
SCOVERSION = "9.2.12"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user