From a75538cfd83beffc1271025cad05a9df45b8daf4 Mon Sep 17 00:00:00 2001 From: Louis DORMAEL <louis.dormael.etu@univ-lille.fr> Date: Fri, 28 Feb 2025 15:31:43 +0000 Subject: [PATCH] Issue 976 (ScoView -> ne peut pas soumettre d'annotations) --- app/api/etudiants.py | 4 +- app/auth/models.py | 1 + app/models/etudiants.py | 1 + app/scodoc/sco_etud.py | 1 + app/scodoc/sco_page_etud.py | 93 +++++++++++++--- app/views/scolar.py | 53 ++++++--- migrations/versions/00486dfafcc9_.py | 38 +++++++ migrations/versions/5b26e1e34405_.py | 46 ++++++++ migrations/versions/8d0999672a2c_.py | 34 ++++++ q | 156 +++++++++++++++++++++++++++ 10 files changed, 393 insertions(+), 34 deletions(-) create mode 100644 migrations/versions/00486dfafcc9_.py create mode 100644 migrations/versions/5b26e1e34405_.py create mode 100644 migrations/versions/8d0999672a2c_.py create mode 100644 q diff --git a/app/api/etudiants.py b/app/api/etudiants.py index 821a545c..e04afe9f 100755 --- a/app/api/etudiants.py +++ b/app/api/etudiants.py @@ -663,7 +663,7 @@ def etudiant_annotation( ------- /etudiant/etudid/1/annotation;{""comment"":""une annotation sur l'étudiant""} """ - if not current_user.has_permission(Permission.ViewEtudData): + if not current_user.has_permission(Permission.ViewEtudData) and not current_user.has_permission(Permission.ScoView): return json_error(403, "non autorisé (manque ViewEtudData)") dept: Departement = ( db.session.get(Departement, g.scodoc_dept_id) if g.scodoc_dept else None @@ -678,7 +678,7 @@ def etudiant_annotation( return json_error(404, "invalid comment (expected string)") if len(comment) > scu.MAX_TEXT_LEN: return json_error(404, "invalid comment (too large)") - annotation = EtudAnnotation(comment=comment, author=current_user.user_name) + annotation = EtudAnnotation(comment=comment, author=current_user.user_name, confidentiel=False) etud.annotations.append(annotation) db.session.add(etud) db.session.commit() diff --git a/app/auth/models.py b/app/auth/models.py index 7020db17..c21ff87d 100644 --- a/app/auth/models.py +++ b/app/auth/models.py @@ -631,6 +631,7 @@ class User(UserMixin, ScoDocModel): def get_nomcomplet(self): "Prénom et nom complets" return scu.format_prenom(self.prenom) + " " + self.get_nom_fmt() + # return "Luigi lancieri" # nomnoacc était le nom en minuscules sans accents (inutile) diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 1dc846e1..921cad55 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -1201,6 +1201,7 @@ class EtudAnnotation(models.ScoDocModel): etudid = db.Column(db.Integer, db.ForeignKey(Identite.id)) author = db.Column(db.Text) # le pseudo (user_name), was zope_authenticated_user comment = db.Column(db.Text) + confidentiel = db.Column(db.Boolean, default=True) _sco_dept_relations = ("Identite",) # accès au dept_id diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py index f74c3c42..4c2bd662 100644 --- a/app/scodoc/sco_etud.py +++ b/app/scodoc/sco_etud.py @@ -567,6 +567,7 @@ _etud_annotationsEditor = ndb.EditableTable( "author", "comment", "author", + "confidentiel", ), sortkey="date desc", convert_null_outputs_to_empty=True, diff --git a/app/scodoc/sco_page_etud.py b/app/scodoc/sco_page_etud.py index 0f70477f..e4f2f07a 100644 --- a/app/scodoc/sco_page_etud.py +++ b/app/scodoc/sco_page_etud.py @@ -171,6 +171,7 @@ def _menu_scolarite( def fiche_etud(etudid=None): "fiche d'informations sur un etudiant" restrict_etud_data = not current_user.has_permission(Permission.ViewEtudData) + restrict_annot_etud = not current_user.has_permission(Permission.ScoView) try: etud = Identite.get_etud(etudid) except Exception as exc: @@ -350,7 +351,7 @@ def fiche_etud(etudid=None): # Liste des annotations html_annotations_list = "\n".join( - [] if restrict_etud_data else get_html_annotations_list(etud) + [] if restrict_annot_etud else get_html_annotations_list(etud) ) # fiche admission @@ -570,7 +571,8 @@ def fiche_etud(etudid=None): """ ) - info["annotations_mkup"] = ( + if not restrict_etud_data : + info["annotations_mkup"] = ( f""" <div class="ficheannotations"> <div class="fichetitre">Annotations</div> @@ -581,6 +583,12 @@ def fiche_etud(etudid=None): <b>Ajouter une annotation sur {etud.nomprenom}: </b> <div> <textarea name="comment" rows="4" cols="50" value=""></textarea> + <div> + <input type="radio" id="public" name="confidentiel" value="public" selected> + <label for="public">Mettre l'annotation en publique.</label><br> + <input type="radio" id="private" name="confidentiel" value="private"> + <label for="private">Mettre l'annotation en privée.</label><br> + </div> <div style="font-size: small; font-style: italic;"> <div>Ces annotations sont lisibles par tous les utilisateurs ayant la permission <tt>ViewEtudData</tt> dans ce département (souvent les enseignants et le @@ -594,10 +602,35 @@ def fiche_etud(etudid=None): <input type="submit" value="Ajouter annotation"> </form> </div> - """ - if not restrict_etud_data - else "" - ) + """) + elif not restrict_annot_etud : + info["annotations_mkup"] = ( + f""" +<div class="ficheannotations"> + <div class="fichetitre">Annotations</div> + <table id="etudannotations">{html_annotations_list}</table> + + <form action="doAddAnnotation" method="GET" class="noprint"> + <input type="hidden" name="etudid" value="{etudid}"> + <b>Ajouter une annotation sur {etud.nomprenom}: </b> + <div> + <textarea name="comment" rows="4" cols="50" value=""></textarea> + <div style="font-size: small; font-style: italic;"> + <div>Ces annotations sont lisibles par tous les utilisateurs ayant la permission + <tt>ViewEtudData</tt> dans ce département (souvent les enseignants et le + secrétariat). + </div> + <div>L'annotation commençant par "PE:" est un avis de poursuite d'études.</div> + </div> + </div> + + <input type="hidden" name="author" width=12 value="{current_user}"> + <input type="submit" value="Ajouter annotation"> + </form> +</div> + """) + else : + "" tmpl = ( """<div class="menus_etud">%(menus_etud)s</div> @@ -752,10 +785,11 @@ def get_html_annotations_list(etud: Identite) -> list[str]: """Liste de chaînes html décrivant les annotations.""" html_annotations_list = [] annotations = EtudAnnotation.query.filter_by(etudid=etud.id).order_by( - sa.desc(EtudAnnotation.date) + sa.desc(EtudAnnotation.date) ) for annot in annotations: - del_link = ( + if current_user.has_permission(Permission.ViewEtudData) : + del_link = ( f"""<td class="annodel"><a href="{ url_for("scolar.doSuppressAnnotation", scodoc_dept=g.scodoc_dept, etudid=etud.id, annotation_id=annot.id)}">{ @@ -768,16 +802,41 @@ def get_html_annotations_list(etud: Identite) -> list[str]: }</a></td>""" if sco_permissions_check.can_suppress_annotation(annot.id) else "" - ) + ) - author = User.query.filter_by(user_name=annot.author).first() - html_annotations_list.append( - f"""<tr><td><span class="annodate">Le { - annot.date.strftime(scu.DATE_FMT) if annot.date else "?"} - par {author.get_prenomnom() if author else "?"} : - </span><span class="annoc">{annot.comment or ""}</span></td>{del_link}</tr> - """ - ) + author = User.query.filter_by(user_name=annot.author).first() + html_annotations_list.append( + f"""<tr><td><span class="annodate">Le { + annot.date.strftime(scu.DATE_FMT) if annot.date else "?"} + par {author.get_prenomnom() if author else "?"} : + </span><span class="annoc">{annot.comment or ""}</span></td>{del_link}</tr> + """ + ) + else: + if annot.confidentiel == False : + del_link = ( + f"""<td class="annodel"><a href="{ + url_for("scolar.doSuppressAnnotation", + scodoc_dept=g.scodoc_dept, etudid=etud.id, annotation_id=annot.id)}">{ + scu.icontag( + "delete_img", + border="0", + alt="suppress", + title="Supprimer cette annotation", + ) + }</a></td>""" + if sco_permissions_check.can_suppress_annotation(annot.id) + else "" + ) + + author = User.query.filter_by(user_name=annot.author).first() + html_annotations_list.append( + f"""<tr><td><span class="annodate">Le { + annot.date.strftime(scu.DATE_FMT) if annot.date else "?"} + par {author.get_prenomnom() if author else "?"} : + </span><span class="annoc">{annot.comment or ""}</span></td>{del_link}</tr> + """ + ) return html_annotations_list diff --git a/app/views/scolar.py b/app/views/scolar.py index 756b051e..5923e5ee 100644 --- a/app/views/scolar.py +++ b/app/views/scolar.py @@ -722,23 +722,46 @@ sco_publish( @scodoc @permission_required(Permission.EtudAddAnnotations) @scodoc7func -def doAddAnnotation(etudid, comment): +def doAddAnnotation(etudid, comment, confidentiel): "ajoute annotation sur etudiant" - _ = Identite.get_etud(etudid) # check existence - if comment: - cnx = ndb.GetDBConnexion() - sco_etud.etud_annotations_create( - cnx, - args={ - "etudid": etudid, - "comment": comment, - "author": current_user.user_name, - }, + if current_user.has_permission(Permission.ScoView) and not current_user.has_permission(Permission.ViewEtudData) : + _ = Identite.get_etud(etudid) # check existence + if comment: + cnx = ndb.GetDBConnexion() + sco_etud.etud_annotations_create( + cnx, + args={ + "etudid": etudid, + "comment": comment, + "author": current_user.user_name, + "confidentiel": False, + }, + ) + Scolog.logdb(method="addAnnotation", etudid=etudid, commit=True) + return flask.redirect( + url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid) + ) + else : + if confidentiel == "private": + boolConfidentiel = True + else : + boolConfidentiel = False + _ = Identite.get_etud(etudid) # check existence + if comment: + cnx = ndb.GetDBConnexion() + sco_etud.etud_annotations_create( + cnx, + args={ + "etudid": etudid, + "comment": comment, + "author": current_user.user_name, + "confidentiel": boolConfidentiel + }, + ) + Scolog.logdb(method="addAnnotation", etudid=etudid, commit=True) + return flask.redirect( + url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid) ) - Scolog.logdb(method="addAnnotation", etudid=etudid, commit=True) - return flask.redirect( - url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid) - ) @bp.route("/doSuppressAnnotation", methods=["GET", "POST"]) diff --git a/migrations/versions/00486dfafcc9_.py b/migrations/versions/00486dfafcc9_.py new file mode 100644 index 00000000..87abf49b --- /dev/null +++ b/migrations/versions/00486dfafcc9_.py @@ -0,0 +1,38 @@ +"""empty message + +Revision ID: 00486dfafcc9 +Revises: e0824c4f1b0b +Create Date: 2025-02-26 08:37:44.811595 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '00486dfafcc9' +down_revision = 'e0824c4f1b0b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('etud_annotations', schema=None) as batch_op: + batch_op.add_column(sa.Column('visibility', sa.Boolean(), nullable=True)) + + with op.batch_alter_table('notes_formsemestre_responsables', schema=None) as batch_op: + batch_op.drop_constraint('uq_notes_formsemestre_responsables', type_='unique') + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('notes_formsemestre_responsables', schema=None) as batch_op: + batch_op.create_unique_constraint('uq_notes_formsemestre_responsables', ['formsemestre_id', 'responsable_id']) + + with op.batch_alter_table('etud_annotations', schema=None) as batch_op: + batch_op.drop_column('visibility') + + # ### end Alembic commands ### diff --git a/migrations/versions/5b26e1e34405_.py b/migrations/versions/5b26e1e34405_.py new file mode 100644 index 00000000..9e836cb4 --- /dev/null +++ b/migrations/versions/5b26e1e34405_.py @@ -0,0 +1,46 @@ +"""empty message + +Revision ID: 5b26e1e34405 +Revises: 8d0999672a2c +Create Date: 2025-02-28 12:58:26.272652 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '5b26e1e34405' +down_revision = '8d0999672a2c' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('etud_annotations', schema=None) as batch_op: + batch_op.add_column(sa.Column('confidentiel', sa.Boolean(), nullable=True)) + batch_op.drop_column('visibility') + + with op.batch_alter_table('identite', schema=None) as batch_op: + batch_op.drop_index('unique_dept_ine_except_null', postgresql_where='(code_ine IS NOT NULL)') + batch_op.create_unique_constraint('unique_dept_ine_except_null', ['dept_id', 'code_ine']) + batch_op.drop_constraint('unique_dept_nip_except_null', type_='unique') + batch_op.create_index('unique_dept_nip_except_null', ['dept_id', 'code_nip'], unique=True, postgresql_where=sa.text('code_nip IS NOT NULL')) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('identite', schema=None) as batch_op: + batch_op.drop_index('unique_dept_nip_except_null', postgresql_where=sa.text('code_nip IS NOT NULL')) + batch_op.create_unique_constraint('unique_dept_nip_except_null', ['dept_id', 'code_nip']) + batch_op.drop_constraint('unique_dept_ine_except_null', type_='unique') + batch_op.create_index('unique_dept_ine_except_null', ['dept_id', 'code_ine'], unique=True, postgresql_where='(code_ine IS NOT NULL)') + + with op.batch_alter_table('etud_annotations', schema=None) as batch_op: + batch_op.add_column(sa.Column('visibility', sa.BOOLEAN(), autoincrement=False, nullable=True)) + batch_op.drop_column('confidentiel') + + # ### end Alembic commands ### diff --git a/migrations/versions/8d0999672a2c_.py b/migrations/versions/8d0999672a2c_.py new file mode 100644 index 00000000..9c8c8b24 --- /dev/null +++ b/migrations/versions/8d0999672a2c_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: 8d0999672a2c +Revises: 00486dfafcc9 +Create Date: 2025-02-26 08:52:26.332134 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '8d0999672a2c' +down_revision = '00486dfafcc9' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('identite', schema=None) as batch_op: + batch_op.drop_index('unique_dept_nip_except_null', postgresql_where='(code_nip IS NOT NULL)') + batch_op.create_unique_constraint('unique_dept_nip_except_null', ['dept_id', 'code_nip']) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('identite', schema=None) as batch_op: + batch_op.drop_constraint('unique_dept_nip_except_null', type_='unique') + batch_op.create_index('unique_dept_nip_except_null', ['dept_id', 'code_nip'], unique=True, postgresql_where='(code_nip IS NOT NULL)') + + # ### end Alembic commands ### diff --git a/q b/q new file mode 100644 index 00000000..360ee501 --- /dev/null +++ b/q @@ -0,0 +1,156 @@ + List of relations + Schema | Name | Type | Owner +--------+-----------------------------------------------+----------+-------- + public | absences | table | scodoc + public | absences_id_seq | sequence | scodoc + public | absences_notifications | table | scodoc + public | absences_notifications_id_seq | sequence | scodoc + public | admissions | table | scodoc + public | admissions_id_seq | sequence | scodoc + public | adresse | table | scodoc + public | adresse_id_seq | sequence | scodoc + public | alembic_version | table | scodoc + public | apc_annee_parcours | table | scodoc + public | apc_annee_parcours_id_seq | sequence | scodoc + public | apc_app_critique | table | scodoc + public | apc_app_critique_id_seq | sequence | scodoc + public | apc_competence | table | scodoc + public | apc_competence_id_seq | sequence | scodoc + public | apc_composante_essentielle | table | scodoc + public | apc_composante_essentielle_id_seq | sequence | scodoc + public | apc_modules_acs | table | scodoc + public | apc_niveau | table | scodoc + public | apc_niveau_id_seq | sequence | scodoc + public | apc_parcours | table | scodoc + public | apc_parcours_id_seq | sequence | scodoc + public | apc_parcours_niveau_competence | table | scodoc + public | apc_referentiel_competences | table | scodoc + public | apc_referentiel_competences_id_seq | sequence | scodoc + public | apc_situation_pro | table | scodoc + public | apc_situation_pro_id_seq | sequence | scodoc + public | apc_validation_annee | table | scodoc + public | apc_validation_annee_id_seq | sequence | scodoc + public | apc_validation_rcue | table | scodoc + public | apc_validation_rcue_id_seq | sequence | scodoc + public | are_contacts | table | scodoc + public | are_contacts_id_seq | sequence | scodoc + public | are_correspondants | table | scodoc + public | are_correspondants_id_seq | sequence | scodoc + public | are_entreprises | table | scodoc + public | are_entreprises_id_seq | sequence | scodoc + public | are_envoi_offre | table | scodoc + public | are_envoi_offre_etudiant | table | scodoc + public | are_envoi_offre_etudiant_id_seq | sequence | scodoc + public | are_envoi_offre_id_seq | sequence | scodoc + public | are_historique | table | scodoc + public | are_historique_id_seq | sequence | scodoc + public | are_offre_departement | table | scodoc + public | are_offre_departement_id_seq | sequence | scodoc + public | are_offres | table | scodoc + public | are_offres_id_seq | sequence | scodoc + public | are_preferences | table | scodoc + public | are_preferences_id_seq | sequence | scodoc + public | are_sites | table | scodoc + public | are_sites_id_seq | sequence | scodoc + public | are_stages_apprentissages | table | scodoc + public | are_stages_apprentissages_id_seq | sequence | scodoc + public | are_taxe_apprentissage | table | scodoc + public | are_taxe_apprentissage_id_seq | sequence | scodoc + public | assiduites | table | scodoc + public | assiduites_id_seq | sequence | scodoc + public | billet_absence | table | scodoc + public | billet_absence_id_seq | sequence | scodoc + public | departement | table | scodoc + public | departement_id_seq | sequence | scodoc + public | dispenseUE | table | scodoc + public | dispenseUE_id_seq | sequence | scodoc + public | etud_annotations | table | scodoc + public | etud_annotations_id_seq | sequence | scodoc + public | evaluation_ue_poids | table | scodoc + public | group_descr | table | scodoc + public | group_descr_id_seq | sequence | scodoc + public | group_membership | table | scodoc + public | identite | table | scodoc + public | identite_id_seq | sequence | scodoc + public | itemsuivi | table | scodoc + public | itemsuivi_id_seq | sequence | scodoc + public | itemsuivi_tags | table | scodoc + public | itemsuivi_tags_assoc | table | scodoc + public | itemsuivi_tags_id_seq | sequence | scodoc + public | justificatifs | table | scodoc + public | justificatifs_id_seq | sequence | scodoc + public | module_ue_coef | table | scodoc + public | notes_appreciations | table | scodoc + public | notes_appreciations_id_seq | sequence | scodoc + public | notes_evaluation | table | scodoc + public | notes_evaluation_id_seq | sequence | scodoc + public | notes_form_modalites | table | scodoc + public | notes_form_modalites_id_seq | sequence | scodoc + public | notes_formations | table | scodoc + public | notes_formations_id_seq | sequence | scodoc + public | notes_formsemestre | table | scodoc + public | notes_formsemestre_custommenu | table | scodoc + public | notes_formsemestre_custommenu_id_seq | sequence | scodoc + public | notes_formsemestre_description | table | scodoc + public | notes_formsemestre_description_id_seq | sequence | scodoc + public | notes_formsemestre_etapes | table | scodoc + public | notes_formsemestre_etapes_id_seq | sequence | scodoc + public | notes_formsemestre_id_seq | sequence | scodoc + public | notes_formsemestre_inscription | table | scodoc + public | notes_formsemestre_inscription_id_seq | sequence | scodoc + public | notes_formsemestre_responsables | table | scodoc + public | notes_formsemestre_ue_computation_expr | table | scodoc + public | notes_formsemestre_ue_computation_expr_id_seq | sequence | scodoc + public | notes_formsemestre_uecoef | table | scodoc + public | notes_formsemestre_uecoef_id_seq | sequence | scodoc + public | notes_idgen_fcod | sequence | scodoc + public | notes_matieres | table | scodoc + public | notes_matieres_id_seq | sequence | scodoc + public | notes_moduleimpl | table | scodoc + public | notes_moduleimpl_id_seq | sequence | scodoc + public | notes_moduleimpl_inscription | table | scodoc + public | notes_moduleimpl_inscription_id_seq | sequence | scodoc + public | notes_modules | table | scodoc + public | notes_modules_enseignants | table | scodoc + public | notes_modules_id_seq | sequence | scodoc + public | notes_modules_tags | table | scodoc + public | notes_notes | table | scodoc + public | notes_notes_id_seq | sequence | scodoc + public | notes_notes_log | table | scodoc + public | notes_notes_log_id_seq | sequence | scodoc + public | notes_semset | table | scodoc + public | notes_semset_formsemestre | table | scodoc + public | notes_semset_id_seq | sequence | scodoc + public | notes_tags | table | scodoc + public | notes_tags_id_seq | sequence | scodoc + public | notes_ue | table | scodoc + public | notes_ue_id_seq | sequence | scodoc + public | parcours_formsemestre | table | scodoc + public | parcours_modules | table | scodoc + public | partition | table | scodoc + public | partition_id_seq | sequence | scodoc + public | role | table | scodoc + public | role_id_seq | sequence | scodoc + public | sco_prefs | table | scodoc + public | sco_prefs_id_seq | sequence | scodoc + public | scodoc_site_config | table | scodoc + public | scodoc_site_config_id_seq | sequence | scodoc + public | scolar_autorisation_inscription | table | scodoc + public | scolar_autorisation_inscription_id_seq | sequence | scodoc + public | scolar_events | table | scodoc + public | scolar_events_id_seq | sequence | scodoc + public | scolar_formsemestre_validation | table | scodoc + public | scolar_formsemestre_validation_id_seq | sequence | scodoc + public | scolar_news | table | scodoc + public | scolar_news_id_seq | sequence | scodoc + public | scolog | table | scodoc + public | scolog_id_seq | sequence | scodoc + public | ue_parcours | table | scodoc + public | user | table | scodoc + public | user_id_seq | sequence | scodoc + public | user_role | table | scodoc + public | user_role_id_seq | sequence | scodoc + public | validation_dut120 | table | scodoc + public | validation_dut120_id_seq | sequence | scodoc +(151 rows) + -- GitLab