# -*- 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 # ############################################################################## """ScoDoc: génération feuille émargement et placement Contribution M. Salomon, UFC / IUT DE BELFORT-MONTBÉLIARD, 2016 """ import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error import random import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc.notes_log import log from app.scodoc import html_sco_header from app.scodoc import sco_edit_module from app.scodoc import sco_evaluations from app.scodoc import sco_excel from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl from app.scodoc import sco_permissions_check from app.scodoc import sco_preferences from app.scodoc import sco_saisie_notes from app.scodoc import sco_etud from app.scodoc import VERSION from app.scodoc.gen_tables import GenTable from app.scodoc.sco_excel import * from app.scodoc.TrivialFormulator import TrivialFormulator def do_placement_selectetuds(context, REQUEST): """ Choisi les étudiants et les infos sur la salle pour leur placement. """ evaluation_id = REQUEST.form["evaluation_id"] E = sco_evaluations.do_evaluation_list(context, {"evaluation_id": evaluation_id}) if not E: raise ScoValueError("invalid evaluation_id") E = E[0] # M = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=E["moduleimpl_id"])[0] # groupes groups = sco_groups.do_evaluation_listegroupes( context, evaluation_id, include_default=True ) grlabs = [g["group_name"] or "tous" for g in groups] # legendes des boutons grnams = [g["group_id"] for g in groups] # noms des checkbox no_groups = (len(groups) == 1) and groups[0]["group_name"] is None # description de l'evaluation H = [ sco_evaluations.evaluation_describe( context, evaluation_id=evaluation_id, REQUEST=REQUEST ), "<h3>Placement et émargement des étudiants</h3>", ] # descr = [ ("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}), ( "placement_method", { "input_type": "radio", "default": "xls", "allow_null": False, "allowed_values": ["pdf", "xls"], "labels": ["fichier pdf", "fichier xls"], "title": "Format de fichier :", }, ), ("teachers", {"size": 25, "title": "Surveillants :"}), ("building", {"size": 25, "title": "Batiment :"}), ("room", {"size": 10, "title": "Salle :"}), ( "columns", { "input_type": "radio", "default": "5", "allow_null": False, "allowed_values": ["3", "4", "5", "6", "7", "8"], "labels": [ "3 colonnes", "4 colonnes", "5 colonnes", "6 colonnes", "7 colonnes", "8 colonnes", ], "title": "Nombre de colonnes :", }, ), ( "numbering", { "input_type": "radio", "default": "coordinate", "allow_null": False, "allowed_values": ["continuous", "coordinate"], "labels": ["continue", "coordonnées"], "title": "Numérotation :", }, ), ] if no_groups: submitbuttonattributes = [] descr += [ ( "group_ids", { "default": [ g["group_id"] # pylint: disable=invalid-sequence-index for g in groups ], "input_type": "hidden", "type": "list", }, ) ] else: descr += [ ( "group_ids", { "input_type": "checkbox", "title": "Choix groupe(s) d'étudiants :", "allowed_values": grnams, "labels": grlabs, "attributes": ['onchange="gr_change(this);"'], }, ) ] if not ("group_ids" in REQUEST.form and REQUEST.form["group_ids"]): submitbuttonattributes = ['disabled="1"'] else: submitbuttonattributes = [] # groupe(s) preselectionnés H.append( # JS pour desactiver le bouton OK si aucun groupe selectionné """<script type="text/javascript"> function gr_change(e) { var boxes = document.getElementsByName("group_ids:list"); var nbchecked = 0; for (var i=0; i < boxes.length; i++) { if (boxes[i].checked) nbchecked++; } if (nbchecked > 0) { document.getElementsByName('gr_submit')[0].disabled=false; } else { document.getElementsByName('gr_submit')[0].disabled=true; } } </script> """ ) tf = TrivialFormulator( REQUEST.URL0, REQUEST.form, descr, cancelbutton="Annuler", submitbuttonattributes=submitbuttonattributes, submitlabel="OK", formid="gr", ) if tf[0] == 0: # H.append( """<div class="saisienote_etape1"> # <span class="titredivplacementetudiants">Choix du groupe et de la localisation</span> # """) H.append("""<div class="saisienote_etape1">""") return "\n".join(H) + "\n" + tf[1] + "\n</div>" elif tf[0] == -1: return REQUEST.RESPONSE.redirect( "%s/Notes/moduleimpl_status?moduleimpl_id=%s" % (scu.ScoURL(), E["moduleimpl_id"]) ) else: placement_method = tf[2]["placement_method"] teachers = tf[2]["teachers"] building = tf[2]["building"] room = tf[2]["room"] group_ids = tf[2]["group_ids"] columns = tf[2]["columns"] numbering = tf[2]["numbering"] if columns in ("3", "4", "5", "6", "7", "8"): gs = [ ("group_ids%3Alist=" + six.moves.urllib.parse.quote_plus(x)) for x in group_ids ] query = ( "evaluation_id=%s&placement_method=%s&teachers=%s&building=%s&room=%s&columns=%s&numbering=%s&" % ( evaluation_id, placement_method, teachers, building, room, columns, numbering, ) + "&".join(gs) ) return REQUEST.RESPONSE.redirect(scu.NotesURL() + "/do_placement?" + query) else: raise ValueError( "invalid placement_method (%s)" % tf[2]["placement_method"] ) def do_placement(context, REQUEST): """ Choisi le placement """ authuser = REQUEST.AUTHENTICATED_USER authusername = str(authuser) try: evaluation_id = REQUEST.form["evaluation_id"] except: raise ScoValueError( "Formulaire incomplet ! Vous avez sans doute attendu trop longtemps, veuillez vous reconnecter. Si le problème persiste, contacter l'administrateur. Merci." ) E = sco_evaluations.do_evaluation_list(context, {"evaluation_id": evaluation_id})[0] # Check access # (admin, respformation, and responsable_id) if not sco_permissions_check.can_edit_notes(context, authuser, E["moduleimpl_id"]): return ( "<h2>Génération du placement impossible pour %s</h2>" % authusername + """<p>(vérifiez que le semestre n'est pas verrouillé et que vous avez l'autorisation d'effectuer cette opération)</p> <p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p> """ % E["moduleimpl_id"] ) cnx = ndb.GetDBConnexion() # Infos transmises placement_method = REQUEST.form["placement_method"] teachers = REQUEST.form["teachers"] building = REQUEST.form["building"] room = REQUEST.form["room"] columns = REQUEST.form["columns"] numbering = REQUEST.form["numbering"] # Construit liste des etudiants group_ids = REQUEST.form.get("group_ids", []) groups = sco_groups.listgroups(context, group_ids) gr_title_filename = sco_groups.listgroups_filename(groups) # gr_title = sco_groups.listgroups_abbrev(groups) if None in [g["group_name"] for g in groups]: # tous les etudiants getallstudents = True gr_title_filename = "tous" else: getallstudents = False etudids = sco_groups.do_evaluation_listeetuds_groups( context, evaluation_id, groups, getallstudents=getallstudents, include_dems=True ) if not etudids: return "<p>Aucun groupe sélectionné !</p>" M = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=E["moduleimpl_id"])[0] Mod = sco_edit_module.do_module_list(context, args={"module_id": M["module_id"]})[0] sem = sco_formsemestre.get_formsemestre(context, M["formsemestre_id"]) evalname = "%s-%s" % (Mod["code"], ndb.DateDMYtoISO(E["jour"])) if E["description"]: evaltitre = E["description"] else: evaltitre = "évaluation du %s" % E["jour"] desceval = [] # une liste de liste de chaines: description de l'evaluation desceval.append(["%s" % sem["titreannee"]]) desceval.append(["Module : %s - %s" % (Mod["code"], Mod["abbrev"])]) desceval.append(["Surveillants : %s" % teachers]) desceval.append(["Batiment : %s - Salle : %s" % (building, room)]) desceval.append(["Controle : %s (coef. %g)" % (evaltitre, E["coefficient"])]) listetud = [] # liste de couples (nom,prenom) for etudid in etudids: # infos identite etudiant (xxx sous-optimal: 1/select par etudiant) ident = sco_etud.etudident_list(cnx, {"etudid": etudid})[ 0 ] # XXX utiliser ZScolar (parent) # infos inscription inscr = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( context, {"etudid": etudid, "formsemestre_id": M["formsemestre_id"]} )[0] if inscr["etat"] != "D": nom = scu.strupper(ident["nom"]) prenom = scu.strcapitalize(scu.strlower(ident["prenom"])) listetud.append((nom, prenom)) random.shuffle(listetud) sem_preferences = sco_preferences.SemPreferences(context) space = sem_preferences.get("feuille_placement_emargement") maxlines = sem_preferences.get("feuille_placement_positions") if placement_method == "xls": filename = "placement_%s_%s.xls" % (evalname, gr_title_filename) xls = Excel_feuille_placement( E, desceval, listetud, columns, space, maxlines, building, room, numbering ) return sco_excel.sendExcelFile(REQUEST, xls, filename) else: nbcolumns = int(columns) pdf_title = "%s<br/>" % sem["titreannee"] pdf_title += "Module : %s - %s<br/>" % (Mod["code"], Mod["abbrev"]) pdf_title += "Surveillants : %s<br/>" % teachers pdf_title += "Batiment : %s - Salle : %s<br/>" % (building, room) pdf_title += "Controle : %s (coef. %g)<br/>" % (evaltitre, E["coefficient"]) pdf_title += "Date : %s - Horaire : %s à %s" % ( E["jour"], E["heure_debut"], E["heure_fin"], ) filename = "placement_%s_%s.pdf" % (evalname, gr_title_filename) titles = { "nom": "Nom", "prenom": "Prenom", "colonne": "Colonne", "ligne": "Ligne", "place": "Place", } if numbering == "coordinate": columns_ids = ["nom", "prenom", "colonne", "ligne"] else: columns_ids = ["nom", "prenom", "place"] # etudiants line = 1 col = 1 orderetud = [] for etudid in listetud: if numbering == "coordinate": orderetud.append((etudid[0], etudid[1], col, line)) else: orderetud.append((etudid[0], etudid[1], col + (line - 1) * nbcolumns)) if col == nbcolumns: col = 0 line += 1 col += 1 rows = [] orderetud.sort() for etudid in orderetud: if numbering == "coordinate": rows.append( { "nom": etudid[0], "prenom": etudid[1], "colonne": etudid[2], "ligne": etudid[3], } ) else: rows.append({"nom": etudid[0], "prenom": etudid[1], "place": etudid[2]}) tab = GenTable( titles=titles, columns_ids=columns_ids, rows=rows, filename=filename, origin="Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr() + "", pdf_title=pdf_title, # pdf_shorttitle = '', preferences=sco_preferences.SemPreferences(context, M["formsemestre_id"]), # html_generate_cells=False # la derniere ligne (moyennes) est incomplete ) t = tab.make_page( context, format="pdf", with_html_headers=False, REQUEST=REQUEST ) return t def placement_eval_selectetuds(context, evaluation_id, REQUEST=None): """Dialogue placement etudiants: choix methode et localisation""" evals = sco_evaluations.do_evaluation_list( context, {"evaluation_id": evaluation_id} ) if not evals: raise ScoValueError("invalid evaluation_id") theeval = evals[0] if theeval["description"]: page_title = 'Placement "%s"' % theeval["description"] else: page_title = "Placement des étudiants" H = [html_sco_header.sco_header(context, REQUEST, page_title=page_title)] formid = "placementfile" if not REQUEST.form.get("%s-submitted" % formid, False): # not submitted, choix groupe r = do_placement_selectetuds(context, REQUEST) if r: H.append(r) H.append( """<h3>Explications</h3> <ul> <li>Choisir le format du fichier résultat :</li> <ul> <li>le format pdf consiste en un tableau précisant pour chaque étudiant la localisation de sa table;</li> <li>le format xls produit un classeur avec deux onglets</li> <ul> <li>le premier onglet donne une vue de la salle avec la localisation des étudiants et peut servir de feuille d'émargement;</li> <li>le second onglet est un tableau similaire à celui du fichier pdf;</li> </ul> </ul> <li>préciser les surveillants et la localisation (bâtiment et salle) et indiquer le nombre de colonnes;</li> <li>deux types de placements sont possibles :</li> <ul> <li>continue suppose que les tables ont toutes un numéro unique;</li> <li>coordonnées localise chaque table via un numéro de colonne et un numéro de ligne (ou rangée).</li> </ul> </ul> """ ) H.append(html_sco_header.sco_footer(context, REQUEST)) return "\n".join(H) def Excel_feuille_placement( E, description, listetud, columns, space, maxlines, building, room, numbering ): """Genere feuille excel pour placement des etudiants. E: evaluation (dict) lines: liste de tuples (etudid, nom, prenom, etat, groupe, val, explanation) """ nbcolumns = int(columns) wb = Workbook() SheetName0 = "Emargement" ws0 = wb.add_sheet(SheetName0.decode(scu.SCO_ENCODING)) # ajuste largeurs colonnes (unite inconnue, empirique) width = 4500 if nbcolumns > 5: width = 22500 // nbcolumns for col in range(nbcolumns): ws0.col(col + 1).width = width ws0.col(0).width = 750 SheetName1 = "Positions" ws1 = wb.add_sheet(SheetName1.decode(scu.SCO_ENCODING)) if numbering == "coordinate": ws1.col(0).width = 4000 ws1.col(1).width = 4500 ws1.col(2).width = 1500 ws1.col(3).width = 1500 ws1.col(4).width = 500 ws1.col(5).width = 4000 ws1.col(6).width = 4500 ws1.col(7).width = 1500 ws1.col(8).width = 1500 else: ws1.col(0).width = 4000 ws1.col(1).width = 4500 ws1.col(2).width = 3000 ws1.col(3).width = 500 ws1.col(4).width = 4000 ws1.col(5).width = 4500 ws1.col(6).width = 3000 # styles font0 = Font() font0.name = "Arial" font0.bold = True font0.height = 12 * 0x14 font1b = Font() font1b.name = "Arial" font1b.bold = True font1b.height = 9 * 0x14 font1i = Font() font1i.name = "Arial" font1i.height = 10 * 0x14 font1i.italic = True font1o = Font() font1o.name = "Arial" font1o.height = 10 * 0x14 font1o.outline = True font2bi = Font() font2bi.name = "Arial" font2bi.height = 8 * 0x14 font2bi.bold = True font2bi.italic = True font2 = Font() font2.name = "Arial" font2.height = 10 * 0x14 style_titres = XFStyle() style_titres.font = font0 style1t = XFStyle() style1t.font = font1b alignment = Alignment() alignment.horz = Alignment.HORZ_CENTER alignment.vert = Alignment.VERT_CENTER style1t.alignment = alignment borders = Borders() borders.left = Borders.DOUBLE borders.top = Borders.DOUBLE borders.bottom = Borders.NO_LINE borders.right = Borders.DOUBLE style1t.borders = borders style1m = XFStyle() style1m.font = font1b alignment = Alignment() alignment.horz = Alignment.HORZ_CENTER alignment.vert = Alignment.VERT_CENTER style1m.alignment = alignment borders = Borders() borders.left = Borders.DOUBLE borders.top = Borders.NO_LINE borders.bottom = Borders.THIN borders.right = Borders.DOUBLE style1m.borders = borders style1bm = XFStyle() borders = Borders() borders.left = Borders.DOUBLE borders.top = Borders.NO_LINE borders.bottom = Borders.NO_LINE borders.right = Borders.DOUBLE style1bm.borders = borders style1bb = XFStyle() style1bb.font = font1o alignment = Alignment() alignment.horz = Alignment.HORZ_RIGHT alignment.vert = Alignment.VERT_BOTTOM style1bb.alignment = alignment borders = Borders() borders.left = Borders.DOUBLE borders.top = Borders.NO_LINE borders.bottom = Borders.DOUBLE borders.right = Borders.DOUBLE style1bb.borders = borders style2b = XFStyle() style2b.font = font1i alignment = Alignment() alignment.horz = Alignment.HORZ_CENTER alignment.vert = Alignment.VERT_CENTER style2b.alignment = alignment borders = Borders() borders.left = Borders.THIN borders.top = Borders.THIN borders.bottom = Borders.THIN borders.right = Borders.THIN style2b.borders = borders style2bi = XFStyle() style2bi.font = font2bi alignment = Alignment() alignment.horz = Alignment.HORZ_CENTER alignment.vert = Alignment.VERT_CENTER style2bi.alignment = alignment borders = Borders() borders.left = Borders.THIN borders.top = Borders.THIN borders.bottom = Borders.THIN borders.right = Borders.THIN style2bi.borders = borders pattern = Pattern() pattern.pattern = Pattern.SOLID_PATTERN pattern._pattern_back_colour = "gray" style2bi.pattern = pattern style2l = XFStyle() style2l.font = font2 alignment = Alignment() alignment.horz = Alignment.HORZ_LEFT alignment.vert = Alignment.VERT_CENTER style2l.alignment = alignment borders = Borders() borders.left = Borders.THIN borders.top = Borders.THIN borders.bottom = Borders.THIN borders.right = Borders.NO_LINE style2l.borders = borders style2m1 = XFStyle() style2m1.font = font2 alignment = Alignment() alignment.horz = Alignment.HORZ_LEFT alignment.vert = Alignment.VERT_CENTER style2m1.alignment = alignment borders = Borders() borders.left = Borders.NO_LINE borders.top = Borders.THIN borders.bottom = Borders.THIN borders.right = Borders.NO_LINE style2m1.borders = borders style2m2 = XFStyle() style2l.font = font2 alignment = Alignment() alignment.horz = Alignment.HORZ_RIGHT alignment.vert = Alignment.VERT_CENTER style2m2.alignment = alignment borders = Borders() borders.left = Borders.NO_LINE borders.top = Borders.THIN borders.bottom = Borders.THIN borders.right = Borders.NO_LINE style2m2.borders = borders style2r = XFStyle() style2l.font = font2 alignment = Alignment() alignment.horz = Alignment.HORZ_RIGHT alignment.vert = Alignment.VERT_CENTER style2r.alignment = alignment borders = Borders() borders.left = Borders.NO_LINE borders.top = Borders.THIN borders.bottom = Borders.THIN borders.right = Borders.THIN style2r.borders = borders # ligne de titres li = 0 line = 0 dt = time.strftime("%d/%m/%Y a %Hh%M") ws0.write(li, 0, u"Feuille placement etudiants éditée le %s" % dt, style_titres) ws1.write(li, 0, u"Feuille placement etudiants éditée le %s" % dt, style_titres) for desceval in description: if line % 2 == 0: li += 2 else: li += 1 line += 1 ws0.write(li, 0, desceval[0].decode(scu.SCO_ENCODING), style_titres) ws1.write(li, 0, desceval[0].decode(scu.SCO_ENCODING), style_titres) li += 1 ws0.write( li, 0, u"Date : %s - Horaire : %s à %s" % (E["jour"], E["heure_debut"], E["heure_fin"]), style_titres, ) ws1.write( li, 0, u"Date : %s - Horaire : %s à %s" % (E["jour"], E["heure_debut"], E["heure_fin"]), style_titres, ) li += 1 # entetes colonnes - feuille0 for col in range(nbcolumns): ws0.write(li, col + 1, u"colonne %s" % (col + 1), style2b) # entetes colonnes - feuille1 if numbering == "coordinate": ws1.write(li, 0, u"Nom", style2bi) ws1.write(li, 1, u"Prénom", style2bi) ws1.write(li, 2, u"Colonne", style2bi) ws1.write(li, 3, u"Ligne", style2bi) ws1.write(li, 5, u"Nom", style2bi) ws1.write(li, 6, u"Prénom", style2bi) ws1.write(li, 7, u"Colonne", style2bi) ws1.write(li, 8, u"Ligne", style2bi) else: ws1.write(li, 0, u"Nom", style2bi) ws1.write(li, 1, u"Prénom", style2bi) ws1.write(li, 2, u"Place", style2bi) ws1.write(li, 4, u"Nom", style2bi) ws1.write(li, 5, u"Prénom", style2bi) ws1.write(li, 6, u"Place", style2bi) # etudiants line = 1 col = 1 linetud = [] orderetud = [] placementetud = [] for etudid in listetud: linetud.append(etudid) if numbering == "coordinate": orderetud.append((etudid[0], etudid[1], col, line)) else: orderetud.append((etudid[0], etudid[1], col + (line - 1) * nbcolumns)) if col == nbcolumns: placementetud.append(linetud) linetud = [] col = 0 line += 1 col += 1 if len(linetud) > 0: placementetud.append(linetud) # etudiants - feuille0 line = 0 li0 = li for linetud in placementetud: li0 += 1 line += 1 ws0.write(li0, 0, line, style2b) col = 1 for etudid in linetud: ws0.write(li0, col, (etudid[0]).decode(scu.SCO_ENCODING), style1t) ws0.write(li0 + 1, col, (etudid[1]).decode(scu.SCO_ENCODING), style1m) ws0.row(li0 + 2).height = space if numbering == "coordinate": ws0.write(li0 + 2, col, " ", style1bb) else: ws0.write( li0 + 2, col, u"place %s" % (col + (line - 1) * nbcolumns), style1bb ) # ws0.write(li+3,col, ' ', style1bm ) # ws0.write(li+4,col, ' ', style1bb ) if col == nbcolumns: col = 0 li0 += 2 col += 1 # etudiants - feuille1 if numbering == "coordinate": coloffset = 5 else: coloffset = 4 line = 0 li1 = li nbcol = 0 col = 0 orderetud.sort() for etudid in orderetud: li1 += 1 line += 1 ws1.write(li1, col, (etudid[0]).decode(scu.SCO_ENCODING), style2l) ws1.write(li1, col + 1, (etudid[1]).decode(scu.SCO_ENCODING), style2m1) if numbering == "coordinate": ws1.write(li1, col + 2, etudid[2], style2m2) ws1.write(li1, col + 3, etudid[3], style2r) else: ws1.write(li1, col + 2, etudid[2], style2r) if line == maxlines: line = 0 li1 = li nbcol = nbcol + 1 col = col + coloffset if nbcol == 2: li = li + maxlines + 2 li1 = li nbcol = 0 col = 0 if numbering == "coordinate": ws1.write(li, 0, u"Nom", style2bi) ws1.write(li, 1, u"Prénom", style2bi) ws1.write(li, 2, u"Colonne", style2bi) ws1.write(li, 3, u"Ligne", style2bi) ws1.write(li, 5, u"Nom", style2bi) ws1.write(li, 6, u"Prénom", style2bi) ws1.write(li, 7, u"Colonne", style2bi) ws1.write(li, 8, u"Ligne", style2bi) else: ws1.write(li, 0, u"Nom", style2bi) ws1.write(li, 1, u"Prénom", style2bi) ws1.write(li, 2, u"Place", style2bi) ws1.write(li, 4, u"Nom", style2bi) ws1.write(li, 5, u"Prénom", style2bi) ws1.write(li, 6, u"Place", style2bi) return wb.savetostr()