diff --git a/README.md b/README.md index 828b5ec99..73488544c 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,27 @@ Installer le bon vieux `pyExcelerator` dans l'environnement: python -m unittest tests.test_users +# Work in Progress + +## Migration ZScolar + +### Méthodes qui ne devraient plus être publiées: +security.declareProtected(ScoView, "get_preferences") + +def get_preferences(context, formsemestre_id=None): + "Get preferences for this instance (a dict-like instance)" + return sco_preferences.sem_preferences(context, formsemestre_id) + +security.declareProtected(ScoView, "get_preference") + +def get_preference(context, name, formsemestre_id=None): + """Returns value of named preference. + All preferences have a sensible default value (see sco_preferences.py), + this function always returns a usable value for all defined preferences names. + """ + return sco_preferences.get_base_preferences(context).get(formsemestre_id, name) + + diff --git a/app/scodoc/ZAbsences.py b/app/scodoc/ZAbsences.py index 91aaf7eb7..9e7ae8134 100644 --- a/app/scodoc/ZAbsences.py +++ b/app/scodoc/ZAbsences.py @@ -904,15 +904,16 @@ class ZAbsences( etuds = [e for e in etuds if e["etudid"] in mod_inscrits] if not moduleimpl_id: moduleimpl_id = None - base_url_noweeks = "SignaleAbsenceGrSemestre?datedebut=%s&datefin=%s&%s&destination=%s" % ( - datedebut, - datefin, - groups_infos.groups_query_args, - urllib.quote(destination), + base_url_noweeks = ( + "SignaleAbsenceGrSemestre?datedebut=%s&datefin=%s&%s&destination=%s" + % ( + datedebut, + datefin, + groups_infos.groups_query_args, + urllib.quote(destination), + ) ) - base_url = ( - base_url_noweeks + "&nbweeks=%s" % nbweeks - ) # sans le moduleimpl_id + base_url = base_url_noweeks + "&nbweeks=%s" % nbweeks # sans le moduleimpl_id if etuds: nt = self.Notes._getNotesCache().get_NotesTable(self.Notes, formsemestre_id) diff --git a/app/scodoc/sco_archives.py b/app/scodoc/sco_archives.py index 2650f0e92..28fdae366 100644 --- a/app/scodoc/sco_archives.py +++ b/app/scodoc/sco_archives.py @@ -557,7 +557,8 @@ def formsemestre_delete_archive( dest_url = "formsemestre_list_archives?formsemestre_id=%s" % (formsemestre_id) if not dialog_confirmed: - return context.confirmDialog( + return scu.confirm_dialog( + context, """

Confirmer la suppression de l'archive du %s ?

La suppression sera définitive.

""" % PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M"), diff --git a/app/scodoc/sco_archives_etud.py b/app/scodoc/sco_archives_etud.py index 39eca7fdd..1469a43b6 100644 --- a/app/scodoc/sco_archives_etud.py +++ b/app/scodoc/sco_archives_etud.py @@ -201,7 +201,8 @@ def etud_delete_archive(context, REQUEST, etudid, archive_name, dialog_confirmed archive_id = EtudsArchive.get_id_from_name(context, etudid, archive_name) dest_url = "ficheEtud?etudid=%s" % etudid if not dialog_confirmed: - return context.confirmDialog( + return scu.confirm_dialog( + context, """

Confirmer la suppression des fichiers ?

Fichier associé le %s à l'étudiant %s

La suppression sera définitive.

""" diff --git a/app/scodoc/sco_edit_formation.py b/app/scodoc/sco_edit_formation.py index 6d249230f..b19477440 100644 --- a/app/scodoc/sco_edit_formation.py +++ b/app/scodoc/sco_edit_formation.py @@ -65,7 +65,8 @@ def formation_delete(context, formation_id=None, dialog_confirmed=False, REQUEST H.append('

Revenir

' % context.NotesURL()) else: if not dialog_confirmed: - return context.confirmDialog( + return scu.confirm_dialog( + context, """

