# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Emmanuel Viennet emmanuel.viennet@viennet.net # ############################################################################## """Semestres: validation semestre et UE dans parcours """ import urllib, time, datetime import notesdb as ndb import sco_utils as scu from notes_log import log from scolog import logdb import notes_table from TrivialFormulator import TrivialFormulator, tf_error_message from sco_exceptions import ScoValueError from sco_abs import getAbsSemEtud import sco_formsemestre import sco_formsemestre_edit import sco_formsemestre_status import sco_parcours_dut import sco_codes_parcours from sco_codes_parcours import * import sco_pvjury import sco_photos # ------------------------------------------------------------------------------------ def formsemestre_validation_etud_form( context, # ZNotes instance formsemestre_id=None, # required etudid=None, # one of etudid or etud_index is required etud_index=None, check=0, # opt: si true, propose juste une relecture du parcours desturl=None, sortcol=None, readonly=True, REQUEST=None, ): nt = context._getNotesCache().get_NotesTable( context, formsemestre_id ) # > get_table_moyennes_triees, get_etud_decision_sem T = nt.get_table_moyennes_triees() if not etudid and not etud_index: raise ValueError("formsemestre_validation_etud_form: missing argument etudid") if etud_index: etud_index = int(etud_index) # cherche l'etudid correspondant if etud_index < 0 or etud_index >= len(T): raise ValueError( "formsemestre_validation_etud_form: invalid etud_index value" ) etudid = T[etud_index][-1] else: # cherche index pour liens navigation etud_index = len(T) - 1 while etud_index >= 0 and T[etud_index][-1] != etudid: etud_index -= 1 if etud_index < 0: raise ValueError( "formsemestre_validation_etud_form: can't retreive etud_index !" ) # prev, next pour liens navigation etud_index_next = etud_index + 1 if etud_index_next >= len(T): etud_index_next = None etud_index_prev = etud_index - 1 if etud_index_prev < 0: etud_index_prev = None if readonly: check = True etud = context.getEtudInfo(etudid=etudid, filled=True)[0] Se = sco_parcours_dut.SituationEtudParcours(context, etud, formsemestre_id) if Se.sem["etat"] != "1": raise ScoValueError("validation: semestre verrouille") H = [ context.sco_header( REQUEST, page_title="Parcours %(nomprenom)s" % etud, javascripts=["js/recap_parcours.js"], ) ] Footer = ["

"] # Navigation suivant/precedent if etud_index_prev != None: etud_p = context.getEtudInfo(etudid=T[etud_index_prev][-1], filled=True)[0] Footer.append( 'Etud. précédent (%s)' % (formsemestre_id, etud_index_prev, etud_p["nomprenom"]) ) if etud_index_next != None: etud_n = context.getEtudInfo(etudid=T[etud_index_next][-1], filled=True)[0] Footer.append( 'Etud. suivant (%s)' % (formsemestre_id, etud_index_next, etud_n["nomprenom"]) ) Footer.append("

") Footer.append(context.sco_footer(REQUEST)) H.append('
') if not check: H.append( '

%s: validation %s%s

Parcours: %s' % ( etud["nomprenom"], Se.parcours.SESSION_NAME_A, Se.parcours.SESSION_NAME, Se.get_parcours_descr(), ) ) else: H.append( '

Parcours de %s

%s' % (etud["nomprenom"], Se.get_parcours_descr()) ) H.append( '
%s
' % ( context.ScoURL(), etudid, sco_photos.etud_photo_html( context, etud, title="fiche de %s" % etud["nom"], REQUEST=REQUEST ), ) ) etud_etat = nt.get_etud_etat(etudid) if etud_etat == "D": H.append('
Etudiant démissionnaire
') if etud_etat == DEF: H.append('
Etudiant défaillant
') if etud_etat != "I": H.append( tf_error_message( """Impossible de statuer sur cet étudiant: il est démissionnaire ou défaillant (voir sa fiche)""" % (context.ScoURL(), etudid) ) ) return "\n".join(H + Footer) H.append( formsemestre_recap_parcours_table( context, Se, etudid, with_links=(check and not readonly) ) ) if check: if not desturl: desturl = ( "formsemestre_recapcomplet?modejury=1&hidemodules=1&hidebac=1&pref_override=0&formsemestre_id=" + formsemestre_id ) if sortcol: desturl += ( "&sortcol=" + sortcol ) # pour refaire tri sorttable du tableau de notes desturl += "#etudid%s" % etudid # va a la bonne ligne H.append('' % desturl) return "\n".join(H + Footer) decision_jury = Se.nt.get_etud_decision_sem(etudid) # Bloque si note en attente if nt.etud_has_notes_attente(etudid): H.append( tf_error_message( """Impossible de statuer sur cet étudiant: il a des notes en attente dans des évaluations de ce semestre (voir tableau de bord)""" % formsemestre_id ) ) return "\n".join(H + Footer) # Infos si pas de semestre précédent if not Se.prev: if Se.sem["semestre_id"] == 1: H.append("

