1
0
forked from ScoDoc/ScoDoc

itemsuivi étudiants (devenir/débouchés): ré-écriture du backend, API, adaptation frontend js.

This commit is contained in:
ilona 2024-09-09 16:21:51 +02:00
parent b79e40f030
commit 41a76267f6
8 changed files with 336 additions and 297 deletions

View File

@ -113,6 +113,7 @@ from app.api import (
assiduites, assiduites,
billets_absences, billets_absences,
departements, departements,
etud_suivi,
etudiants, etudiants,
evaluations, evaluations,
formations, formations,

213
app/api/etud_suivi.py Normal file
View File

@ -0,0 +1,213 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""
API : itemsuivi devenir/débouché des étudiants
CATEGORY
--------
Étudiants
"""
import datetime
from flask import g, request, Response
from flask_json import as_json
from flask_login import login_required
from app.api import api_bp as bp, api_web_bp
from app.api import api_permission_required as permission_required
from app import db, log
from app.decorators import scodoc
from app.models import Identite, ItemSuivi, ItemSuiviTag, Scolog
from app.scodoc import sco_permissions_check
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import AccessDenied
import app.scodoc.sco_utils as scu
@bp.post("/etudiant/<int:etudid>/itemsuivi/create")
@api_web_bp.post("/etudiant/<int:etudid>/itemsuivi/create")
@login_required
@scodoc
@permission_required(Permission.EtudChangeAdr)
@as_json
def itemsuivi_create(etudid):
"""Création d'un item de suivi associé à l'étudiant.
The form MAY contain:
- item_date: date of the item
- situation: text
"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
etud = Identite.get_etud(etudid)
ok, item_date = _get_date_from_form()
item_date = item_date if ok else datetime.datetime.now()
situation = request.form.get("situation") or ""
item = ItemSuivi(etudid=etudid, item_date=item_date, situation=situation)
db.session.add(item)
db.session.commit()
Scolog.logdb(method="itemsuivi_create", etudid=etud.id, commit=True)
log(f"itemsuivi_create {item} for {etud}")
db.session.refresh(item)
return item.to_dict(merge_tags=True)
@bp.post("/etudiant/itemsuivi/<int:itemid>/delete")
@api_web_bp.post("/etudiant/itemsuivi/<int:itemid>/delete")
@login_required
@scodoc
@permission_required(Permission.EtudChangeAdr)
@as_json
def itemsuivi_suppress(itemid):
"""Suppression d'un item"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
item: ItemSuivi = ItemSuivi.get_instance(itemid, accept_none=True)
if item is None:
return {"status": "ok", "message": "item not found"}
etudid = item.etudid
db.session.delete(item)
db.session.commit()
Scolog.logdb(method="itemsuivi_suppress", etudid=etudid, commit=True)
log(f"itemsuivi_suppress: itemid={itemid}")
return {"status": "ok", "message": "item deleted"}
@bp.get("/etudiant/<int:etudid>/itemsuivis")
@api_web_bp.get("/etudiant/<int:etudid>/itemsuivis")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def itemsuivi_list_etud(etudid: int):
"""Liste des items pour cet étudiant, avec tags"""
etud = Identite.get_etud(etudid)
items = [it.to_dict(merge_tags=True) for it in etud.itemsuivis]
return items
@bp.post("/etudiant/itemsuivi/<int:itemid>/tag")
@api_web_bp.post("/etudiant/itemsuivi/<int:itemid>/tag")
@login_required
@scodoc
@permission_required(Permission.EtudChangeAdr)
@as_json
def itemsuivi_tag_set(itemid: int):
"""
taglist a string with tag names separated by commas e.g.: `un,deux`
"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
item: ItemSuivi = ItemSuivi.get_instance(itemid)
taglist_str = request.form.get("taglist")[: scu.MAX_TEXT_LEN]
if taglist_str is None:
return scu.json_error(400, "missing taglist in form")
taglist = [t.strip() for t in taglist_str.split(",")]
item.set_tags(taglist)
db.session.add(item)
db.session.commit()
log(f"itemsuivi_tag_set: itemsuivi_id={item.id} taglist={taglist}")
return item.to_dict(merge_tags=True)
@bp.post("/etudiant/itemsuivi/<int:itemid>/set_situation")
@api_web_bp.post("/etudiant/itemsuivi/<int:itemid>/set_situation")
@login_required
@scodoc
@permission_required(Permission.EtudChangeAdr)
def itemsuivi_set_situation(itemid: int):
"""Modifie le champ situation de l'item de suivi de l'étudiant.
Form data: "value" : "situation ..."
Appel utilisé par champ éditable sur fiche étudiant.
"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
item: ItemSuivi = ItemSuivi.get_instance(itemid)
if "value" not in request.form:
return scu.json_error(400, "missing value in form")
situation = request.form["value"]
item.situation = situation.strip("-_ \t\r\n")[: scu.MAX_TEXT_LEN]
db.session.add(item)
db.session.commit()
log(f"itemsuivi_set_situation: itemid={itemid} situation={item.situation[:32]}")
return item.situation or scu.IT_SITUATION_MISSING_STR
@bp.post("/etudiant/itemsuivi/<int:itemid>/set_date")
@api_web_bp.post("/etudiant/itemsuivi/<int:itemid>/set_date")
@login_required
@scodoc
@permission_required(Permission.EtudChangeAdr)
@as_json
def itemsuivi_set_date(itemid: int):
"""set item date
Specify date as an ISO date string
or date_dmy, string dd/mm/yyyy
"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
ok, date_or_resp = _get_date_from_form()
if not ok:
return date_or_resp
item: ItemSuivi = ItemSuivi.get_instance(itemid)
item.item_date = date_or_resp
db.session.add(item)
db.session.commit()
log(f"itemsuivi_set_date: itemid={itemid} date={item.item_date}")
return item.to_dict(merge_tags=True)
def _get_date_from_form() -> tuple[bool, datetime.datetime | Response]:
"""get date from form
Specify date as an ISO date string
or date_dmy, string dd/mm/yyyy
"""
date_iso = request.form.get("date") # ISO format
if date_iso:
try:
return True, datetime.datetime.fromisoformat(date_iso)
except ValueError:
return False, scu.json_error(400, "invalide date format (iso)")
date_dmy = request.form.get("date_dmy")
if not date_dmy:
return False, scu.json_error(400, "missing date or date_dmy in form")
try:
return True, datetime.datetime.strptime(date_dmy, scu.DATE_FMT)
except ValueError:
return False, scu.json_error(400, "invalide date format (jj/mm/aaaa)")
@bp.get("/etudiant/itemsuivi/tags/search")
@api_web_bp.get("/etudiant/itemsuivi/tags/search")
@login_required
@scodoc
@permission_required(Permission.ScoView)
@as_json
def itemsuivi_tag_search():
"""List all used tag names (for auto-completion)
Query: term
"""
if getattr(g, "scodoc_dept_id", None) is None:
return [] # accès départemental seulement
term = request.args.get("term", "").strip()[: scu.MAX_TEXT_LEN]
# restrict charset to avoid injections
if not scu.ALPHANUM_EXP.match(term):
data = []
else:
data = [
x.title
for x in ItemSuiviTag.query.filter(
ItemSuiviTag.title.like(f"{term}%"),
ItemSuiviTag.dept_id == g.scodoc_dept_id,
).all()
]
return data

View File

@ -128,9 +128,13 @@ class ScoDocModel(db.Model):
def get_instance(cls, oid: int, accept_none=False): def get_instance(cls, oid: int, accept_none=False):
"""Instance du modèle ou ou 404 (ou None si accept_none), """Instance du modèle ou ou 404 (ou None si accept_none),
cherche uniquement dans le département courant. cherche uniquement dans le département courant.
Ne fonctionne que si le modèle a un attribut dept_id.
Ne fonctionne que si le modèle a un attribut dept_id
ou que l'attribut de classe _sco_dept_relations indique les jointures
à effectuer pour trouver le département.
Si accept_none, return None si l'id est invalide ou ne correspond Si accept_none, return None si l'id est invalide ou ne correspond
pas à une validation. pas à une instance. Sinon lève 404 en cas d'erreur.
""" """
if not isinstance(oid, int): if not isinstance(oid, int):
try: try:

View File

@ -115,7 +115,12 @@ class Identite(models.ScoDocModel):
cascade="all, delete", cascade="all, delete",
passive_deletes=True, passive_deletes=True,
) )
itemsuivis = db.relationship(
"ItemSuivi",
backref="etudiant",
cascade="all, delete-orphan",
lazy="dynamic",
)
# Relations avec les assiduites et les justificatifs # Relations avec les assiduites et les justificatifs
assiduites = db.relationship( assiduites = db.relationship(
"Assiduite", back_populates="etudiant", lazy="dynamic", cascade="all, delete" "Assiduite", back_populates="etudiant", lazy="dynamic", cascade="all, delete"
@ -1118,8 +1123,39 @@ class ItemSuivi(models.ScoDocModel):
_sco_dept_relations = ("Identite",) # accès au dept_id _sco_dept_relations = ("Identite",) # accès au dept_id
tags = db.relationship(
"ItemSuiviTag",
secondary="itemsuivi_tags_assoc",
lazy=True,
backref=db.backref("items", lazy=True),
)
class ItemSuiviTag(db.Model): def to_dict(self, merge_tags=False):
"""Représentation dictionnaire.
Si merge_tags, regroupe les tags sur une seule chaine, valeurs séparées par des virgules
"""
d = super().to_dict()
# Convertit les tags en liste de strings:
if merge_tags:
d["tags"] = ", ".join([tag.title for tag in self.tags])
else:
d["tags"] = [tag.title for tag in self.tags]
# Ajoute date locale
d["item_date_dmy"] = self.item_date.strftime(scu.DATE_FMT)
return d
def set_tags(self, tags: list[str]):
"""Définit les tags de l'itemsuivi"""
self.tags = []
for tag in tags:
tag_obj = ItemSuiviTag.query.filter_by(title=tag).first()
if tag_obj is None:
tag_obj = ItemSuiviTag(title=tag)
self.tags.append(tag_obj)
class ItemSuiviTag(models.ScoDocModel):
"Tag sur un itemsuivi"
__tablename__ = "itemsuivi_tags" __tablename__ = "itemsuivi_tags"
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True) dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
@ -1127,7 +1163,7 @@ class ItemSuiviTag(db.Model):
title = db.Column(db.Text(), nullable=False, unique=True) title = db.Column(db.Text(), nullable=False, unique=True)
# Association tag <-> module # Association tag <-> itemsuivi
itemsuivi_tags_assoc = db.Table( itemsuivi_tags_assoc = db.Table(
"itemsuivi_tags_assoc", "itemsuivi_tags_assoc",
db.Column( db.Column(

View File

@ -28,22 +28,15 @@
""" """
Rapport (table) avec dernier semestre fréquenté et débouché de chaque étudiant Rapport (table) avec dernier semestre fréquenté et débouché de chaque étudiant
""" """
import http
from flask import g, render_template, request, url_for from flask import g, render_template, request, url_for
from app import log
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, Scolog from app.models import Identite
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc import safehtml
from app.scodoc import sco_permissions_check
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_tag_module
from app.scodoc import sco_etud
import sco_version import sco_version
@ -75,6 +68,7 @@ def report_debouche_date(start_year=None, fmt="html"):
tab.base_url = f"{request.base_url}?start_year={start_year}" tab.base_url = f"{request.base_url}?start_year={start_year}"
return tab.make_page( return tab.make_page(
title="""<h2 class="formsemestre">Débouchés étudiants </h2>""", title="""<h2 class="formsemestre">Débouchés étudiants </h2>""",
page_title="Débouchés étudiants",
fmt=fmt, fmt=fmt,
with_html_headers=True, with_html_headers=True,
template="sco_page_dept.j2", template="sco_page_dept.j2",
@ -115,70 +109,62 @@ def table_debouche_etudids(etudids, keep_numeric=True):
"""Rapport pour ces étudiants""" """Rapport pour ces étudiants"""
rows = [] rows = []
# Recherche les débouchés: # Recherche les débouchés:
itemsuivi_etuds = {etudid: itemsuivi_list_etud(etudid) for etudid in etudids} # itemsuivi_etuds = {etudid: itemsuivi_list_etud(etudid) for etudid in etudids}
all_tags = set() all_tags = set()
for debouche in itemsuivi_etuds.values():
if debouche:
for it in debouche:
all_tags.update(tag.strip() for tag in it["tags"].split(","))
all_tags = tuple(sorted(all_tags))
for etudid in etudids: for etudid in etudids:
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0] etud = Identite.get_etud(etudid)
# collecte les tags
all_tags.update(tag.title for item in etud.itemsuivis for tag in item.tags)
# retrouve le "dernier" semestre (au sens de la date de fin) # retrouve le "dernier" semestre (au sens de la date de fin)
sems = etud["sems"] formsemestres = etud.get_formsemestres()
es = [(s["date_fin_iso"], i) for i, s in enumerate(sems)] dates_fin = [s.date_fin for s in formsemestres]
imax = max(es)[1] imax = dates_fin.index(max(dates_fin))
last_sem = sems[imax] formsemestre = formsemestres[imax]
formsemestre = FormSemestre.query.get_or_404(last_sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
row = { row = {
"etudid": etudid, "etudid": etudid,
"civilite": etud["civilite"], "civilite": etud.civilite,
"nom": etud["nom"], "nom": etud.nom,
"prenom": etud["prenom"], "prenom": etud.prenom,
"_nom_target": url_for( "_nom_target": url_for(
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
), ),
"_prenom_target": url_for( "_prenom_target": url_for(
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
), ),
"_nom_td_attrs": 'id="%s" class="etudinfo"' % (etud["etudid"]), "_nom_td_attrs": f'id="{etudid}" class="etudinfo"',
# 'debouche' : etud['debouche'], # 'debouche' : etud['debouche'],
"moy": scu.fmt_note(nt.get_etud_moy_gen(etudid), keep_numeric=keep_numeric), "moy": scu.fmt_note(nt.get_etud_moy_gen(etudid), keep_numeric=keep_numeric),
"rang": nt.get_etud_rang(etudid), "rang": nt.get_etud_rang(etudid),
"effectif": len(nt.T), "effectif": len(nt.T),
"semestre_id": last_sem["semestre_id"], "semestre_id": formsemestre.semestre_id,
"semestre": last_sem["titre"], "semestre": formsemestre.titre,
"date_debut": last_sem["date_debut"], "date_debut": formsemestre.date_debut,
"date_fin": last_sem["date_fin"], "date_fin": formsemestre.date_fin,
"periode": "%s - %s" % (last_sem["mois_debut"], last_sem["mois_fin"]), "periode": f"{formsemestre.mois_debut()} - {formsemestre.mois_fin()}",
"sem_ident": "%s %s" "sem_ident": f"{formsemestre.date_debut.isoformat()} {formsemestre.titre}", # utile pour tris
% (last_sem["date_debut_iso"], last_sem["titre"]), # utile pour tris
} }
# recherche des débouchés # recherche des débouchés
debouche = itemsuivi_etuds[etudid] # liste de plusieurs items itemsuivis = etud.itemsuivis # liste de plusieurs items
if debouche: if itemsuivis:
if keep_numeric: # pour excel: if keep_numeric: # pour excel:
row["debouche"] = "\n".join( row["debouche"] = "\n".join(
f"""{it["item_date"]}: {it["situation"]}""" for it in debouche f"""{it.item_date.strftime(scu.DATE_FMT)}: {it.situation or ""}"""
for it in itemsuivis
) )
else: else:
row["debouche"] = "<br>".join( row["debouche"] = "<br>".join(
[ [
str(it["item_date"]) f"""{it.item_date.strftime(scu.DATE_FMT)} : {it.situation or ""}
+ " : " <i>{', '.join( tag.title for tag in it.tags)}</i>
+ it["situation"] """
+ " <i>" for it in itemsuivis
+ it["tags"]
+ "</i>"
for it in debouche
] ]
) )
for it in debouche: for it in itemsuivis:
for tag in it["tags"].split(","): for tag in it.tags:
tag = tag.strip() tag_name = tag.title.strip()
row[f"tag_{tag}"] = tag row[f"tag_{tag_name}"] = tag_name
else: else:
row["debouche"] = "non renseigné" row["debouche"] = "non renseigné"
rows.append(row) rows.append(row)
@ -236,177 +222,3 @@ def report_debouche_ask_date(msg: str) -> str:
</form> </form>
""", """,
) )
# ----------------------------------------------------------------------------
#
# Nouveau suivi des etudiants (nov 2017)
#
# ----------------------------------------------------------------------------
_itemsuiviEditor = ndb.EditableTable(
"itemsuivi",
"itemsuivi_id",
("itemsuivi_id", "etudid", "item_date", "situation"),
sortkey="item_date desc",
convert_null_outputs_to_empty=True,
output_formators={
"situation": safehtml.html_to_safe_html,
"item_date": ndb.DateISOtoDMY,
},
input_formators={"item_date": ndb.DateDMYtoISO},
)
_itemsuivi_create = _itemsuiviEditor.create
_itemsuivi_delete = _itemsuiviEditor.delete
_itemsuivi_list = _itemsuiviEditor.list
_itemsuivi_edit = _itemsuiviEditor.edit
class ItemSuiviTag(sco_tag_module.ScoTag):
"""Les tags sur les items"""
tag_table = "itemsuivi_tags" # table (tag_id, title)
assoc_table = "itemsuivi_tags_assoc" # table (tag_id, object_id)
obj_colname = "itemsuivi_id" # column name for object_id in assoc_table
def itemsuivi_get(cnx, itemsuivi_id, ignore_errors=False):
"""get an item"""
items = _itemsuivi_list(cnx, {"itemsuivi_id": itemsuivi_id})
if items:
return items[0]
elif not ignore_errors:
raise ScoValueError(f"Débouché: item inexistant ({itemsuivi_id})")
return None
def itemsuivi_suppress(itemsuivi_id):
"""Suppression d'un item"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
cnx = ndb.GetDBConnexion()
item = itemsuivi_get(cnx, itemsuivi_id, ignore_errors=True)
if item:
_itemsuivi_delete(cnx, itemsuivi_id)
Scolog.logdb(method="itemsuivi_suppress", etudid=item["etudid"], commit=True)
log(f"suppressed itemsuivi {itemsuivi_id}")
return ("", 204)
def itemsuivi_create(etudid, item_date=None, situation="", fmt=None):
"""Creation d'un item"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
cnx = ndb.GetDBConnexion()
itemsuivi_id = _itemsuivi_create(
cnx, args={"etudid": etudid, "item_date": item_date, "situation": situation}
)
Scolog.logdb(method="itemsuivi_create", etudid=etudid, commit=True)
log("created itemsuivi %s for %s" % (itemsuivi_id, etudid))
item = itemsuivi_get(cnx, itemsuivi_id)
if fmt == "json":
return scu.sendJSON(item)
return item
def itemsuivi_set_date(itemsuivi_id, item_date):
"""set item date
item_date is a string dd/mm/yyyy
"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
# log('itemsuivi_set_date %s : %s' % (itemsuivi_id, item_date))
cnx = ndb.GetDBConnexion()
item = itemsuivi_get(cnx, itemsuivi_id)
item["item_date"] = item_date
_itemsuivi_edit(cnx, item)
return ("", 204)
def itemsuivi_set_situation(obj, value):
"""set situation"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
itemsuivi_id = obj
situation = value.strip("-_ \t")
# log('itemsuivi_set_situation %s : %s' % (itemsuivi_id, situation))
cnx = ndb.GetDBConnexion()
item = itemsuivi_get(cnx, itemsuivi_id)
item["situation"] = situation
_itemsuivi_edit(cnx, item)
return situation or scu.IT_SITUATION_MISSING_STR
def itemsuivi_list_etud(etudid, fmt=None):
"""Liste des items pour cet étudiant, avec tags"""
cnx = ndb.GetDBConnexion()
items = _itemsuivi_list(cnx, {"etudid": etudid})
for it in items:
it["tags"] = ", ".join(itemsuivi_tag_list(it["itemsuivi_id"]))
if fmt == "json":
return scu.sendJSON(items)
return items
def itemsuivi_tag_list(itemsuivi_id):
"""les noms de tags associés à cet item"""
r = ndb.SimpleDictFetch(
"""SELECT t.title
FROM itemsuivi_tags_assoc a, itemsuivi_tags t
WHERE a.tag_id = t.id
AND a.itemsuivi_id = %(itemsuivi_id)s
""",
{"itemsuivi_id": itemsuivi_id},
)
return [x["title"] for x in r]
def itemsuivi_tag_search(term):
"""List all used tag names (for auto-completion)"""
# restrict charset to avoid injections
if not scu.ALPHANUM_EXP.match(term):
data = []
else:
r = ndb.SimpleDictFetch(
"SELECT title FROM itemsuivi_tags WHERE title LIKE %(term)s AND dept_id=%(dept_id)s",
{
"term": term + "%",
"dept_id": g.scodoc_dept_id,
},
)
data = [x["title"] for x in r]
return scu.sendJSON(data)
def itemsuivi_tag_set(itemsuivi_id="", taglist=None):
"""taglist may either be:
a string with tag names separated by commas ("un;deux")
or a list of strings (["un", "deux"])
"""
if not sco_permissions_check.can_edit_suivi():
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
if not taglist:
taglist = []
elif isinstance(taglist, str):
taglist = taglist.split(",")
taglist = [t.strip() for t in taglist]
# log('itemsuivi_tag_set: itemsuivi_id=%s taglist=%s' % (itemsuivi_id, taglist))
# Sanity check:
cnx = ndb.GetDBConnexion()
_ = itemsuivi_get(cnx, itemsuivi_id)
newtags = set(taglist)
oldtags = set(itemsuivi_tag_list(itemsuivi_id))
to_del = oldtags - newtags
to_add = newtags - oldtags
# should be atomic, but it's not.
for tagname in to_add:
t = ItemSuiviTag(tagname, object_id=itemsuivi_id)
for tagname in to_del:
t = ItemSuiviTag(tagname)
t.remove_tag_from_object(itemsuivi_id)
return "", http.HTTPStatus.NO_CONTENT

View File

@ -262,6 +262,7 @@ def module_tag_set(module_id="", taglist=None):
return scu.json_error(404, "invalid tag") return scu.json_error(404, "invalid tag")
# TODO code à moderniser (+ revoir classe ScoTag, utiliser modèle) # TODO code à moderniser (+ revoir classe ScoTag, utiliser modèle)
# TODO Voir ItemSuiviTag et api etud_suivi
# Sanity check: # Sanity check:
mod_dict = sco_edit_module.module_list(args={"module_id": module_id}) mod_dict = sco_edit_module.module_list(args={"module_id": module_id})

View File

@ -16,9 +16,8 @@ function display_itemsuivis(active) {
.off("click") .off("click")
.click(function (e) { .click(function (e) {
e.preventDefault(); e.preventDefault();
$.post(SCO_URL + "itemsuivi_create", { $.post(`${SCO_URL}../api/etudiant/${etudid}/itemsuivi/create`, {
etudid: etudid, etudid: etudid,
fmt: "json",
}).done(item_insert_new); }).done(item_insert_new);
return false; return false;
@ -26,13 +25,13 @@ function display_itemsuivis(active) {
} }
// add existing items // add existing items
$.get( $.get(
SCO_URL + "itemsuivi_list_etud", `${SCO_URL}../api/etudiant/${etudid}/itemsuivis`,
{ etudid: etudid, fmt: "json" }, { },
function (L) { function (L) {
for (var i in L) { for (var i in L) {
item_insert( item_insert(
L[i]["itemsuivi_id"], L[i]["id"],
L[i]["item_date"], L[i]["item_date_dmy"],
L[i]["situation"], L[i]["situation"],
L[i]["tags"], L[i]["tags"],
readonly readonly
@ -49,7 +48,7 @@ function display_itemsuivis(active) {
} }
function item_insert_new(it) { function item_insert_new(it) {
item_insert(it.itemsuivi_id, it.item_date, it.situation, "", false); item_insert(it.id, it.item_date, it.situation, "", false);
} }
function item_insert(itemsuivi_id, item_date, situation, tags, readonly) { function item_insert(itemsuivi_id, item_date, situation, tags, readonly) {
@ -78,35 +77,36 @@ function item_nodes(itemsuivi_id, item_date, situation, tags, readonly) {
var h = sel_mois; var h = sel_mois;
// situation // situation
h += h += `<div class="itemsituation editable"
'<div class="itemsituation editable" data-type="textarea" data-url="itemsuivi_set_situation" data-placeholder="<em>décrire situation...</em>" data-object="' + data-type="textarea"
itemsuivi_id + data-url="${SCO_URL}../api/etudiant/itemsuivi/${itemsuivi_id}/set_situation"
'">' + data-placeholder="<em>décrire situation...</em>"
situation + data-object="'${itemsuivi_id}'">${situation}</div>`;
"</div>";
// tags: // tags:
h += h +=
'<div class="itemsuivi_tag_edit"><textarea class="itemsuivi_tag_editor">' + `<div class="itemsuivi_tag_edit">
tags + <textarea class="itemsuivi_tag_editor"
"</textarea></div>"; data-itemsuivi_id="${itemsuivi_id}"
>${tags}</textarea>
</div>`;
var nodes = $($.parseHTML('<li class="itemsuivi">' + h + "</li>")); var nodes = $($.parseHTML('<li class="itemsuivi">' + h + "</li>"));
var dp = nodes.find(".itemsuividatepicker"); var dp = nodes.find(".itemsuividatepicker");
dp.blur(function (e) { dp.blur(function (e) {
var date = this.value; var date = this.value;
// console.log('selected text: ' + date); if (date) {
$.post(SCO_URL + "itemsuivi_set_date", { $.post(`${SCO_URL}../api/etudiant/itemsuivi/${itemsuivi_id}/set_date`, {
item_date: date, date_dmy: date,
itemsuivi_id: itemsuivi_id,
}); });
}
}); });
dp.datepicker({ dp.datepicker({
onSelect: function (date, instance) { onSelect: function (date, instance) {
// console.log('selected: ' + date + 'for itemsuivi_id ' + itemsuivi_id); if (date) {
$.post(SCO_URL + "itemsuivi_set_date", { $.post(`${SCO_URL}../api/etudiant/itemsuivi/${itemsuivi_id}/set_date`, {
item_date: date, date_dmy: date,
itemsuivi_id: itemsuivi_id,
}); });
}
}, },
showOn: "button", showOn: "button",
buttonImage: "/ScoDoc/static/icons/calendar_img.png", buttonImage: "/ScoDoc/static/icons/calendar_img.png",
@ -129,7 +129,8 @@ function item_nodes(itemsuivi_id, item_date, situation, tags, readonly) {
initialTags: "", initialTags: "",
placeholder: "Tags...", placeholder: "Tags...",
onChange: function (field, editor, tags) { onChange: function (field, editor, tags) {
$.post("itemsuivi_tag_set", { let itemsuivi_id = field.data("itemsuivi_id");
$.post(`${SCO_URL}../api/etudiant/itemsuivi/${itemsuivi_id}/tag`, {
itemsuivi_id: itemsuivi_id, itemsuivi_id: itemsuivi_id,
taglist: tags.join(), taglist: tags.join(),
}); });
@ -160,9 +161,20 @@ function Date2DMY(date) {
return day + "/" + month + "/" + year; return day + "/" + month + "/" + year;
} }
function itemsuivi_suppress(itemsuivi_id) { async function itemsuivi_suppress(itemsuivi_id) {
$.post(SCO_URL + "itemsuivi_suppress", { itemsuivi_id: itemsuivi_id }); const deleteUrl = `${SCO_URL}../api/etudiant/itemsuivi/${itemsuivi_id}/delete`;
try {
const response = await fetch(deleteUrl, {
method: 'POST'
});
if (response.ok) {
// Clear items and rebuild: // Clear items and rebuild:
$("ul.listdebouches li.itemsuivi").remove(); $("ul.listdebouches li.itemsuivi").remove();
display_itemsuivis(0); display_itemsuivis(0);
} }
} catch (error) {
console.error('Error deleting itemsuivi:', error);
}
}

View File

@ -719,46 +719,6 @@ sco_publish(
) )
# Debouche / devenir etudiant
sco_publish(
"/itemsuivi_suppress",
sco_debouche.itemsuivi_suppress,
Permission.EtudChangeAdr,
methods=["GET", "POST"],
)
sco_publish(
"/itemsuivi_create",
sco_debouche.itemsuivi_create,
Permission.EtudChangeAdr,
methods=["GET", "POST"],
)
sco_publish(
"/itemsuivi_set_date",
sco_debouche.itemsuivi_set_date,
Permission.EtudChangeAdr,
methods=["GET", "POST"],
)
sco_publish(
"/itemsuivi_set_situation",
sco_debouche.itemsuivi_set_situation,
Permission.EtudChangeAdr,
methods=["GET", "POST"],
)
sco_publish(
"/itemsuivi_list_etud", sco_debouche.itemsuivi_list_etud, Permission.ScoView
)
sco_publish("/itemsuivi_tag_list", sco_debouche.itemsuivi_tag_list, Permission.ScoView)
sco_publish(
"/itemsuivi_tag_search", sco_debouche.itemsuivi_tag_search, Permission.ScoView
)
sco_publish(
"/itemsuivi_tag_set",
sco_debouche.itemsuivi_tag_set,
Permission.EtudChangeAdr,
methods=["GET", "POST"],
)
@bp.route("/doAddAnnotation", methods=["GET", "POST"]) @bp.route("/doAddAnnotation", methods=["GET", "POST"])
@scodoc @scodoc
@permission_required(Permission.EtudAddAnnotations) @permission_required(Permission.EtudAddAnnotations)