Confirmer la suppression de la formation %(titre)s (%(acronyme)s) ?

Attention: la suppression d'une formation est irréversible et implique la supression de toutes les UE, matières et modules de la formation !

diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index 452b12a6d..0a977afe7 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -267,7 +267,8 @@ def ue_delete( ue = ue[0] if not dialog_confirmed: - return context.confirmDialog( + return scu.confirm_dialog( + context, "

Suppression de l'UE %(titre)s (%(acronyme)s))

" % ue, dest_url="", REQUEST=REQUEST, diff --git a/app/scodoc/sco_etape_apogee_view.py b/app/scodoc/sco_etape_apogee_view.py index 2376e08cc..530ca6b50 100644 --- a/app/scodoc/sco_etape_apogee_view.py +++ b/app/scodoc/sco_etape_apogee_view.py @@ -215,9 +215,9 @@ def apo_semset_maq_status( ) if apo_dups: - url_list = ( - "view_apo_etuds?semset_id=%s&title=Doublons%%20Apogee&nips=%s" - % (semset_id, "&nips=".join(apo_dups)) + url_list = "view_apo_etuds?semset_id=%s&title=Doublons%%20Apogee&nips=%s" % ( + semset_id, + "&nips=".join(apo_dups), ) H.append( '
  • %d étudiants présents dans les plusieurs maquettes Apogée chargées
  • ' @@ -659,7 +659,8 @@ def view_apo_csv_delete( semset = sco_semset.SemSet(context, semset_id=semset_id) dest_url = "apo_semset_maq_status?semset_id=" + semset_id if not dialog_confirmed: - return context.confirmDialog( + return scu.confirm_dialog( + context, """

    Confirmer la suppression du fichier étape %s?

    La suppression sera définitive.

    """ % (etape_apo,), diff --git a/app/scodoc/sco_exceptions.py b/app/scodoc/sco_exceptions.py index 95044dcbe..12aa05a10 100644 --- a/app/scodoc/sco_exceptions.py +++ b/app/scodoc/sco_exceptions.py @@ -66,6 +66,12 @@ class FormatError(ScoValueError): pass +class ScoInvalidDept(ScoValueError): + """departement invalide""" + + pass + + class ScoConfigurationError(ScoValueError): """Configuration invalid""" diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index 2c05a600f..927a2829e 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -1129,7 +1129,8 @@ def formsemestre_associate_new_version( % (s["formsemestre_id"], checked, disabled, s["titremois"]) ) - return context.confirmDialog( + return scu.confirm_dialog( + context, """

    Associer à une nouvelle version de formation non verrouillée ?

    Le programme pédagogique ("formation") va être dupliqué pour que vous puissiez le modifier sans affecter les autres semestres. Les autres paramètres (étudiants, notes...) du semestre seront inchangés.

    Veillez à ne pas abuser de cette possibilité, car créer trop de versions de formations va vous compliquer la gestion (à vous de garder trace des différences et à ne pas vous tromper par la suite...). @@ -1280,7 +1281,8 @@ def formsemestre_delete2( """Delete a formsemestre (confirmation)""" # Confirmation dialog if not dialog_confirmed: - return context.confirmDialog( + return scu.confirm_dialog( + context, """

    Vous voulez vraiment supprimer ce semestre ???

    (opération irréversible)

    """, dest_url="", REQUEST=REQUEST, @@ -1423,7 +1425,8 @@ def formsemestre_change_lock( msg = "déverrouillage" else: msg = "verrouillage" - return context.confirmDialog( + return scu.confirm_dialog( + context, "

    Confirmer le %s du semestre ?

    " % msg, helpmsg="""Les notes d'un semestre verrouillé ne peuvent plus être modifiées. Un semestre verrouillé peut cependant être déverrouillé facilement à tout moment @@ -1462,7 +1465,8 @@ def formsemestre_change_publication_bul( msg = "non" else: msg = "" - return context.confirmDialog( + return scu.confirm_dialog( + context, "

    Confirmer la %s publication des bulletins ?

    " % msg, helpmsg="""Il est parfois utile de désactiver la diffusion des bulletins, par exemple pendant la tenue d'un jury ou avant harmonisation des notes. diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index a9c63623a..2824a3c43 100644 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -725,12 +725,130 @@ def formsemestre_lists(context, formsemestre_id, REQUEST=None): sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) H = [ context.html_sem_header(REQUEST, "", sem), - context.make_listes_sem(sem, REQUEST), + _make_listes_sem(context, sem, REQUEST), context.sco_footer(REQUEST), ] return "\n".join(H) +# genere liste html pour accès aux groupes de ce semestre +# XXX #sco8 vérifier si c'est encore utilisé ! +def _make_listes_sem(context, sem, REQUEST=None, with_absences=True): + context = context + authuser = REQUEST.AUTHENTICATED_USER + r = context.ScoURL() # root url + # construit l'URL "destination" + # (a laquelle on revient apres saisie absences) + query_args = cgi.parse_qs(REQUEST.QUERY_STRING) + if "head_message" in query_args: + del query_args["head_message"] + destination = "%s?%s" % (REQUEST.URL, urllib.urlencode(query_args, True)) + destination = destination.replace( + "%", "%%" + ) # car ici utilisee dans un format string ! + + # + H = [] + # pas de menu absences si pas autorise: + if with_absences and not authuser.has_permission(ScoAbsChange, context): + with_absences = False + + # + H.append( + '

    Listes de %(titre)s (%(mois_debut)s - %(mois_fin)s)

    ' + % sem + ) + + formsemestre_id = sem["formsemestre_id"] + + # calcule dates 1er jour semaine pour absences + try: + if with_absences: + first_monday = sco_abs.ddmmyyyy(sem["date_debut"]).prev_monday() + FA = [] # formulaire avec menu saisi absences + FA.append( + '
    ' + ) + FA.append( + '' % sem + ) + FA.append('') + + FA.append( + '' % destination + ) + FA.append('') + FA.append('") + FA.append( + 'état' + % sem + ) + FA.append("
    ") + FormAbs = "\n".join(FA) + else: + FormAbs = "" + except ScoInvalidDateError: # dates incorrectes dans semestres ? + FormAbs = "" + # + H.append('
    ') + # Genere liste pour chaque partition (categorie de groupes) + for partition in sco_groups.get_partitions_list(context, sem["formsemestre_id"]): + if not partition["partition_name"]: + H.append("

    Tous les étudiants

    " % partition) + else: + H.append("

    Groupes de %(partition_name)s

    " % partition) + groups = sco_groups.get_partition_groups(context, partition) + if groups: + H.append("") + for group in groups: + n_members = len( + sco_groups.get_group_members(context, group["group_id"]) + ) + group["url"] = r + if group["group_name"]: + group["label"] = "groupe %(group_name)s" % group + else: + group["label"] = "liste" + H.append('') + H.append( + """""" + % group + ) + H.append("" % n_members) + + if with_absences: + H.append(FormAbs % group) + + H.append("") + H.append("
    ") + else: + H.append('

    Aucun groupe dans cette partition') + if sco_groups.can_change_groups(context, REQUEST, formsemestre_id): + H.append( + ' (créer)' + % partition["partition_id"] + ) + H.append("

    ") + if sco_groups.can_change_groups(context, REQUEST, formsemestre_id): + H.append( + '

    Ajouter une partition

    ' + % formsemestre_id + ) + + H.append("
    ") + return "\n".join(H) + + def html_expr_diagnostic(context, diagnostics): """Affiche messages d'erreur des formules utilisateurs""" H = [] diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index d1dfc481b..74f714037 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -973,7 +973,8 @@ def partition_delete( grnames = "(" + ", ".join([g["group_name"] or "" for g in groups]) + ")" else: grnames = "" - return context.confirmDialog( + return scu.confirm_dialog( + context, """

    Supprimer la partition "%s" ?

    Les groupes %s de cette partition seront supprimés

    """ diff --git a/app/scodoc/sco_inscr_passage.py b/app/scodoc/sco_inscr_passage.py index d1fd08855..e1f9ac24c 100644 --- a/app/scodoc/sco_inscr_passage.py +++ b/app/scodoc/sco_inscr_passage.py @@ -340,7 +340,8 @@ def formsemestre_inscr_passage( if not a_inscrire and not a_desinscrire: H.append("""

    Il n'y a rien à modifier !

    """) H.append( - context.confirmDialog( + scu.confirm_dialog( + context, dest_url="formsemestre_inscr_passage", add_headers=False, cancel_url="formsemestre_inscr_passage?formsemestre_id=" diff --git a/app/scodoc/sco_poursuite_dut.py b/app/scodoc/sco_poursuite_dut.py index a2fdc2fe9..86c6779cc 100644 --- a/app/scodoc/sco_poursuite_dut.py +++ b/app/scodoc/sco_poursuite_dut.py @@ -150,13 +150,27 @@ def _flatten_info(info): return ids +def _getEtudInfoGroupes(context, group_ids, etat=None): + """liste triée d'infos (dict) sur les etudiants du groupe indiqué. + Attention: lent, car plusieurs requetes SQL par etudiant ! + """ + etuds = [] + for group_id in group_ids: + members = sco_groups.get_group_members(context, group_id, etat=etat) + for m in members: + etud = context.getEtudInfo(etudid=m["etudid"], filled=True)[0] + etuds.append(etud) + + return etuds + + def formsemestre_poursuite_report( context, formsemestre_id, format="html", REQUEST=None ): """Table avec informations "poursuite" """ sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) - etuds = context.getEtudInfoGroupes( - [sco_groups.get_default_group(context, formsemestre_id)] + etuds = _getEtudInfoGroupes( + context, [sco_groups.get_default_group(context, formsemestre_id)] ) infos = [] diff --git a/app/scodoc/sco_report.py b/app/scodoc/sco_report.py index 26ebb8651..fdcf1c97e 100644 --- a/app/scodoc/sco_report.py +++ b/app/scodoc/sco_report.py @@ -88,11 +88,22 @@ def formsemestre_etuds_stats(context, sem, only_primo=False): bs.append(etud["specialite"]) etud["bac-specialite"] = " ".join(bs) # - if (not only_primo) or context.isPrimoEtud(etud, sem): + if (not only_primo) or is_primo_etud(context, etud, sem): etuds.append(etud) return etuds +def is_primo_etud(context, etud, sem): + """Determine si un (filled) etud a ete inscrit avant ce semestre. + Regarde la liste des semestres dans lesquels l'étudiant est inscrit + """ + now = sem["dateord"] + for s in etud["sems"]: # le + recent d'abord + if s["dateord"] < now: + return False + return True + + def _categories_and_results(etuds, category, result): categories = {} results = {} @@ -416,7 +427,7 @@ def table_suivi_cohorte( and (not annee_bac or (annee_bac == str(etud["annee_bac"]))) and (not civilite or (civilite == etud["civilite"])) and (not statut or (statut == etud["statut"])) - and (not only_primo or context.isPrimoEtud(etud, sem)) + and (not only_primo or is_primo_etud(context, etud, sem)) ): orig_set.add(etudid) # semestres suivants: @@ -708,14 +719,16 @@ def formsemestre_suivi_cohorte( return t base_url = REQUEST.URL0 - burl = ( - "%s?formsemestre_id=%s&bac=%s&bacspecialite=%s&civilite=%s&statut=%s" - % (base_url, formsemestre_id, bac, bacspecialite, civilite, statut) + burl = "%s?formsemestre_id=%s&bac=%s&bacspecialite=%s&civilite=%s&statut=%s" % ( + base_url, + formsemestre_id, + bac, + bacspecialite, + civilite, + statut, ) if percent: - pplink = ( - '

    Afficher les résultats bruts

    ' % burl - ) + pplink = '

    Afficher les résultats bruts

    ' % burl else: pplink = ( '

    Afficher les résultats en pourcentages

    ' @@ -1028,7 +1041,7 @@ def tsp_etud_list( and (not annee_bac or (annee_bac == str(etud["annee_bac"]))) and (not civilite or (civilite == etud["civilite"])) and (not statut or (statut == etud["statut"])) - and (not only_primo or context.isPrimoEtud(etud, sem)) + and (not only_primo or is_primo_etud(context, etud, sem)) ): etuds.append(etud) diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py index 5309ee9cd..98fc06bb1 100644 --- a/app/scodoc/sco_saisie_notes.py +++ b/app/scodoc/sco_saisie_notes.py @@ -349,7 +349,8 @@ def do_evaluation_set_missing( ) # Confirm action if not dialog_confirmed: - return context.confirmDialog( + return scu.confirm_dialog( + context, """

    Mettre toutes les notes manquantes de l'évaluation à la valeur %s ?

    Seuls les étudiants pour lesquels aucune note (ni valeur, ni ABS, ni EXC) @@ -418,7 +419,8 @@ def evaluation_suppress_alln(context, evaluation_id, REQUEST, dialog_confirmed=F msg = "

    Confirmer la suppression des %d notes ?

    " % nb_suppress if existing_decisions: msg += """

    Important: il y a déjà des décisions de jury enregistrées, qui seront potentiellement à revoir suite à cette modification !

    """ - return context.confirmDialog( + return scu.confirm_dialog( + context, msg, dest_url="", REQUEST=REQUEST, diff --git a/app/scodoc/sco_semset.py b/app/scodoc/sco_semset.py index d0e3aa9b7..4e09f58f0 100644 --- a/app/scodoc/sco_semset.py +++ b/app/scodoc/sco_semset.py @@ -362,7 +362,8 @@ def do_semset_delete(context, semset_id, dialog_confirmed=False, REQUEST=None): raise ScoValueError("empty semset_id") s = SemSet(context, semset_id=semset_id) if not dialog_confirmed: - return context.confirmDialog( + return scu.confirm_dialog( + context, "

    Suppression de l'ensemble %(title)s ?

    " % s, dest_url="", REQUEST=REQUEST, diff --git a/app/scodoc/sco_synchro_etuds.py b/app/scodoc/sco_synchro_etuds.py index 10ec89216..d9c99493c 100644 --- a/app/scodoc/sco_synchro_etuds.py +++ b/app/scodoc/sco_synchro_etuds.py @@ -205,7 +205,8 @@ def formsemestre_synchro_etuds( H.append("""

    Il n'y a rien à modifier !

    """) H.append( - context.confirmDialog( + scu.confirm_dialog( + context, dest_url="formsemestre_synchro_etuds", add_headers=False, cancel_url="formsemestre_synchro_etuds?formsemestre_id=" diff --git a/app/scodoc/sco_trombino.py b/app/scodoc/sco_trombino.py index 478d74b45..7d5c34fac 100644 --- a/app/scodoc/sco_trombino.py +++ b/app/scodoc/sco_trombino.py @@ -185,12 +185,12 @@ def check_local_photos_availability(context, groups_infos, REQUEST, format=""): parameters = {"group_ids": groups_infos.group_ids, "format": format} return ( False, - context.confirmDialog( + scu.confirm_dialog( + context, """

    Attention: %d photos ne sont pas disponibles et ne peuvent pas être exportées.

    Vous pouvez exporter seulement les photos existantes""" % ( nb_missing, - groups_infos.base_url - + "&dialog_confirmed=1&format=%s" % format, + groups_infos.base_url + "&dialog_confirmed=1&format=%s" % format, ), dest_url="trombino", OK="Exporter seulement les photos existantes", @@ -252,7 +252,8 @@ def trombino_copy_photos(context, group_ids=[], REQUEST=None, dialog_confirmed=F + footer ) if not dialog_confirmed: - return context.confirmDialog( + return scu.confirm_dialog( + context, """

    Copier les photos du portail vers ScoDoc ?

    Les photos du groupe %s présentes dans ScoDoc seront remplacées par celles du portail (si elles existent).

    (les photos sont normalement automatiquement copiées lors de leur première utilisation, l'usage de cette fonction n'est nécessaire que si les photos du portail ont été modifiées)

    diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 4a78f7783..927109347 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -840,7 +840,25 @@ def log_unknown_etud(context, REQUEST=None, format="html"): "unknown student: etudid=%s code_nip=%s code_ine=%s" % (etudid, code_nip, code_ine) ) - return context.ScoErrorResponse("unknown student", format=format, REQUEST=REQUEST) + return _sco_error_response("unknown student", format=format, REQUEST=REQUEST) + + +# XXX #sco8 à tester ou ré-écrire +def _sco_error_response(context, msg, format="html", REQUEST=None): + """Send an error message to the client, in html or xml format.""" + REQUEST.RESPONSE.setStatus(404, reason=msg) + if format == "html" or format == "pdf": + raise ScoValueError(msg) + elif format == "xml": + REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE) + doc = jaxml.XML_document(encoding=scu.SCO_ENCODING) + doc.error(msg=msg) + return repr(doc) + elif format == "json": + REQUEST.RESPONSE.setHeader("content-type", scu.JSON_MIMETYPE) + return "undefined" # XXX voir quoi faire en cas d'erreur json + else: + raise ValueError("ScoErrorResponse: invalid format") # XXX @@ -869,3 +887,55 @@ def return_text_if_published(val, REQUEST): if REQUEST and not isinstance(val, STRING_TYPES): return sendJSON(REQUEST, val) return val + + +def confirm_dialog( + context, + message="

    Confirmer ?

    ", + OK="OK", + Cancel="Annuler", + dest_url="", + cancel_url="", + target_variable="dialog_confirmed", + parameters={}, + add_headers=True, # complete page + REQUEST=None, # required + helpmsg=None, +): + # dialog de confirmation simple + parameters[target_variable] = 1 + # Attention: la page a pu etre servie en GET avec des parametres + # si on laisse l'url "action" vide, les parametres restent alors que l'on passe en POST... + if not dest_url: + dest_url = REQUEST.URL + # strip remaining parameters from destination url: + dest_url = urllib.splitquery(dest_url)[0] + H = [ + """
    """ % dest_url, + message, + """""" % OK, + ] + if cancel_url: + H.append( + """""" + % (Cancel, cancel_url) + ) + for param in parameters.keys(): + if parameters[param] is None: + parameters[param] = "" + if type(parameters[param]) == type([]): + for e in parameters[param]: + H.append('' % (param, e)) + else: + H.append( + '' + % (param, parameters[param]) + ) + H.append("
    ") + if helpmsg: + H.append('

    ' + helpmsg + "

    ") + if add_headers and REQUEST: + return context.sco_header(REQUEST) + "\n".join(H) + context.sco_footer(REQUEST) + else: + return "\n".join(H) diff --git a/app/scodoc/scolars.py b/app/scodoc/scolars.py index 05ebade47..db991a285 100644 --- a/app/scodoc/scolars.py +++ b/app/scodoc/scolars.py @@ -357,7 +357,8 @@ def _check_duplicate_code(cnx, args, code_name, context, edit=True, REQUEST=None dest_url = "" parameters = {} if context: - err_page = context.confirmDialog( + err_page = scu.confirm_dialog( + context, message="""

    Code étudiant (%s) dupliqué !

    """ % code_name, helpmsg="""Le %s %s est déjà utilisé: un seul étudiant peut avoir ce code. Vérifier votre valeur ou supprimer l'autre étudiant avec cette valeur.