Premier semestre (pas de précédent)

") else: H.append("

Pas de semestre précédent !

") else: if not Se.prev_decision: H.append( tf_error_message( """Le jury n\'a pas statué sur le semestre précédent ! (le faire maintenant)""" % (Se.prev["formsemestre_id"], etudid) ) ) if decision_jury: H.append( 'Supprimer décision existante' % (etudid, formsemestre_id) ) H.append(context.sco_footer(REQUEST)) return "\n".join(H) # Infos sur decisions déjà saisies if decision_jury: if decision_jury["assidu"]: ass = "assidu" else: ass = "non assidu" H.append("

Décision existante du %(event_date)s: %(code)s" % decision_jury) H.append(" (%s)" % ass) auts = sco_parcours_dut.formsemestre_get_autorisation_inscription( context, etudid, formsemestre_id ) if auts: H.append(". Autorisé%s à s'inscrire en " % etud["ne"]) alist = [] for aut in auts: alist.append(str(aut["semestre_id"])) H.append(", ".join(["S%s" % x for x in alist]) + ".") H.append("

") # Cas particulier pour ATJ: corriger precedent avant de continuer if Se.prev_decision and Se.prev_decision["code"] == ATJ: H.append( """

La décision du semestre précédent est en attente à cause d\'un problème d\'assiduité.

Vous devez la corriger avant de continuer ce jury. Soit vous considérez que le problème d'assiduité n'est pas réglé et choisissez de ne pas valider le semestre précédent (échec), soit vous entrez une décision sans prendre en compte l'assiduité.

""" % (Se.prev["formsemestre_id"], etudid, etudid, formsemestre_id) ) if sortcol: H.append('' % sortcol) H.append("
") H.append(context.sco_footer(REQUEST)) return "\n".join(H) # Explication sur barres actuelles H.append('

L\'étudiant ') if Se.barre_moy_ok: H.append("a la moyenne générale, ") else: H.append("n'a pas la moyenne générale, ") H.append(Se.barres_ue_diag) # eg 'les UEs sont au dessus des barres' if (not Se.barre_moy_ok) and Se.can_compensate_with_prev: H.append(", et ce semestre peut se compenser avec le précédent") H.append(".

") # Décisions possibles rows_assidu = decisions_possible_rows( Se, True, subtitle="Etudiant assidu:", trclass="sfv_ass" ) rows_non_assidu = decisions_possible_rows( Se, False, subtitle="Si problème d'assiduité:", trclass="sfv_pbass" ) # s'il y a des decisions recommandees issues des regles: if rows_assidu or rows_non_assidu: H.append( """
""" % (etudid, formsemestre_id) ) if desturl: H.append('' % desturl) if sortcol: H.append('' % sortcol) H.append('

Décisions recommandées :

') H.append("") H.append(rows_assidu) if rows_non_assidu: H.append("") # spacer H.append(rows_non_assidu) H.append("
 
") H.append( '


' ) H.append("
") H.append(form_decision_manuelle(context, Se, formsemestre_id, etudid)) H.append( """""" % (etudid, formsemestre_id) ) H.append('

Formation ') if Se.sem["gestion_semestrielle"] == "1": H.append("avec semestres décalés

") else: H.append("sans semestres décalés

") return "".join(H + Footer) def formsemestre_validation_etud( context, # ZNotes instance formsemestre_id=None, # required etudid=None, # required codechoice=None, # required desturl="", sortcol=None, REQUEST=None, ): """Enregistre validation""" etud = context.getEtudInfo(etudid=etudid, filled=True)[0] Se = sco_parcours_dut.SituationEtudParcours(context, etud, formsemestre_id) # retrouve la decision correspondant au code: choices = Se.get_possible_choices(assiduite=True) choices += Se.get_possible_choices(assiduite=False) found = False for choice in choices: if choice.codechoice == codechoice: found = True break if not found: raise ValueError("code choix invalide ! (%s)" % codechoice) # Se.valide_decision(choice, REQUEST) # enregistre return _redirect_valid_choice( formsemestre_id, etudid, Se, choice, desturl, sortcol, REQUEST ) def formsemestre_validation_etud_manu( context, # ZNotes instance formsemestre_id=None, # required etudid=None, # required code_etat="", new_code_prev="", devenir="", # required (la decision manuelle) assidu=False, desturl="", sortcol=None, REQUEST=None, redirect=True, ): """Enregistre validation""" if assidu: assidu = 1 etud = context.getEtudInfo(etudid=etudid, filled=True)[0] Se = sco_parcours_dut.SituationEtudParcours(context, etud, formsemestre_id) if code_etat in Se.parcours.UNUSED_CODES: raise ScoValueError("code decision invalide dans ce parcours") # Si code ADC, extrait le semestre utilisé: if code_etat[:3] == ADC: formsemestre_id_utilise_pour_compenser = code_etat.split("_")[1] if not formsemestre_id_utilise_pour_compenser: formsemestre_id_utilise_pour_compenser = ( None # compense avec semestre hors ScoDoc ) code_etat = ADC else: formsemestre_id_utilise_pour_compenser = None # Construit le choix correspondant: choice = sco_parcours_dut.DecisionSem( code_etat=code_etat, new_code_prev=new_code_prev, devenir=devenir, assiduite=assidu, formsemestre_id_utilise_pour_compenser=formsemestre_id_utilise_pour_compenser, ) # Se.valide_decision(choice, REQUEST) # enregistre if redirect: return _redirect_valid_choice( formsemestre_id, etudid, Se, choice, desturl, sortcol, REQUEST ) def _redirect_valid_choice( formsemestre_id, etudid, Se, choice, desturl, sortcol, REQUEST ): adr = "formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&check=1" % ( formsemestre_id, etudid, ) if sortcol: adr += "&sortcol=" + sortcol # if desturl: # desturl += "&desturl=" + desturl return REQUEST.RESPONSE.redirect(adr) # Si le precedent a été modifié, demande relecture du parcours. # sinon renvoie au listing general, # if choice.new_code_prev: # REQUEST.RESPONSE.redirect( 'formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&check=1&desturl=%s' % (formsemestre_id, etudid, desturl) ) # else: # if not desturl: # desturl = 'formsemestre_recapcomplet?modejury=1&hidemodules=1&formsemestre_id=' + formsemestre_id # REQUEST.RESPONSE.redirect(desturl) def _dispcode(c): if not c: return "" return c def decisions_possible_rows(Se, assiduite, subtitle="", trclass=""): "Liste HTML des decisions possibles" choices = Se.get_possible_choices(assiduite=assiduite) if not choices: return "" TitlePrev = "" if Se.prev: if Se.prev["semestre_id"] >= 0: TitlePrev = "%s%d" % (Se.parcours.SESSION_ABBRV, Se.prev["semestre_id"]) else: TitlePrev = "Prec." if Se.sem["semestre_id"] >= 0: TitleCur = "%s%d" % (Se.parcours.SESSION_ABBRV, Se.sem["semestre_id"]) else: TitleCur = Se.parcours.SESSION_NAME H = [ '%s' % (trclass, subtitle) ] if Se.prev: H.append("Code %s" % TitlePrev) H.append("Code %sDevenir" % TitleCur) for ch in choices: H.append( """""" % (trclass, ch.rule_id, ch.codechoice, ch.rule_id) ) H.append("%s " % ch.explication) if Se.prev: H.append('%s' % _dispcode(ch.new_code_prev)) H.append( '%s%s' % (_dispcode(ch.code_etat), Se.explique_devenir(ch.devenir)) ) H.append("") return "\n".join(H) def formsemestre_recap_parcours_table( context, Se, etudid, with_links=False, with_all_columns=True, a_url="", sem_info={}, show_details=False, ): """Tableau HTML recap parcours Si with_links, ajoute liens pour modifier decisions (colonne de droite) sem_info = { formsemestre_id : txt } permet d'ajouter des informations associées à chaque semestre with_all_columns: si faux, pas de colonne "assiduité". """ H = [] linktmpl = '%s' minuslink = linktmpl % scu.icontag("minus_img", border="0", alt="-") pluslink = linktmpl % scu.icontag("plus_img", border="0", alt="+") if show_details: sd = " recap_show_details" plusminus = minuslink else: sd = " recap_hide_details" plusminus = pluslink H.append('' % sd) H.append( '' % scu.icontag("plus18_img", width=18, height=18, border=0, title="", alt="+") ) H.append("") # titres des UE H.append("" * Se.nb_max_ue) # if with_links: H.append("") H.append("") num_sem = 0 for sem in Se.get_semestres(): is_prev = Se.prev and (Se.prev["formsemestre_id"] == sem["formsemestre_id"]) is_cur = Se.formsemestre_id == sem["formsemestre_id"] num_sem += 1 dpv = sco_pvjury.dict_pvjury(context, sem["formsemestre_id"], etudids=[etudid]) pv = dpv["decisions"][0] decision_sem = pv["decision_sem"] decisions_ue = pv["decisions_ue"] if with_all_columns and decision_sem and decision_sem["assidu"] == 0: ass = " (non ass.)" else: ass = "" nt = context._getNotesCache().get_NotesTable( context, sem["formsemestre_id"] ) # > get_ues, get_etud_moy_gen, get_etud_ue_status if is_cur: type_sem = "*" # now unused class_sem = "sem_courant" elif is_prev: type_sem = "p" class_sem = "sem_precedent" else: type_sem = "" class_sem = "sem_autre" if sem["formation_code"] != Se.formation["formation_code"]: class_sem += " sem_autre_formation" if sem["bul_bgcolor"]: bgcolor = sem["bul_bgcolor"] else: bgcolor = "background-color: rgb(255,255,240)" # 1ere ligne: titre sem, decision, acronymes UE H.append('' % (class_sem, sem["formsemestre_id"])) if is_cur: pm = "" elif is_prev: pm = minuslink % sem["formsemestre_id"] else: pm = plusminus % sem["formsemestre_id"] H.append( '' % (bgcolor, num_sem, pm) ) H.append('' % sem) H.append( '' % (a_url, sem["formsemestre_id"], etudid, sem["titreannee"]) ) if decision_sem: H.append('' % decision_sem["code"]) else: H.append('') H.append('' % ass) # abs # acronymes UEs ues = nt.get_ues(filter_sport=True, filter_non_inscrit=True, etudid=etudid) for ue in ues: H.append('' % ue["acronyme"]) if len(ues) < Se.nb_max_ue: H.append('' % (Se.nb_max_ue - len(ues))) # indique le semestre compensé par celui ci: if decision_sem and decision_sem["compense_formsemestre_id"]: csem = sco_formsemestre.get_formsemestre( context, decision_sem["compense_formsemestre_id"] ) H.append("" % csem["semestre_id"]) else: H.append("") if with_links: H.append("") H.append("") # 2eme ligne: notes H.append('' % (class_sem, sem["formsemestre_id"])) H.append( '' % (bgcolor) ) if is_prev: default_sem_info = '[sem. précédent]' else: default_sem_info = "" if sem["etat"] != "1": # locked lockicon = scu.icontag("lock32_img", title="verrouillé", border="0") default_sem_info += lockicon if sem["formation_code"] != Se.formation["formation_code"]: default_sem_info += "Autre formation: %s" % sem["formation_code"] H.append( '' % (sem["mois_fin"], sem_info.get(sem["formsemestre_id"], default_sem_info)) ) # Moy Gen (sous le code decision) H.append( '' % scu.fmt_note(nt.get_etud_moy_gen(etudid)) ) # Absences (nb d'abs non just. dans ce semestre) AbsEtudSem = getAbsSemEtud(context, sem, etudid) nbabs = AbsEtudSem.CountAbs() nbabsjust = AbsEtudSem.CountAbsJust() H.append('' % (nbabs - nbabsjust)) # UEs for ue in ues: if decisions_ue and decisions_ue.has_key(ue["ue_id"]): code = decisions_ue[ue["ue_id"]]["code"] else: code = "" ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"]) moy_ue = ue_status["moy"] explanation_ue = [] # list of strings if code == ADM: class_ue = "ue_adm" elif code == CMP: class_ue = "ue_cmp" else: class_ue = "ue" if ue_status["is_external"]: # validation externe explanation_ue.append("UE externe.") # log('x'*12+' EXTERNAL %s' % notes_table.fmt_note(moy_ue)) XXXXXXX # log('UE=%s' % pprint.pformat(ue)) # log('explanation_ue=%s\n'%explanation_ue) if ue_status["is_capitalized"]: class_ue += " ue_capitalized" explanation_ue.append( "Capitalisée le %s." % (ue_status["event_date"] or "?") ) # log('x'*12+' CAPITALIZED %s' % notes_table.fmt_note(moy_ue)) # log('UE=%s' % pprint.pformat(ue)) # log('UE_STATUS=%s' % pprint.pformat(ue_status)) XXXXXX # log('') H.append( '' % ( class_ue, ue["acronyme"], " ".join(explanation_ue), scu.fmt_note(moy_ue), ) ) if len(ues) < Se.nb_max_ue: H.append('' % (Se.nb_max_ue - len(ues))) H.append("") if with_links: H.append( '' % (a_url, sem["formsemestre_id"], etudid) ) H.append("") # 3eme ligne: ECTS if ( context.get_preference("bul_show_ects", sem["formsemestre_id"]) or nt.parcours.ECTS_ONLY ): etud_moy_infos = nt.get_etud_moy_infos(etudid) H.append( '' % (class_sem, sem["formsemestre_id"]) ) H.append( '' % (bgcolor) ) # total ECTS (affiché sous la moyenne générale) H.append( '' % (etud_moy_infos["ects_pot"], etud_moy_infos["ects_pot_fond"]) ) H.append('') # ECTS validables dans chaque UE for ue in ues: ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"]) H.append( '' % (ue_status["ects_pot"], ue_status["ects_pot_fond"]) ) H.append("") H.append("
%sSemestreEtatAbs
%s%s%(mois_debut)s%s%sen cours%s%scompense S%s
 %s%s%s%d%smodifier
 ECTS:%g %g%g%g
") return "\n".join(H) def form_decision_manuelle( context, Se, formsemestre_id, etudid, desturl="", sortcol=None ): """Formulaire pour saisie décision manuelle""" H = [ """
""" % (etudid, formsemestre_id) ] if desturl: H.append('' % desturl) if sortcol: H.append('' % sortcol) H.append( '

Décisions manuelles : (vérifiez bien votre choix !)

' ) # Choix code semestre: codes = sco_codes_parcours.CODES_EXPL.keys() codes.sort() # fortuitement, cet ordre convient bien ! H.append( '") # Choix code semestre precedent: if Se.prev: H.append( '") # Choix code devenir codes = sco_codes_parcours.DEVENIR_EXPL.keys() codes.sort() # fortuitement, cet ordre convient aussi bien ! if Se.sem["semestre_id"] == -1: allowed_codes = sco_codes_parcours.DEVENIRS_MONO else: allowed_codes = set(sco_codes_parcours.DEVENIRS_STD) # semestres decales ? if Se.sem["gestion_semestrielle"] == "1": allowed_codes = allowed_codes.union(sco_codes_parcours.DEVENIRS_DEC) # n'autorise les codes NEXT2 que si semestres décalés et s'il ne manque qu'un semestre avant le n+2 if Se.can_jump_to_next2(): allowed_codes = allowed_codes.union(sco_codes_parcours.DEVENIRS_NEXT2) H.append( '") H.append( '' ) H.append( """
Code semestre:
Code semestre précédent:
Devenir:
assidu
Supprimer décision existante
""" % (etudid, formsemestre_id) ) return "\n".join(H) # ----------- def formsemestre_validation_auto(context, formsemestre_id, REQUEST): "Formulaire saisie automatisee des decisions d'un semestre" sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) H = [ context.html_sem_header( REQUEST, "Saisie automatique des décisions du semestre", sem ), """

Il est donc vivement conseillé de relire soigneusement les décisions à l'issue de cette procédure !

Le calcul prend quelques minutes, soyez patients !

""" % formsemestre_id, context.sco_footer(REQUEST), ] return "\n".join(H) def do_formsemestre_validation_auto(context, formsemestre_id, REQUEST): "Saisie automatisee des decisions d'un semestre" sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) next_semestre_id = sem["semestre_id"] + 1 nt = context._getNotesCache().get_NotesTable( context, formsemestre_id ) # > get_etudids, get_etud_decision_sem, etudids = nt.get_etudids() nb_valid = 0 conflicts = [] # liste des etudiants avec decision differente déjà saisie for etudid in etudids: etud = context.getEtudInfo(etudid=etudid, filled=True)[0] Se = sco_parcours_dut.SituationEtudParcours(context, etud, formsemestre_id) ins = context.do_formsemestre_inscription_list( {"etudid": etudid, "formsemestre_id": formsemestre_id} )[0] # Conditions pour validation automatique: if ins["etat"] == "I" and ( ( (not Se.prev) or (Se.prev_decision and Se.prev_decision["code"] in (ADM, ADC, ADJ)) ) and Se.barre_moy_ok and Se.barres_ue_ok and not nt.etud_has_notes_attente(etudid) ): # check: s'il existe une decision ou autorisation et qu'elles sont differentes, # warning (et ne fait rien) decision_sem = nt.get_etud_decision_sem(etudid) ok = True if decision_sem and decision_sem["code"] != ADM: ok = False conflicts.append(etud) autorisations = sco_parcours_dut.formsemestre_get_autorisation_inscription( context, etudid, formsemestre_id ) if ( len(autorisations) != 0 ): # accepte le cas ou il n'y a pas d'autorisation : BUG 23/6/7, A RETIRER ENSUITE if ( len(autorisations) != 1 or autorisations[0]["semestre_id"] != next_semestre_id ): if ok: conflicts.append(etud) ok = False # ok, valide ! if ok: formsemestre_validation_etud_manu( context, formsemestre_id, etudid, code_etat=ADM, devenir="NEXT", assidu=True, REQUEST=REQUEST, redirect=False, ) nb_valid += 1 log( "do_formsemestre_validation_auto: %d validations, %d conflicts" % (nb_valid, len(conflicts)) ) H = [context.sco_header(REQUEST, page_title="Saisie automatique")] H.append( """

Saisie automatique des décisions du semestre %s

Opération effectuée.

%d étudiants validés (sur %s)

""" % (sem["titreannee"], nb_valid, len(etudids)) ) if conflicts: H.append( """

Attention: %d étudiants non modifiés car décisions différentes déja saisies :

") H.append( 'continuer' % formsemestre_id ) H.append(context.sco_footer(REQUEST)) return "\n".join(H) def formsemestre_fix_validation_ues(context, formsemestre_id, REQUEST=None): """ Suite à un bug (fix svn 502, 26 juin 2008), les UE sans notes ont parfois été validées avec un code ADM (au lieu de AJ ou ADC, suivant le code semestre). Cette fonction vérifie les codes d'UE et les modifie si nécessaire. Si semestre validé: decision UE == CMP ou ADM Sinon: si moy UE > barre et assiduité au semestre: ADM, sinon AJ N'affecte que le semestre indiqué, pas les précédents. """ sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) nt = context._getNotesCache().get_NotesTable( context, formsemestre_id ) # > get_etudids, get_etud_decision_sem, get_ues, get_etud_decision_ues, get_etud_ue_status etudids = nt.get_etudids() modifs = [] # liste d'étudiants modifiés cnx = context.GetDBConnexion(autocommit=False) for etudid in etudids: etud = context.getEtudInfo(etudid=etudid, filled=True)[0] Se = sco_parcours_dut.SituationEtudParcours(context, etud, formsemestre_id) ins = context.do_formsemestre_inscription_list( {"etudid": etudid, "formsemestre_id": formsemestre_id} )[0] decision_sem = nt.get_etud_decision_sem(etudid) if not decision_sem: continue # pas encore de decision semestre valid_semestre = CODES_SEM_VALIDES.get(decision_sem["code"], False) ue_ids = [x["ue_id"] for x in nt.get_ues(etudid=etudid, filter_sport=True)] for ue_id in ue_ids: existing_code = nt.get_etud_decision_ues(etudid)[ue_id]["code"] if existing_code == None: continue # pas encore de decision UE ue_status = nt.get_etud_ue_status(etudid, ue_id) moy_ue = ue_status["moy"] if valid_semestre: if ( type(moy_ue) == FloatType and ue_status["moy"] >= nt.parcours.NOTES_BARRE_VALID_UE ): code_ue = ADM else: code_ue = CMP else: if not decision_sem["assidu"]: code_ue = AJ elif ( type(moy_ue) == FloatType and ue_status["moy"] >= nt.parcours.NOTES_BARRE_VALID_UE ): code_ue = ADM else: code_ue = AJ if code_ue != existing_code: msg = "%s: %s: code %s changé en %s" % ( etud["nomprenom"], ue_id, existing_code, code_ue, ) modifs.append(msg) log(msg) sco_parcours_dut.do_formsemestre_validate_ue( cnx, nt, formsemestre_id, etudid, ue_id, code_ue ) cnx.commit() # H = [ context.sco_header(REQUEST, page_title="Réparation des codes UE"), sco_formsemestre_status.formsemestre_status_head( context, REQUEST=REQUEST, formsemestre_id=formsemestre_id ), ] if modifs: H = H + [ "

Modifications des codes UE

", "", ] context._inval_cache(formsemestre_id=formsemestre_id) # > modif decision UE else: H.append("

Aucune modification: codes UE corrects ou inexistants

") H.append(context.sco_footer(REQUEST)) return "\n".join(H) def formsemestre_validation_suppress_etud(context, formsemestre_id, etudid): """Suppression des decisions de jury pour un etudiant.""" log("formsemestre_validation_suppress_etud( %s, %s)" % (formsemestre_id, etudid)) cnx = context.GetDBConnexion(autocommit=False) cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) args = {"formsemestre_id": formsemestre_id, "etudid": etudid} try: # -- Validation du semestre et des UEs cursor.execute( """delete from scolar_formsemestre_validation where etudid = %(etudid)s and formsemestre_id=%(formsemestre_id)s""", args, ) # -- Autorisations d'inscription cursor.execute( """delete from scolar_autorisation_inscription where etudid = %(etudid)s and origin_formsemestre_id=%(formsemestre_id)s""", args, ) cnx.commit() except: cnx.rollback() raise sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) _invalidate_etud_formation_caches( context, etudid, sem["formation_id"] ) # > suppr. decision jury (peut affecter de plusieurs semestres utilisant UE capitalisée) def formsemestre_validate_previous_ue(context, formsemestre_id, etudid, REQUEST=None): """Form. saisie UE validée hors ScoDoc (pour étudiants arrivant avec un UE antérieurement validée). """ etud = context.getEtudInfo(etudid=etudid, filled=True)[0] sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) Fo = context.formation_list(args={"formation_id": sem["formation_id"]})[0] H = [ context.sco_header( REQUEST, page_title="Validation UE", javascripts=["js/validate_previous_ue.js"], ), '
', """

%s: validation d'une UE antérieure

""" % etud["nomprenom"], ( '
%s
' % ( context.ScoURL(), etudid, sco_photos.etud_photo_html( context, etud, title="fiche de %s" % etud["nom"], REQUEST=REQUEST ), ) ), """

Utiliser cette page pour enregistrer une UE validée antérieurement, dans un semestre hors ScoDoc.

Les UE validées dans ScoDoc sont déjà automatiquement prises en compte. Cette page n'est utile que pour les étudiants ayant suivi un début de cursus dans un autre établissement, ou bien dans un semestre géré sans ScoDoc et qui redouble ce semestre (ne pas utiliser pour les semestres précédents !).

Notez que l'UE est validée, avec enregistrement immédiat de la décision et l'attribution des ECTS.

""", "

On ne peut prendre en compte ici que les UE du cursus %(titre)s

" % Fo, ] # Toutes les UE de cette formation sont présentées (même celles des autres semestres) ues = context.do_ue_list({"formation_id": Fo["formation_id"]}) ue_names = ["Choisir..."] + ["%(acronyme)s %(titre)s" % ue for ue in ues] ue_ids = [""] + [ue["ue_id"] for ue in ues] tf = TrivialFormulator( REQUEST.URL0, REQUEST.form, ( ("etudid", {"input_type": "hidden"}), ("formsemestre_id", {"input_type": "hidden"}), ( "ue_id", { "input_type": "menu", "title": "Unité d'Enseignement (UE)", "allow_null": False, "allowed_values": ue_ids, "labels": ue_names, }, ), ( "semestre_id", { "input_type": "menu", "title": "Indice du semestre", "explanation": "Facultatif: indice du semestre dans la formation", "allow_null": True, "allowed_values": [""] + [str(x) for x in range(11)], "labels": ["-"] + range(11), }, ), ( "date", { "input_type": "date", "size": 9, "explanation": "j/m/a", "default": time.strftime("%d/%m/%Y"), }, ), ( "moy_ue", { "type": "float", "allow_null": False, "min_value": 0, "max_value": 20, "title": "Moyenne (/20) obtenue dans cette UE:", }, ), ), cancelbutton="Annuler", submitlabel="Enregistrer validation d'UE", ) if tf[0] == 0: X = """
""" warn, ue_multiples = check_formation_ues(context, Fo["formation_id"]) return "\n".join(H) + tf[1] + X + warn + context.sco_footer(REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect( context.NotesURL() + "/formsemestre_status?formsemestre_id=" + formsemestre_id ) else: if tf[2]["semestre_id"]: semestre_id = int(tf[2]["semestre_id"]) else: semestre_id = None do_formsemestre_validate_previous_ue( context, formsemestre_id, etudid, tf[2]["ue_id"], tf[2]["moy_ue"], tf[2]["date"], semestre_id=semestre_id, REQUEST=REQUEST, ) return REQUEST.RESPONSE.redirect( context.ScoURL() + "/Notes/formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s&head_message=Validation%%20d'UE%%20enregistree" % (formsemestre_id, etudid) ) def do_formsemestre_validate_previous_ue( context, formsemestre_id, etudid, ue_id, moy_ue, date, code=ADM, semestre_id=None, ue_coefficient=None, REQUEST=None, ): """Enregistre (ou modifie) validation d'UE (obtenue hors ScoDoc). Si le coefficient est spécifié, modifie le coefficient de cette UE (utile seulement pour les semestres extérieurs). """ sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) cnx = context.GetDBConnexion(autocommit=False) nt = context._getNotesCache().get_NotesTable( context, formsemestre_id ) # > get_etud_ue_status if ue_coefficient != None: sco_formsemestre.do_formsemestre_uecoef_edit_or_create( context, cnx, formsemestre_id, ue_id, ue_coefficient ) else: sco_formsemestre.do_formsemestre_uecoef_delete( context, cnx, formsemestre_id, ue_id ) sco_parcours_dut.do_formsemestre_validate_ue( cnx, nt, formsemestre_id, # "importe" cette UE dans le semestre (new 3/2015) etudid, ue_id, code, moy_ue=moy_ue, date=date, semestre_id=semestre_id, is_external=1, ) logdb( REQUEST, cnx, method="formsemestre_validate_previous_ue", etudid=etudid, msg="Validation UE %s" % ue_id, commit=False, ) _invalidate_etud_formation_caches(context, etudid, sem["formation_id"]) cnx.commit() def _invalidate_etud_formation_caches(context, etudid, formation_id): "Invalide tous les semestres de cette formation où l'etudiant est inscrit..." r = ndb.SimpleDictFetch( context, """SELECT sem.* FROM notes_formsemestre sem, notes_formsemestre_inscription i WHERE sem.formation_id = %(formation_id)s AND i.formsemestre_id = sem.formsemestre_id AND i.etudid = %(etudid)s """, {"etudid": etudid, "formation_id": formation_id}, ) for fsid in [s["formsemestre_id"] for s in r]: context._inval_cache( formsemestre_id=fsid ) # > modif decision UE (inval tous semestres avec cet etudiant, ok mais conservatif) def get_etud_ue_cap_html(context, etudid, formsemestre_id, ue_id, REQUEST=None): """Ramene bout de HTML pour pouvoir supprimer une validation de cette UE""" valids = ndb.SimpleDictFetch( context, """SELECT SFV.* FROM scolar_formsemestre_validation SFV WHERE ue_id=%(ue_id)s AND etudid=%(etudid)s""", {"etudid": etudid, "ue_id": ue_id}, ) if not valids: return "" H = [ '
Validations existantes pour cette UE:
    ' ] for valid in valids: valid["event_date"] = ndb.DateISOtoDMY(valid["event_date"]) if valid["moy_ue"] != None: valid["m"] = ", moyenne %(moy_ue)g/20" % valid else: valid["m"] = "" if valid["formsemestre_id"]: sem = sco_formsemestre.get_formsemestre(context, valid["formsemestre_id"]) valid["s"] = ", du semestre %s" % sem["titreannee"] else: valid["s"] = " enregistrée d'un parcours antérieur (hors ScoDoc)" if valid["semestre_id"]: valid["s"] += " (S%d)" % valid["semestre_id"] valid["ds"] = formsemestre_id H.append( '
  • %(code)s%(m)s%(s)s, le %(event_date)s effacer
  • ' % valid ) H.append("
") return "\n".join(H) def etud_ue_suppress_validation(context, etudid, formsemestre_id, ue_id, REQUEST=None): """Suppress a validation (ue_id, etudid) and redirect to formsemestre""" log("etud_ue_suppress_validation( %s, %s, %s)" % (etudid, formsemestre_id, ue_id)) cnx = context.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor.execute( "DELETE FROM scolar_formsemestre_validation WHERE etudid=%(etudid)s and ue_id=%(ue_id)s", {"etudid": etudid, "ue_id": ue_id}, ) sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) _invalidate_etud_formation_caches(context, etudid, sem["formation_id"]) return REQUEST.RESPONSE.redirect( context.NotesURL() + "/formsemestre_validate_previous_ue?etudid=%s&formsemestre_id=%s" % (etudid, formsemestre_id) ) def check_formation_ues(context, formation_id): """Verifie que les UE d'une formation sont chacune utilisée dans un seul semestre_id Si ce n'est pas le cas, c'est probablement (mais pas forcément) une erreur de définition du programme: cette fonction retourne un bout de HTML à afficher pour prévenir l'utilisateur, ou '' si tout est ok. """ ues = context.do_ue_list({"formation_id": formation_id}) ue_multiples = {} # { ue_id : [ liste des formsemestre ] } for ue in ues: # formsemestres utilisant cette ue ? sems = ndb.SimpleDictFetch( context, """SELECT DISTINCT sem.* FROM notes_formsemestre sem, notes_modules mod, notes_moduleimpl mi WHERE sem.formation_id = %(formation_id)s AND mod.module_id = mi.module_id AND mi.formsemestre_id = sem.formsemestre_id AND mod.ue_id = %(ue_id)s""", {"ue_id": ue["ue_id"], "formation_id": formation_id}, ) semestre_ids = set([x["semestre_id"] for x in sems]) if ( len(semestre_ids) > 1 ): # plusieurs semestres d'indices differents dans le cursus ue_multiples[ue["ue_id"]] = sems if not ue_multiples: return "", {} # Genere message HTML: H = [ """
Attention: les UE suivantes de cette formation sont utilisées dans des semestres de rangs différents (eg S1 et S3).
Cela peut engendrer des problèmes pour la capitalisation des UE. Il serait préférable d'essayer de rectifier cette situation: soit modifier le programme de la formation (définir des UE dans chaque semestre), soit veiller à saisir le bon indice de semestre dans le menu lors de la validation d'une UE extérieure.
    """ ] for ue in ues: if ue["ue_id"] in ue_multiples: sems = [ sco_formsemestre.get_formsemestre(context, x["formsemestre_id"]) for x in ue_multiples[ue["ue_id"]] ] slist = ", ".join( ["%(titreannee)s (semestre %(semestre_id)s)" % s for s in sems] ) H.append("
  • %s : %s
  • " % (ue["acronyme"], slist)) H.append("
") return "\n".join(H), ue_multiples