diff --git a/.gitignore b/.gitignore
index 6d49cf2c81..9a2e411be1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -170,3 +170,4 @@ Thumbs.db
*.code-workspace
+copy
diff --git a/app/scodoc/gen_tables.py b/app/scodoc/gen_tables.py
index 67a52261c8..c199d0b3ef 100644
--- a/app/scodoc/gen_tables.py
+++ b/app/scodoc/gen_tables.py
@@ -482,9 +482,9 @@ class GenTable(object):
ses.append_blank_row() # empty line
ses.append_single_cell_row(self.origin, style_base)
if wb is None:
- return ses.generate_standalone()
+ return ses.generate()
else:
- ses.generate_embeded()
+ ses.generate()
def text(self):
"raw text representation of the table"
diff --git a/app/scodoc/sco_archives_etud.py b/app/scodoc/sco_archives_etud.py
index fa41bb656e..0d871cda07 100644
--- a/app/scodoc/sco_archives_etud.py
+++ b/app/scodoc/sco_archives_etud.py
@@ -269,9 +269,14 @@ def etudarchive_generate_excel_sample(group_id=None, REQUEST=None):
],
extra_cols=["fichier_a_charger"],
)
- return sco_excel.send_excel_file(
- REQUEST, data, "ImportFichiersEtudiants" + scu.XLSX_SUFFIX
+ return scu.send_file(
+ data,
+ "ImportFichiersEtudiants",
+ scu.XLSX_SUFFIX,
+ scu.XLSX_MIMETYPE,
+ attached=True,
)
+ # return sco_excel.send_excel_file(REQUEST, data, "ImportFichiersEtudiants" + scu.XLSX_SUFFIX)
def etudarchive_import_files_form(group_id, REQUEST=None):
diff --git a/app/scodoc/sco_excel.py b/app/scodoc/sco_excel.py
index b5f9879ed7..1e70858aab 100644
--- a/app/scodoc/sco_excel.py
+++ b/app/scodoc/sco_excel.py
@@ -35,11 +35,11 @@ from enum import Enum
from tempfile import NamedTemporaryFile
import openpyxl.utils.datetime
+from openpyxl.styles.numbers import FORMAT_NUMBER_00, FORMAT_GENERAL
+from openpyxl.comments import Comment
from openpyxl import Workbook, load_workbook
from openpyxl.cell import WriteOnlyCell
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
-from openpyxl.styles.numbers import FORMAT_NUMBER_00, FORMAT_GENERAL
-from openpyxl.comments import Comment
import app.scodoc.sco_utils as scu
from app.scodoc import notesdb
@@ -59,24 +59,9 @@ class COLORS(Enum):
LIGHT_YELLOW = "FFFFFF99"
-def send_excel_file(request, data, filename, mime=scu.XLSX_MIMETYPE):
- """publication fichier.
- (on ne doit rien avoir émis avant, car ici sont générés les entetes)
- """
- filename = (
- scu.unescape_html(scu.suppress_accents(filename))
- .replace("&", "")
- .replace(" ", "_")
- )
- request.RESPONSE.setHeader("content-type", mime)
- request.RESPONSE.setHeader(
- "content-disposition", 'attachment; filename="%s"' % filename
- )
- return data
-
-
# Un style est enregistré comme un dictionnaire qui précise la valeur d'un attributdans la liste suivante:
-# font, border, number_format, fill, .. (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles)
+# font, border, number_format, fill,...
+# (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles)
def xldate_as_datetime(xldate, datemode=0):
@@ -86,6 +71,12 @@ def xldate_as_datetime(xldate, datemode=0):
return openpyxl.utils.datetime.from_ISO8601(xldate)
+def adjust_sheetname(sheet_name):
+ # Le nom de la feuille ne peut faire plus de 31 caractères.
+ # si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)
+ return sheet_name[:31]
+
+
class ScoExcelBook:
"""Permet la génération d'un classeur xlsx composé de plusieurs feuilles.
usage:
@@ -98,13 +89,16 @@ class ScoExcelBook:
def __init__(self):
self.sheets = [] # list of sheets
+ self.wb = Workbook(write_only=True)
def create_sheet(self, sheet_name="feuille", default_style=None):
"""Crée une nouvelle feuille dans ce classeur
sheet_name -- le nom de la feuille
default_style -- le style par défaut
"""
- sheet = ScoExcelSheet(sheet_name, default_style)
+ sheet_name = adjust_sheetname(sheet_name)
+ ws = self.wb.create_sheet(sheet_name)
+ sheet = ScoExcelSheet(sheet_name, default_style, ws)
self.sheets.append(sheet)
return sheet
@@ -112,12 +106,12 @@ class ScoExcelBook:
"""génération d'un stream binaire représentant la totalité du classeur.
retourne le flux
"""
- wb = Workbook(write_only=True)
for sheet in self.sheets:
- sheet.generate(self)
- # construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
+ sheet.prepare()
+ # construction d'un flux
+ # (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
with NamedTemporaryFile() as tmp:
- wb.save(tmp.name)
+ self.wb.save(tmp.name)
tmp.seek(0)
return tmp.read()
@@ -125,6 +119,7 @@ class ScoExcelBook:
def excel_make_style(
bold=False,
italic=False,
+ outline=False,
color: COLORS = COLORS.BLACK,
bgcolor: COLORS = None,
halign=None,
@@ -145,7 +140,14 @@ def excel_make_style(
size -- taille de police
"""
style = {}
- font = Font(name=font_name, bold=bold, italic=italic, color=color.value, size=size)
+ font = Font(
+ name=font_name,
+ bold=bold,
+ italic=italic,
+ outline=outline,
+ color=color.value,
+ size=size,
+ )
style["font"] = font
if bgcolor:
style["fill"] = PatternFill(fill_type="solid", fgColor=bgcolor.value)
@@ -182,41 +184,93 @@ class ScoExcelSheet:
"""
def __init__(self, sheet_name="feuille", default_style=None, wb=None):
- """Création de la feuille.
- sheet_name -- le nom de la feuille
- default_style -- le style par défaut des cellules
- wb -- le WorkBook dans laquelle se trouve la feuille. Si wb est None (cas d'un classeur mono-feuille),
- un workbook est crée et associé à cette feuille.
+ """Création de la feuille. sheet_name
+ -- le nom de la feuille default_style
+ -- le style par défaut des cellules ws
+ -- None si la feuille est autonome (dans ce cas ell crée son propre wb), sinon c'est la worksheet
+ créée par le workbook propriétaire un workbook est crée et associé à cette feuille.
"""
# Le nom de la feuille ne peut faire plus de 31 caractères.
# si la taille du nom de feuille est > 31 on tronque (on pourrait remplacer par 'feuille' ?)
- self.sheet_name = sheet_name[
- :31
- ] # if len(sheet_name) > 31: sheet_name = 'Feuille' ?
- self.rows = [] # list of list of cells
- # self.cells_styles_lico = {} # { (li,co) : style }
- # self.cells_styles_li = {} # { li : style }
- # self.cells_styles_co = {} # { co : style }
+ self.sheet_name = adjust_sheetname(sheet_name)
if default_style is None:
default_style = excel_make_style()
self.default_style = default_style
- self.wb = wb or Workbook(write_only=True) # Création de workbook si nécessaire
- self.ws = self.wb.create_sheet(title=self.sheet_name)
+ if wb is None:
+ self.wb = Workbook()
+ self.ws = self.wb.active
+ self.ws.title = self.sheet_name
+ else:
+ self.wb = None
+ self.ws = wb
+ # internal data
+ self.rows = [] # list of list of cells
self.column_dimensions = {}
+ self.row_dimensions = {}
- def set_column_dimension_width(self, cle, value):
- """Détermine la largeur d'une colonne.
- cle -- identifie la colonne ("A"n "B", ...)
- value -- la dimension (unité : 7 pixels comme affiché dans Excel)
+ def excel_make_composite_style(
+ self,
+ alignment=None,
+ border=None,
+ fill=None,
+ number_format=None,
+ font=None,
+ ):
+ style = {}
+ if font is not None:
+ style["font"] = font
+ if alignment is not None:
+ style["alignment"] = alignment
+ if border is not None:
+ style["border"] = border
+ if fill is not None:
+ style["fill"] = fill
+ if number_format is None:
+ style["number_format"] = FORMAT_GENERAL
+ else:
+ style["number_format"] = number_format
+ return style
+
+ @staticmethod
+ def i2col(idx):
+ if idx < 26: # one letter key
+ return chr(idx + 65)
+ else: # two letters AA..ZZ
+ first = (idx // 26) + 66
+ second = (idx % 26) + 65
+ return "" + chr(first) + chr(second)
+
+ def set_column_dimension_width(self, cle=None, value=21):
+ """Détermine la largeur d'une colonne. cle -- identifie la colonne ("A" "B", ... ou 0, 1, 2, ...) si None,
+ value donne la liste des largeurs de colonnes depuis A, B, C, ... value -- la dimension (unité : 7 pixels
+ comme affiché dans Excel)
"""
- self.ws.column_dimensions[cle].width = value
+ if cle is None:
+ for i, val in enumerate(value):
+ self.ws.column_dimensions[self.i2col(i)].width = val
+ # No keys: value is a list of widths
+ elif type(cle) == str: # accepts set_column_with("D", ...)
+ self.ws.column_dimensions[cle].width = value
+ else:
+ self.ws.column_dimensions[self.i2col(cle)].width = value
- def set_column_dimension_hidden(self, cle, value):
- """Masque ou affiche une colonne.
- cle -- identifie la colonne ("A"n "B", ...)
+ def set_row_dimension_height(self, cle=None, value=21):
+ """Détermine la hauteur d'une ligne. cle -- identifie la ligne (1, 2, ...) si None,
+ value donne la liste des hauteurs de colonnes depuis 1, 2, 3, ... value -- la dimension
+ """
+ if cle is None:
+ for i, val in enumerate(value, start=1):
+ self.ws.row_dimensions[i].height = val
+ # No keys: value is a list of widths
+ else:
+ self.ws.row_dimensions[cle].height = value
+
+ def set_row_dimension_hidden(self, cle, value):
+ """Masque ou affiche une ligne.
+ cle -- identifie la colonne (1...)
value -- boolean (vrai = colonne cachée)
"""
- self.ws.column_dimensions[cle].hidden = value
+ self.ws.row_dimensions[cle].hidden = value
def make_cell(self, value: any = None, style=None, comment=None):
"""Construit une cellule.
@@ -232,8 +286,12 @@ class ScoExcelSheet:
style = self.default_style
if "font" in style:
cell.font = style["font"]
+ if "alignment" in style:
+ cell.alignment = style["alignment"]
if "border" in style:
cell.border = style["border"]
+ if "fill" in style:
+ cell.fill = style["fill"]
if "number_format" in style:
cell.number_format = style["number_format"]
if "fill" in style:
@@ -272,73 +330,31 @@ class ScoExcelSheet:
"""ajoute une ligne déjà construite à la feuille."""
self.rows.append(row)
- # def set_style(self, style=None, li=None, co=None):
- # if li is not None and co is not None:
- # self.cells_styles_lico[(li, co)] = style
- # elif li is None:
- # self.cells_styles_li[li] = style
- # elif co is None:
- # self.cells_styles_co[co] = style
- #
- # def get_cell_style(self, li, co):
- # """Get style for specified cell"""
- # return (
- # self.cells_styles_lico.get((li, co), None)
- # or self.cells_styles_li.get(li, None)
- # or self.cells_styles_co.get(co, None)
- # or self.default_style
- # )
-
- def _generate_ws(self):
+ def prepare(self):
"""génére un flux décrivant la feuille.
Ce flux pourra ensuite être repris dans send_excel_file (classeur mono feille)
ou pour la génération d'un classeur multi-feuilles
"""
- for col in self.column_dimensions.keys():
- self.ws.column_dimensions[col] = self.column_dimensions[col]
+ for row in self.column_dimensions.keys():
+ self.ws.column_dimensions[row] = self.column_dimensions[row]
+ for row in self.row_dimensions.keys():
+ self.ws.row_dimensions[row] = self.row_dimensions[row]
for row in self.rows:
self.ws.append(row)
- def generate_standalone(self):
+ def generate(self):
"""génération d'un classeur mono-feuille"""
- self._generate_ws()
+ # this method makes sense only if it is a standalone worksheet (else call workbook.generate()
+ if self.wb is None: # embeded sheet
+ raise ScoValueError("can't generate a single sheet from a ScoWorkbook")
+
# construction d'un flux (https://openpyxl.readthedocs.io/en/stable/tutorial.html#saving-as-a-stream)
+ self.prepare()
with NamedTemporaryFile() as tmp:
self.wb.save(tmp.name)
tmp.seek(0)
return tmp.read()
- def generate_embeded(self):
- """generation d'une feuille include dans un classeur multi-feuilles"""
- self._generate_ws()
-
- def gen_workbook(self, wb=None):
- """TODO: à remplacer"""
- """Generates and returns a workbook from stored data.
- If wb, add a sheet (tab) to the existing workbook (in this case, returns None).
- """
- if wb is None:
- wb = Workbook() # Création du fichier
- sauvegarde = True
- else:
- sauvegarde = False
- ws0 = wb.add_sheet(self.sheet_name)
- li = 0
- for row in self.rows:
- co = 0
- for c in row:
- # safety net: allow only str, int and float
- # #py3 #sco8 A revoir lors de la ré-écriture de ce module
- # XXX if type(c) not in (IntType, FloatType):
- # c = str(c).decode(scu.SCO_ENCODING)
- ws0.write(li, co, c, self.get_cell_style(li, co))
- co += 1
- li += 1
- if sauvegarde:
- return wb.savetostr()
- else:
- return None
-
def excel_simple_table(
titles=None, lines=None, sheet_name=b"feuille", titles_styles=None, comments=None
@@ -377,7 +393,7 @@ def excel_simple_table(
cell_style = text_style
cells.append(ws.make_cell(it, cell_style))
ws.append_row(cells)
- return ws.generate_standalone()
+ return ws.generate()
def excel_feuille_saisie(e, titreannee, description, lines):
@@ -538,7 +554,7 @@ def excel_feuille_saisie(e, titreannee, description, lines):
ws.make_cell("cellule vide -> note non modifiée", style_expl),
]
)
- return ws.generate_standalone()
+ return ws.generate()
def excel_bytes_to_list(bytes_content):
@@ -758,4 +774,4 @@ def excel_feuille_listeappel(
cell_2 = ws.make_cell(("Liste éditée le " + dt), style1i)
ws.append_row([None, cell_2])
- return ws.generate_standalone()
+ return ws.generate()
diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py
index b98def5179..82c137e2de 100644
--- a/app/scodoc/sco_groups_view.py
+++ b/app/scodoc/sco_groups_view.py
@@ -1,983 +1,989 @@
-# -*- 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
-#
-##############################################################################
-
-"""Affichage étudiants d'un ou plusieurs groupes
- sous forme: de liste html (table exportable), de trombinoscope (exportable en pdf)
-"""
-
-# Re-ecriture en 2014 (re-organisation de l'interface, modernisation du code)
-
-import collections
-import datetime
-import operator
-import urllib
-from urllib.parse import parse_qs
-import time
-
-
-from flask import url_for, g, request
-
-import app.scodoc.sco_utils as scu
-from app.scodoc import html_sco_header
-from app.scodoc import sco_abs
-from app.scodoc import sco_excel
-from app.scodoc import sco_formsemestre
-from app.scodoc import sco_groups
-from app.scodoc import sco_moduleimpl
-from app.scodoc import sco_parcours_dut
-from app.scodoc import sco_portal_apogee
-from app.scodoc import sco_preferences
-from app.scodoc import sco_etud
-from app.scodoc.gen_tables import GenTable
-from app.scodoc.sco_exceptions import ScoValueError
-from app.scodoc.sco_permissions import Permission
-from six.moves import range
-
-JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + [
- "js/etud_info.js",
- "js/groups_view.js",
-]
-
-CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
-
-
-def groups_view(
- group_ids=[],
- format="html",
- REQUEST=None,
- # Options pour listes:
- with_codes=0,
- etat=None,
- with_paiement=0, # si vrai, ajoute colonnes infos paiement droits et finalisation inscription (lent car interrogation portail)
- with_archives=0, # ajoute colonne avec noms fichiers archivés
- with_annotations=0,
- formsemestre_id=None, # utilise si aucun groupe selectionné
-):
- """Affichage des étudiants des groupes indiqués
- group_ids: liste de group_id
- format: csv, json, xml, xls, allxls, xlsappel, moodlecsv, pdf
- """
- # Informations sur les groupes à afficher:
- groups_infos = DisplayedGroupsInfos(
- group_ids,
- formsemestre_id=formsemestre_id,
- etat=etat,
- select_all_when_unspecified=True,
- )
- # Formats spéciaux: download direct
- if format != "html":
- return groups_table(
- groups_infos=groups_infos,
- format=format,
- REQUEST=REQUEST,
- with_codes=with_codes,
- etat=etat,
- with_paiement=with_paiement,
- with_archives=with_archives,
- with_annotations=with_annotations,
- )
-
- H = [
- html_sco_header.sco_header(
- javascripts=JAVASCRIPTS,
- cssstyles=CSSSTYLES,
- init_qtip=True,
- )
- ]
- # Menu choix groupe
- H.append("""
""")
- H.append(form_groups_choice(groups_infos, submit_on_change=True))
- # Note: le formulaire est soumis a chaque modif des groupes
- # on pourrait faire comme pour le form de saisie des notes. Il faudrait pour cela:
- # - charger tous les etudiants au debut, quels que soient les groupes selectionnés
- # - ajouter du JS pour modifier les liens (arguments group_ids) quand le menu change
-
- # Tabs
- # H.extend( ("""
toto """,) )
- H.extend(
- (
- """
-
-
-
-
- """,
- groups_table(
- groups_infos=groups_infos,
- format=format,
- REQUEST=REQUEST,
- with_codes=with_codes,
- etat=etat,
- with_paiement=with_paiement,
- with_archives=with_archives,
- with_annotations=with_annotations,
- ),
- "
",
- """
""",
- tab_photos_html(groups_infos, etat=etat, REQUEST=REQUEST),
- #'
hello
',
- "
",
- '
',
- tab_absences_html(groups_infos, etat=etat, REQUEST=REQUEST),
- "
",
- )
- )
-
- H.append(html_sco_header.sco_footer())
- return "\n".join(H)
-
-
-def form_groups_choice(groups_infos, with_selectall_butt=False, submit_on_change=False):
- """form pour selection groupes
- group_ids est la liste des groupes actuellement sélectionnés
- et doit comporter au moins un élément, sauf si formsemestre_id est spécifié.
- (utilisé pour retrouver le semestre et proposer la liste des autres groupes)
-
- Si submit_on_change, ajoute une classe "submit_on_change" qui est utilisee en JS
- """
- default_group_id = sco_groups.get_default_group(groups_infos.formsemestre_id)
-
- H = [
- """
")
-
- return "\n".join(H)
-
-
-def menu_groups_choice(groups_infos, submit_on_change=False):
- """menu pour selection groupes
- group_ids est la liste des groupes actuellement sélectionnés
- et doit comporter au moins un élément, sauf si formsemestre_id est spécifié.
- (utilisé pour retrouver le semestre et proposer la liste des autres groupes)
- """
- default_group_id = sco_groups.get_default_group(groups_infos.formsemestre_id)
-
- if submit_on_change:
- klass = "submit_on_change"
- else:
- klass = ""
- H = [
- """
- """
- % (klass,)
- ]
-
- n_members = len(sco_groups.get_group_members(default_group_id))
- if default_group_id in groups_infos.group_ids:
- selected = "selected"
- else:
- selected = ""
- H.append(
- '%s (%s) '
- % (default_group_id, selected, "Tous", n_members)
- )
-
- for partition in groups_infos.partitions:
- H.append('' % partition["partition_name"])
- # Les groupes dans cette partition:
- for g in sco_groups.get_partition_groups(partition):
- if g["group_id"] in groups_infos.group_ids:
- selected = "selected"
- else:
- selected = ""
- if g["group_name"]:
- n_members = len(sco_groups.get_group_members(g["group_id"]))
- H.append(
- '%s (%s) '
- % (g["group_id"], selected, g["group_name"], n_members)
- )
- H.append(" ")
- H.append(" ")
- return "\n".join(H)
-
-
-def menu_group_choice(group_id=None, formsemestre_id=None):
- """Un bête menu pour choisir un seul groupe
- group_id est le groupe actuellement sélectionné.
- Si aucun groupe selectionné, utilise formsemestre_id pour lister les groupes.
- """
- if group_id:
- group = sco_groups.get_group(group_id)
- formsemestre_id = group["formsemestre_id"]
- elif not formsemestre_id:
- raise ValueError("missing formsemestre_id")
- H = [
- """
-
- """
- ]
- if not group_id:
- H.append('choisir... ')
- for partition in sco_groups.get_partitions_list(formsemestre_id):
- if partition["partition_name"]:
- H.append('' % partition["partition_name"])
- groups = sco_groups.get_partition_groups(partition)
- for group in groups:
- if group["group_id"] == group_id:
- selected = "selected"
- else:
- selected = ""
- name = group["group_name"] or "Tous"
- n_members = len(sco_groups.get_group_members(group["group_id"]))
- H.append(
- '%s (%s) '
- % (group["group_id"], selected, name, n_members)
- )
- if partition["partition_name"]:
- H.append(" ")
- H.append(
- """
-
- """
- )
- return "\n".join(H)
-
-
-class DisplayedGroupsInfos(object):
- """Container with attributes describing groups to display in the page
- .groups_query_args : 'group_ids=xxx&group_ids=yyy'
- .base_url : url de la requete, avec les groupes, sans les autres paramètres
- .formsemestre_id : semestre "principal" (en fait celui du 1er groupe de la liste)
- .members
- .groups_titles
- """
-
- def __init__(
- self,
- group_ids=[], # groupes specifies dans l'URL, ou un seul int
- formsemestre_id=None,
- etat=None,
- select_all_when_unspecified=False,
- moduleimpl_id=None, # used to find formsemestre when unspecified
- ):
- if isinstance(group_ids, int):
- if group_ids:
- group_ids = [group_ids] # cas ou un seul parametre, pas de liste
- else:
- group_ids = []
- if not formsemestre_id and moduleimpl_id:
- mods = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)
- if len(mods) != 1:
- raise ValueError("invalid moduleimpl_id")
- formsemestre_id = mods[0]["formsemestre_id"]
-
- if not group_ids: # appel sans groupe (eg page accueil)
- if not formsemestre_id:
- raise Exception("missing parameter formsemestre_id or group_ids")
- if select_all_when_unspecified:
- group_ids = [sco_groups.get_default_group(formsemestre_id)]
- else:
- # selectionne le premier groupe trouvé, s'il y en a un
- partition = sco_groups.get_partitions_list(
- formsemestre_id, with_default=True
- )[0]
- groups = sco_groups.get_partition_groups(partition)
- if groups:
- group_ids = [groups[0]["group_id"]]
- else:
- group_ids = [sco_groups.get_default_group(formsemestre_id)]
-
- gq = []
- for group_id in group_ids:
- gq.append("group_ids=" + str(group_id))
- self.groups_query_args = "&".join(gq)
- self.base_url = request.base_url + "?" + self.groups_query_args
- self.group_ids = group_ids
- self.groups = []
- groups_titles = []
- self.members = []
- self.tous_les_etuds_du_sem = (
- False # affiche tous les etuds du semestre ? (si un seul semestre)
- )
- self.sems = collections.OrderedDict() # formsemestre_id : sem
- self.formsemestre = None
- self.formsemestre_id = formsemestre_id
- self.nbdem = 0 # nombre d'étudiants démissionnaires en tout
- sem = None
- selected_partitions = set()
- for group_id in group_ids:
- group_members, group, group_tit, sem, nbdem = sco_groups.get_group_infos(
- group_id, etat=etat
- )
- self.groups.append(group)
- self.nbdem += nbdem
- self.sems[sem["formsemestre_id"]] = sem
- if not self.formsemestre_id:
- self.formsemestre_id = sem["formsemestre_id"]
- self.formsemestre = sem
- self.members.extend(group_members)
- groups_titles.append(group_tit)
- if group["group_name"] == None:
- self.tous_les_etuds_du_sem = True
- else:
- # liste les partitions explicitement sélectionnés (= des groupes de group_ids)
- selected_partitions.add((group["numero"], group["partition_id"]))
-
- self.selected_partitions = [
- x[1] for x in sorted(list(selected_partitions))
- ] # -> [ partition_id ]
-
- if not self.formsemestre: # aucun groupe selectionne
- self.formsemestre = sco_formsemestre.get_formsemestre(formsemestre_id)
-
- self.sortuniq()
-
- if len(self.sems) > 1:
- self.tous_les_etuds_du_sem = False # plusieurs semestres
- if self.tous_les_etuds_du_sem:
- if sem and sem["semestre_id"] >= 0:
- self.groups_titles = "S%d" % sem["semestre_id"]
- else:
- self.groups_titles = "tous"
- self.groups_filename = self.groups_titles
- else:
- self.groups_titles = ", ".join(groups_titles)
- self.groups_filename = "_".join(groups_titles).replace(" ", "_")
- # Sanitize filename:
- self.groups_filename = scu.make_filename(self.groups_filename)
-
- # colonnes pour affichages nom des groupes:
- # gère le cas où les étudiants appartiennent à des semestres différents
- self.partitions = [] # les partitions, sans celle par defaut
- for formsemestre_id in self.sems:
- for partition in sco_groups.get_partitions_list(formsemestre_id):
- if partition["partition_name"]:
- self.partitions.append(partition)
-
- def sortuniq(self):
- "Trie les étudiants (de plusieurs groupes) et elimine les doublons"
- if (len(self.group_ids) <= 1) or len(self.members) <= 1:
- return # on suppose que les etudiants d'un groupe sont deja triés
- self.members.sort(
- key=operator.itemgetter("nom_disp", "prenom")
- ) # tri selon nom_usuel ou nom
- to_remove = []
- T = self.members
- for i in range(len(T) - 1, 0, -1):
- if T[i - 1]["etudid"] == T[i]["etudid"]:
- to_remove.append(i)
- for i in to_remove:
- del T[i]
-
- def get_form_elem(self):
- """html hidden input with groups"""
- H = []
- for group_id in self.group_ids:
- H.append('
' % group_id)
- return "\n".join(H)
-
-
-# Ancien ZScolar.group_list renommé ici en group_table
-def groups_table(
- REQUEST=None,
- groups_infos=None, # instance of DisplayedGroupsInfos
- with_codes=0,
- etat=None,
- format="html",
- with_paiement=0, # si vrai, ajoute colonnes infos paiement droits et finalisation inscription (lent car interrogation portail)
- with_archives=0, # ajoute colonne avec noms fichiers archivés
- with_annotations=0,
-):
- """liste etudiants inscrits dans ce semestre
- format: csv, json, xml, xls, allxls, xlsappel, moodlecsv, pdf
- Si with_codes, ajoute 4 colonnes avec les codes etudid, NIP, INE et etape
- """
- from app.scodoc import sco_report
-
- # log(
- # "enter groups_table %s: %s"
- # % (groups_infos.members[0]["nom"], groups_infos.members[0].get("etape", "-"))
- # )
- with_codes = int(with_codes)
- with_paiement = int(with_paiement)
- with_archives = int(with_archives)
- with_annotations = int(with_annotations)
-
- base_url_np = groups_infos.base_url + "&with_codes=%s" % with_codes
- base_url = (
- base_url_np
- + "&with_paiement=%s&with_archives=%s&with_annotations=%s"
- % (with_paiement, with_archives, with_annotations)
- )
- #
- columns_ids = ["civilite_str", "nom_disp", "prenom"] # colonnes a inclure
- titles = {
- "civilite_str": "Civ.",
- "nom_disp": "Nom",
- "prenom": "Prénom",
- "email": "Mail",
- "emailperso": "Personnel",
- "etat": "Etat",
- "etudid": "etudid",
- "code_nip": "code_nip",
- "code_ine": "code_ine",
- "datefinalisationinscription_str": "Finalisation inscr.",
- "paiementinscription_str": "Paiement",
- "etudarchive": "Fichiers",
- "annotations_str": "Annotations",
- "etape": "Etape",
- "semestre_groupe": "Semestre-Groupe", # pour Moodle
- }
-
- # ajoute colonnes pour groupes
- columns_ids.extend([p["partition_id"] for p in groups_infos.partitions])
- titles.update(
- dict(
- [(p["partition_id"], p["partition_name"]) for p in groups_infos.partitions]
- )
- )
- partitions_name = {
- p["partition_id"]: p["partition_name"] for p in groups_infos.partitions
- }
-
- if format != "html": # ne mentionne l'état que en Excel (style en html)
- columns_ids.append("etat")
- columns_ids.append("email")
- columns_ids.append("emailperso")
-
- if format == "moodlecsv":
- columns_ids = ["email", "semestre_groupe"]
-
- if with_codes:
- columns_ids += ["etape", "etudid", "code_nip", "code_ine"]
- if with_paiement:
- columns_ids += ["datefinalisationinscription_str", "paiementinscription_str"]
- if with_paiement or with_codes:
- sco_portal_apogee.check_paiement_etuds(groups_infos.members)
- if with_archives:
- from app.scodoc import sco_archives_etud
-
- sco_archives_etud.add_archives_info_to_etud_list(groups_infos.members)
- columns_ids += ["etudarchive"]
- if with_annotations:
- sco_etud.add_annotations_to_etud_list(groups_infos.members)
- columns_ids += ["annotations_str"]
- moodle_sem_name = groups_infos.formsemestre["session_id"]
- moodle_groupenames = set()
- # ajoute liens
- for etud in groups_infos.members:
- if etud["email"]:
- etud["_email_target"] = "mailto:" + etud["email"]
- else:
- etud["_email_target"] = ""
- if etud["emailperso"]:
- etud["_emailperso_target"] = "mailto:" + etud["emailperso"]
- else:
- etud["_emailperso_target"] = ""
- fiche_url = url_for(
- "scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
- )
- etud["_nom_disp_target"] = fiche_url
- etud["_prenom_target"] = fiche_url
-
- etud["_nom_disp_td_attrs"] = 'id="%s" class="etudinfo"' % (etud["etudid"])
-
- if etud["etat"] == "D":
- etud["_css_row_class"] = "etuddem"
- # et groupes:
- for partition_id in etud["partitions"]:
- etud[partition_id] = etud["partitions"][partition_id]["group_name"]
- # Ajoute colonne pour moodle: semestre_groupe, de la forme RT-DUT-FI-S3-2021-PARTITION-GROUPE
- moodle_groupename = []
- if groups_infos.selected_partitions:
- # il y a des groupes selectionnes, utilise leurs partitions
- for partition_id in groups_infos.selected_partitions:
- if partition_id in etud["partitions"]:
- moodle_groupename.append(
- partitions_name[partition_id]
- + "-"
- + etud["partitions"][partition_id]["group_name"]
- )
- else:
- # pas de groupes sélectionnés: prend le premier s'il y en a un
- moodle_groupename = ["tous"]
- if etud["partitions"]:
- for p in etud["partitions"].items(): # partitions is an OrderedDict
- moodle_groupename = [
- partitions_name[p[0]] + "-" + p[1]["group_name"]
- ]
- break
-
- moodle_groupenames |= set(moodle_groupename)
- etud["semestre_groupe"] = moodle_sem_name + "-" + "+".join(moodle_groupename)
-
- if groups_infos.nbdem > 1:
- s = "s"
- else:
- s = ""
-
- if format == "moodlecsv":
- # de la forme S1-[FI][FA]-groupe.csv
- if not moodle_groupenames:
- moodle_groupenames = {"tous"}
- filename = (
- moodle_sem_name
- + "-"
- + groups_infos.formsemestre["modalite"]
- + "-"
- + "+".join(sorted(moodle_groupenames))
- )
- else:
- filename = "etudiants_%s" % groups_infos.groups_filename
-
- prefs = sco_preferences.SemPreferences(groups_infos.formsemestre_id)
- tab = GenTable(
- rows=groups_infos.members,
- columns_ids=columns_ids,
- titles=titles,
- caption="soit %d étudiants inscrits et %d démissionaire%s."
- % (len(groups_infos.members) - groups_infos.nbdem, groups_infos.nbdem, s),
- base_url=base_url,
- filename=filename,
- pdf_link=False, # pas d'export pdf
- html_sortable=True,
- html_class="table_leftalign table_listegroupe",
- xml_outer_tag="group_list",
- xml_row_tag="etud",
- text_fields_separator=prefs["moodle_csv_separator"],
- text_with_titles=prefs["moodle_csv_with_headerline"],
- preferences=prefs,
- )
- #
- if format == "html":
- amail_inst = [
- x["email"] for x in groups_infos.members if x["email"] and x["etat"] != "D"
- ]
- amail_perso = [
- x["emailperso"]
- for x in groups_infos.members
- if x["emailperso"] and x["etat"] != "D"
- ]
-
- if len(groups_infos.members):
- if groups_infos.tous_les_etuds_du_sem:
- htitle = "Les %d étudiants inscrits" % len(groups_infos.members)
- else:
- htitle = "Groupe %s (%d étudiants)" % (
- groups_infos.groups_titles,
- len(groups_infos.members),
- )
- else:
- htitle = "Aucun étudiant !"
- H = [
- '
")
- if groups_infos.members:
- H.extend(
- [
- tab.html(),
- "
",
- 'Feuille d\'appel Excel '
- % (tab.base_url,),
- 'Table Excel '
- % (tab.base_url,),
- 'Fichier CSV pour Moodle (groupe sélectionné) '
- % (tab.base_url,),
- """
- Fichier CSV pour Moodle (tous les groupes)
- (voir le paramétrage pour modifier le format des fichiers Moodle exportés)
- """
- % groups_infos.formsemestre_id,
- ]
- )
- if amail_inst:
- H.append(
- 'Envoyer un mail collectif au groupe de %s (via %d adresses institutionnelles) '
- % (
- ",".join(amail_inst),
- groups_infos.groups_titles,
- len(amail_inst),
- )
- )
-
- if amail_perso:
- H.append(
- 'Envoyer un mail collectif au groupe de %s (via %d adresses personnelles) '
- % (
- ",".join(amail_perso),
- groups_infos.groups_titles,
- len(amail_perso),
- )
- )
- else:
- H.append("Adresses personnelles non renseignées ")
-
- H.append(" ")
-
- return "".join(H) + "
"
-
- elif (
- format == "pdf"
- or format == "xml"
- or format == "json"
- or format == "xls"
- or format == "moodlecsv"
- ):
- if format == "moodlecsv":
- format = "csv"
- return tab.make_page(format=format)
-
- elif format == "xlsappel":
- xls = sco_excel.excel_feuille_listeappel(
- groups_infos.formsemestre,
- groups_infos.groups_titles,
- groups_infos.members,
- partitions=groups_infos.partitions,
- with_codes=with_codes,
- with_paiement=with_paiement,
- server_name=request.url_root,
- )
- filename = "liste_%s" % groups_infos.groups_filename + ".xlsx"
- return sco_excel.send_excel_file(REQUEST, xls, filename)
- elif format == "allxls":
- # feuille Excel avec toutes les infos etudiants
- if not groups_infos.members:
- return ""
- keys = [
- "etudid",
- "code_nip",
- "etat",
- "civilite_str",
- "nom",
- "nom_usuel",
- "prenom",
- "inscriptionstr",
- ]
- if with_paiement:
- keys.append("paiementinscription")
- keys += [
- "email",
- "emailperso",
- "domicile",
- "villedomicile",
- "codepostaldomicile",
- "paysdomicile",
- "telephone",
- "telephonemobile",
- "fax",
- "date_naissance",
- "lieu_naissance",
- "bac",
- "specialite",
- "annee_bac",
- "nomlycee",
- "villelycee",
- "codepostallycee",
- "codelycee",
- "type_admission",
- "boursier_prec",
- "debouche",
- "parcours",
- "codeparcours",
- ]
- titles = keys[:]
- other_partitions = sco_groups.get_group_other_partitions(groups_infos.groups[0])
- keys += [p["partition_id"] for p in other_partitions]
- titles += [p["partition_name"] for p in other_partitions]
- # remplis infos lycee si on a que le code lycée
- # et ajoute infos inscription
- for m in groups_infos.members:
- etud = sco_etud.get_etud_info(m["etudid"], filled=True)[0]
- m.update(etud)
- sco_etud.etud_add_lycee_infos(etud)
- # et ajoute le parcours
- Se = sco_parcours_dut.SituationEtudParcours(
- etud, groups_infos.formsemestre_id
- )
- m["parcours"] = Se.get_parcours_descr()
- m["codeparcours"], _ = sco_report.get_codeparcoursetud(etud)
-
- def dicttakestr(d, keys):
- r = []
- for k in keys:
- r.append(str(d.get(k, "")))
- return r
-
- L = [dicttakestr(m, keys) for m in groups_infos.members]
- title = "etudiants_%s" % groups_infos.groups_filename
- xls = sco_excel.excel_simple_table(titles=titles, lines=L, sheet_name=title)
- filename = title + scu.XLSX_SUFFIX
- return sco_excel.send_excel_file(REQUEST, xls, filename)
- else:
- raise ValueError("unsupported format")
-
-
-def tab_absences_html(groups_infos, etat=None, REQUEST=None):
- """contenu du tab "absences et feuilles diverses" """
- authuser = REQUEST.AUTHENTICATED_USER
- H = ['
']
- if not groups_infos.members:
- return "".join(H) + "
Aucun étudiant ! "
- H.extend(
- [
- "
Absences ",
- '
',
- "",
- form_choix_saisie_semaine(groups_infos, REQUEST=REQUEST), # Ajout Le Havre
- " ",
- "",
- form_choix_jour_saisie_hebdo(groups_infos, REQUEST=REQUEST),
- " ",
- """État des absences du groupe """
- % (
- groups_infos.groups_query_args,
- groups_infos.formsemestre["date_debut"],
- groups_infos.formsemestre["date_fin"],
- ),
- " ",
- "
Feuilles ",
- '
",
- ]
- )
-
- H.append('
Opérations diverses ')
- # Lien pour verif codes INE/NIP
- # (pour tous les etudiants du semestre)
- group_id = sco_groups.get_default_group(groups_infos.formsemestre_id)
- if authuser.has_permission(Permission.ScoEtudInscrit):
- H.append(
- 'Vérifier codes Apogée (de tous les groupes) '
- % (group_id, etat or "")
- )
- # Lien pour ajout fichiers étudiants
- if authuser.has_permission(Permission.ScoEtudAddAnnotations):
- H.append(
- """Télécharger des fichiers associés aux étudiants (e.g. dossiers d'admission) """
- % (group_id)
- )
-
- H.append(" ")
- return "".join(H)
-
-
-def tab_photos_html(groups_infos, etat=None, REQUEST=None):
- """contenu du tab "photos" """
- from app.scodoc import sco_trombino
-
- if not groups_infos.members:
- return '
Aucun étudiant ! '
-
- return sco_trombino.trombino_html(groups_infos, REQUEST=REQUEST)
-
-
-def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None, REQUEST=None):
- """Formulaire choix jour semaine pour saisie."""
- authuser = REQUEST.AUTHENTICATED_USER
- if not authuser.has_permission(Permission.ScoAbsChange):
- return ""
- sem = groups_infos.formsemestre
- first_monday = sco_abs.ddmmyyyy(sem["date_debut"]).prev_monday()
- today_idx = datetime.date.today().weekday()
-
- FA = [] # formulaire avec menu saisi absences
- FA.append(
- '")
- return "\n".join(FA)
-
-
-# Ajout Le Havre
-# Formulaire saisie absences semaine
-def form_choix_saisie_semaine(groups_infos, REQUEST=None):
- authuser = REQUEST.AUTHENTICATED_USER
- if not authuser.has_permission(Permission.ScoAbsChange):
- return ""
- # construit l'URL "destination"
- # (a laquelle on revient apres saisie absences)
- query_args = parse_qs(request.query_string)
- moduleimpl_id = query_args.get("moduleimpl_id", [""])[0]
- if "head_message" in query_args:
- del query_args["head_message"]
- destination = "%s?%s" % (
- request.base_url,
- urllib.parse.urlencode(query_args, True),
- )
- destination = destination.replace(
- "%", "%%"
- ) # car ici utilisee dans un format string !
-
- DateJour = time.strftime("%d/%m/%Y")
- datelundi = sco_abs.ddmmyyyy(DateJour).prev_monday()
- FA = [] # formulaire avec menu saisie hebdo des absences
- FA.append('")
- return "\n".join(FA)
-
-
-def export_groups_as_moodle_csv(formsemestre_id=None):
- """Export all students/groups, in a CSV format suitable for Moodle
- Each (student,group) will be listed on a separate line
- jo@univ.fr,S3-A
- jo@univ.fr,S3-B1
- if jo belongs to group A in a partition, and B1 in another one.
- Caution: if groups in different partitions share the same name, there will be
- duplicates... should we prefix the group names with the partition's name ?
- """
- if not formsemestre_id:
- raise ScoValueError("missing parameter: formsemestre_id")
- _, partitions_etud_groups = sco_groups.get_formsemestre_groups(
- formsemestre_id, with_default=True
- )
- sem = sco_formsemestre.get_formsemestre(formsemestre_id)
- moodle_sem_name = sem["session_id"]
-
- columns_ids = ("email", "semestre_groupe")
- T = []
- for partition_id in partitions_etud_groups:
- partition = sco_groups.get_partition(partition_id)
- members = partitions_etud_groups[partition_id]
- for etudid in members:
- etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
- group_name = members[etudid]["group_name"]
- elts = [moodle_sem_name]
- if partition["partition_name"]:
- elts.append(partition["partition_name"])
- if group_name:
- elts.append(group_name)
- T.append({"email": etud["email"], "semestre_groupe": "-".join(elts)})
- # Make table
- prefs = sco_preferences.SemPreferences(formsemestre_id)
- tab = GenTable(
- rows=T,
- columns_ids=("email", "semestre_groupe"),
- filename=moodle_sem_name + "-moodle",
- titles={x: x for x in columns_ids},
- text_fields_separator=prefs["moodle_csv_separator"],
- text_with_titles=prefs["moodle_csv_with_headerline"],
- preferences=prefs,
- )
- return tab.make_page(format="csv")
+# -*- 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
+#
+##############################################################################
+
+"""Affichage étudiants d'un ou plusieurs groupes
+ sous forme: de liste html (table exportable), de trombinoscope (exportable en pdf)
+"""
+
+# Re-ecriture en 2014 (re-organisation de l'interface, modernisation du code)
+
+import collections
+import datetime
+import operator
+import urllib
+from urllib.parse import parse_qs
+import time
+
+
+from flask import url_for, g, request
+
+import app.scodoc.sco_utils as scu
+from app.scodoc import html_sco_header
+from app.scodoc import sco_abs
+from app.scodoc import sco_excel
+from app.scodoc import sco_formsemestre
+from app.scodoc import sco_groups
+from app.scodoc import sco_moduleimpl
+from app.scodoc import sco_parcours_dut
+from app.scodoc import sco_portal_apogee
+from app.scodoc import sco_preferences
+from app.scodoc import sco_etud
+from app.scodoc.gen_tables import GenTable
+from app.scodoc.sco_exceptions import ScoValueError
+from app.scodoc.sco_permissions import Permission
+from six.moves import range
+
+JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + [
+ "js/etud_info.js",
+ "js/groups_view.js",
+]
+
+CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
+
+
+def groups_view(
+ group_ids=[],
+ format="html",
+ REQUEST=None,
+ # Options pour listes:
+ with_codes=0,
+ etat=None,
+ with_paiement=0, # si vrai, ajoute colonnes infos paiement droits et finalisation inscription (lent car interrogation portail)
+ with_archives=0, # ajoute colonne avec noms fichiers archivés
+ with_annotations=0,
+ formsemestre_id=None, # utilise si aucun groupe selectionné
+):
+ """Affichage des étudiants des groupes indiqués
+ group_ids: liste de group_id
+ format: csv, json, xml, xls, allxls, xlsappel, moodlecsv, pdf
+ """
+ # Informations sur les groupes à afficher:
+ groups_infos = DisplayedGroupsInfos(
+ group_ids,
+ formsemestre_id=formsemestre_id,
+ etat=etat,
+ select_all_when_unspecified=True,
+ )
+ # Formats spéciaux: download direct
+ if format != "html":
+ return groups_table(
+ groups_infos=groups_infos,
+ format=format,
+ REQUEST=REQUEST,
+ with_codes=with_codes,
+ etat=etat,
+ with_paiement=with_paiement,
+ with_archives=with_archives,
+ with_annotations=with_annotations,
+ )
+
+ H = [
+ html_sco_header.sco_header(
+ javascripts=JAVASCRIPTS,
+ cssstyles=CSSSTYLES,
+ init_qtip=True,
+ )
+ ]
+ # Menu choix groupe
+ H.append("""""")
+ H.append(form_groups_choice(groups_infos, submit_on_change=True))
+ # Note: le formulaire est soumis a chaque modif des groupes
+ # on pourrait faire comme pour le form de saisie des notes. Il faudrait pour cela:
+ # - charger tous les etudiants au debut, quels que soient les groupes selectionnés
+ # - ajouter du JS pour modifier les liens (arguments group_ids) quand le menu change
+
+ # Tabs
+ # H.extend( ("""
toto """,) )
+ H.extend(
+ (
+ """
+
+
+
+
+ """,
+ groups_table(
+ groups_infos=groups_infos,
+ format=format,
+ REQUEST=REQUEST,
+ with_codes=with_codes,
+ etat=etat,
+ with_paiement=with_paiement,
+ with_archives=with_archives,
+ with_annotations=with_annotations,
+ ),
+ "
",
+ """
""",
+ tab_photos_html(groups_infos, etat=etat, REQUEST=REQUEST),
+ #'
hello
',
+ "
",
+ '
',
+ tab_absences_html(groups_infos, etat=etat, REQUEST=REQUEST),
+ "
",
+ )
+ )
+
+ H.append(html_sco_header.sco_footer())
+ return "\n".join(H)
+
+
+def form_groups_choice(groups_infos, with_selectall_butt=False, submit_on_change=False):
+ """form pour selection groupes
+ group_ids est la liste des groupes actuellement sélectionnés
+ et doit comporter au moins un élément, sauf si formsemestre_id est spécifié.
+ (utilisé pour retrouver le semestre et proposer la liste des autres groupes)
+
+ Si submit_on_change, ajoute une classe "submit_on_change" qui est utilisee en JS
+ """
+ default_group_id = sco_groups.get_default_group(groups_infos.formsemestre_id)
+
+ H = [
+ """
")
+
+ return "\n".join(H)
+
+
+def menu_groups_choice(groups_infos, submit_on_change=False):
+ """menu pour selection groupes
+ group_ids est la liste des groupes actuellement sélectionnés
+ et doit comporter au moins un élément, sauf si formsemestre_id est spécifié.
+ (utilisé pour retrouver le semestre et proposer la liste des autres groupes)
+ """
+ default_group_id = sco_groups.get_default_group(groups_infos.formsemestre_id)
+
+ if submit_on_change:
+ klass = "submit_on_change"
+ else:
+ klass = ""
+ H = [
+ """
+ """
+ % (klass,)
+ ]
+
+ n_members = len(sco_groups.get_group_members(default_group_id))
+ if default_group_id in groups_infos.group_ids:
+ selected = "selected"
+ else:
+ selected = ""
+ H.append(
+ '%s (%s) '
+ % (default_group_id, selected, "Tous", n_members)
+ )
+
+ for partition in groups_infos.partitions:
+ H.append('' % partition["partition_name"])
+ # Les groupes dans cette partition:
+ for g in sco_groups.get_partition_groups(partition):
+ if g["group_id"] in groups_infos.group_ids:
+ selected = "selected"
+ else:
+ selected = ""
+ if g["group_name"]:
+ n_members = len(sco_groups.get_group_members(g["group_id"]))
+ H.append(
+ '%s (%s) '
+ % (g["group_id"], selected, g["group_name"], n_members)
+ )
+ H.append(" ")
+ H.append(" ")
+ return "\n".join(H)
+
+
+def menu_group_choice(group_id=None, formsemestre_id=None):
+ """Un bête menu pour choisir un seul groupe
+ group_id est le groupe actuellement sélectionné.
+ Si aucun groupe selectionné, utilise formsemestre_id pour lister les groupes.
+ """
+ if group_id:
+ group = sco_groups.get_group(group_id)
+ formsemestre_id = group["formsemestre_id"]
+ elif not formsemestre_id:
+ raise ValueError("missing formsemestre_id")
+ H = [
+ """
+
+ """
+ ]
+ if not group_id:
+ H.append('choisir... ')
+ for partition in sco_groups.get_partitions_list(formsemestre_id):
+ if partition["partition_name"]:
+ H.append('' % partition["partition_name"])
+ groups = sco_groups.get_partition_groups(partition)
+ for group in groups:
+ if group["group_id"] == group_id:
+ selected = "selected"
+ else:
+ selected = ""
+ name = group["group_name"] or "Tous"
+ n_members = len(sco_groups.get_group_members(group["group_id"]))
+ H.append(
+ '%s (%s) '
+ % (group["group_id"], selected, name, n_members)
+ )
+ if partition["partition_name"]:
+ H.append(" ")
+ H.append(
+ """
+
+ """
+ )
+ return "\n".join(H)
+
+
+class DisplayedGroupsInfos(object):
+ """Container with attributes describing groups to display in the page
+ .groups_query_args : 'group_ids=xxx&group_ids=yyy'
+ .base_url : url de la requete, avec les groupes, sans les autres paramètres
+ .formsemestre_id : semestre "principal" (en fait celui du 1er groupe de la liste)
+ .members
+ .groups_titles
+ """
+
+ def __init__(
+ self,
+ group_ids=[], # groupes specifies dans l'URL, ou un seul int
+ formsemestre_id=None,
+ etat=None,
+ select_all_when_unspecified=False,
+ moduleimpl_id=None, # used to find formsemestre when unspecified
+ ):
+ if isinstance(group_ids, int):
+ if group_ids:
+ group_ids = [group_ids] # cas ou un seul parametre, pas de liste
+ else:
+ group_ids = []
+ if not formsemestre_id and moduleimpl_id:
+ mods = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)
+ if len(mods) != 1:
+ raise ValueError("invalid moduleimpl_id")
+ formsemestre_id = mods[0]["formsemestre_id"]
+
+ if not group_ids: # appel sans groupe (eg page accueil)
+ if not formsemestre_id:
+ raise Exception("missing parameter formsemestre_id or group_ids")
+ if select_all_when_unspecified:
+ group_ids = [sco_groups.get_default_group(formsemestre_id)]
+ else:
+ # selectionne le premier groupe trouvé, s'il y en a un
+ partition = sco_groups.get_partitions_list(
+ formsemestre_id, with_default=True
+ )[0]
+ groups = sco_groups.get_partition_groups(partition)
+ if groups:
+ group_ids = [groups[0]["group_id"]]
+ else:
+ group_ids = [sco_groups.get_default_group(formsemestre_id)]
+
+ gq = []
+ for group_id in group_ids:
+ gq.append("group_ids=" + str(group_id))
+ self.groups_query_args = "&".join(gq)
+ self.base_url = request.base_url + "?" + self.groups_query_args
+ self.group_ids = group_ids
+ self.groups = []
+ groups_titles = []
+ self.members = []
+ self.tous_les_etuds_du_sem = (
+ False # affiche tous les etuds du semestre ? (si un seul semestre)
+ )
+ self.sems = collections.OrderedDict() # formsemestre_id : sem
+ self.formsemestre = None
+ self.formsemestre_id = formsemestre_id
+ self.nbdem = 0 # nombre d'étudiants démissionnaires en tout
+ sem = None
+ selected_partitions = set()
+ for group_id in group_ids:
+ group_members, group, group_tit, sem, nbdem = sco_groups.get_group_infos(
+ group_id, etat=etat
+ )
+ self.groups.append(group)
+ self.nbdem += nbdem
+ self.sems[sem["formsemestre_id"]] = sem
+ if not self.formsemestre_id:
+ self.formsemestre_id = sem["formsemestre_id"]
+ self.formsemestre = sem
+ self.members.extend(group_members)
+ groups_titles.append(group_tit)
+ if group["group_name"] == None:
+ self.tous_les_etuds_du_sem = True
+ else:
+ # liste les partitions explicitement sélectionnés (= des groupes de group_ids)
+ selected_partitions.add((group["numero"], group["partition_id"]))
+
+ self.selected_partitions = [
+ x[1] for x in sorted(list(selected_partitions))
+ ] # -> [ partition_id ]
+
+ if not self.formsemestre: # aucun groupe selectionne
+ self.formsemestre = sco_formsemestre.get_formsemestre(formsemestre_id)
+
+ self.sortuniq()
+
+ if len(self.sems) > 1:
+ self.tous_les_etuds_du_sem = False # plusieurs semestres
+ if self.tous_les_etuds_du_sem:
+ if sem and sem["semestre_id"] >= 0:
+ self.groups_titles = "S%d" % sem["semestre_id"]
+ else:
+ self.groups_titles = "tous"
+ self.groups_filename = self.groups_titles
+ else:
+ self.groups_titles = ", ".join(groups_titles)
+ self.groups_filename = "_".join(groups_titles).replace(" ", "_")
+ # Sanitize filename:
+ self.groups_filename = scu.make_filename(self.groups_filename)
+
+ # colonnes pour affichages nom des groupes:
+ # gère le cas où les étudiants appartiennent à des semestres différents
+ self.partitions = [] # les partitions, sans celle par defaut
+ for formsemestre_id in self.sems:
+ for partition in sco_groups.get_partitions_list(formsemestre_id):
+ if partition["partition_name"]:
+ self.partitions.append(partition)
+
+ def sortuniq(self):
+ "Trie les étudiants (de plusieurs groupes) et elimine les doublons"
+ if (len(self.group_ids) <= 1) or len(self.members) <= 1:
+ return # on suppose que les etudiants d'un groupe sont deja triés
+ self.members.sort(
+ key=operator.itemgetter("nom_disp", "prenom")
+ ) # tri selon nom_usuel ou nom
+ to_remove = []
+ T = self.members
+ for i in range(len(T) - 1, 0, -1):
+ if T[i - 1]["etudid"] == T[i]["etudid"]:
+ to_remove.append(i)
+ for i in to_remove:
+ del T[i]
+
+ def get_form_elem(self):
+ """html hidden input with groups"""
+ H = []
+ for group_id in self.group_ids:
+ H.append('
' % group_id)
+ return "\n".join(H)
+
+
+# Ancien ZScolar.group_list renommé ici en group_table
+def groups_table(
+ REQUEST=None,
+ groups_infos=None, # instance of DisplayedGroupsInfos
+ with_codes=0,
+ etat=None,
+ format="html",
+ with_paiement=0, # si vrai, ajoute colonnes infos paiement droits et finalisation inscription (lent car interrogation portail)
+ with_archives=0, # ajoute colonne avec noms fichiers archivés
+ with_annotations=0,
+):
+ """liste etudiants inscrits dans ce semestre
+ format: csv, json, xml, xls, allxls, xlsappel, moodlecsv, pdf
+ Si with_codes, ajoute 4 colonnes avec les codes etudid, NIP, INE et etape
+ """
+ from app.scodoc import sco_report
+
+ # log(
+ # "enter groups_table %s: %s"
+ # % (groups_infos.members[0]["nom"], groups_infos.members[0].get("etape", "-"))
+ # )
+ with_codes = int(with_codes)
+ with_paiement = int(with_paiement)
+ with_archives = int(with_archives)
+ with_annotations = int(with_annotations)
+
+ base_url_np = groups_infos.base_url + "&with_codes=%s" % with_codes
+ base_url = (
+ base_url_np
+ + "&with_paiement=%s&with_archives=%s&with_annotations=%s"
+ % (with_paiement, with_archives, with_annotations)
+ )
+ #
+ columns_ids = ["civilite_str", "nom_disp", "prenom"] # colonnes a inclure
+ titles = {
+ "civilite_str": "Civ.",
+ "nom_disp": "Nom",
+ "prenom": "Prénom",
+ "email": "Mail",
+ "emailperso": "Personnel",
+ "etat": "Etat",
+ "etudid": "etudid",
+ "code_nip": "code_nip",
+ "code_ine": "code_ine",
+ "datefinalisationinscription_str": "Finalisation inscr.",
+ "paiementinscription_str": "Paiement",
+ "etudarchive": "Fichiers",
+ "annotations_str": "Annotations",
+ "etape": "Etape",
+ "semestre_groupe": "Semestre-Groupe", # pour Moodle
+ }
+
+ # ajoute colonnes pour groupes
+ columns_ids.extend([p["partition_id"] for p in groups_infos.partitions])
+ titles.update(
+ dict(
+ [(p["partition_id"], p["partition_name"]) for p in groups_infos.partitions]
+ )
+ )
+ partitions_name = {
+ p["partition_id"]: p["partition_name"] for p in groups_infos.partitions
+ }
+
+ if format != "html": # ne mentionne l'état que en Excel (style en html)
+ columns_ids.append("etat")
+ columns_ids.append("email")
+ columns_ids.append("emailperso")
+
+ if format == "moodlecsv":
+ columns_ids = ["email", "semestre_groupe"]
+
+ if with_codes:
+ columns_ids += ["etape", "etudid", "code_nip", "code_ine"]
+ if with_paiement:
+ columns_ids += ["datefinalisationinscription_str", "paiementinscription_str"]
+ if with_paiement or with_codes:
+ sco_portal_apogee.check_paiement_etuds(groups_infos.members)
+ if with_archives:
+ from app.scodoc import sco_archives_etud
+
+ sco_archives_etud.add_archives_info_to_etud_list(groups_infos.members)
+ columns_ids += ["etudarchive"]
+ if with_annotations:
+ sco_etud.add_annotations_to_etud_list(groups_infos.members)
+ columns_ids += ["annotations_str"]
+ moodle_sem_name = groups_infos.formsemestre["session_id"]
+ moodle_groupenames = set()
+ # ajoute liens
+ for etud in groups_infos.members:
+ if etud["email"]:
+ etud["_email_target"] = "mailto:" + etud["email"]
+ else:
+ etud["_email_target"] = ""
+ if etud["emailperso"]:
+ etud["_emailperso_target"] = "mailto:" + etud["emailperso"]
+ else:
+ etud["_emailperso_target"] = ""
+ fiche_url = url_for(
+ "scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
+ )
+ etud["_nom_disp_target"] = fiche_url
+ etud["_prenom_target"] = fiche_url
+
+ etud["_nom_disp_td_attrs"] = 'id="%s" class="etudinfo"' % (etud["etudid"])
+
+ if etud["etat"] == "D":
+ etud["_css_row_class"] = "etuddem"
+ # et groupes:
+ for partition_id in etud["partitions"]:
+ etud[partition_id] = etud["partitions"][partition_id]["group_name"]
+ # Ajoute colonne pour moodle: semestre_groupe, de la forme RT-DUT-FI-S3-2021-PARTITION-GROUPE
+ moodle_groupename = []
+ if groups_infos.selected_partitions:
+ # il y a des groupes selectionnes, utilise leurs partitions
+ for partition_id in groups_infos.selected_partitions:
+ if partition_id in etud["partitions"]:
+ moodle_groupename.append(
+ partitions_name[partition_id]
+ + "-"
+ + etud["partitions"][partition_id]["group_name"]
+ )
+ else:
+ # pas de groupes sélectionnés: prend le premier s'il y en a un
+ moodle_groupename = ["tous"]
+ if etud["partitions"]:
+ for p in etud["partitions"].items(): # partitions is an OrderedDict
+ moodle_groupename = [
+ partitions_name[p[0]] + "-" + p[1]["group_name"]
+ ]
+ break
+
+ moodle_groupenames |= set(moodle_groupename)
+ etud["semestre_groupe"] = moodle_sem_name + "-" + "+".join(moodle_groupename)
+
+ if groups_infos.nbdem > 1:
+ s = "s"
+ else:
+ s = ""
+
+ if format == "moodlecsv":
+ # de la forme S1-[FI][FA]-groupe.csv
+ if not moodle_groupenames:
+ moodle_groupenames = {"tous"}
+ filename = (
+ moodle_sem_name
+ + "-"
+ + groups_infos.formsemestre["modalite"]
+ + "-"
+ + "+".join(sorted(moodle_groupenames))
+ )
+ else:
+ filename = "etudiants_%s" % groups_infos.groups_filename
+
+ prefs = sco_preferences.SemPreferences(groups_infos.formsemestre_id)
+ tab = GenTable(
+ rows=groups_infos.members,
+ columns_ids=columns_ids,
+ titles=titles,
+ caption="soit %d étudiants inscrits et %d démissionaire%s."
+ % (len(groups_infos.members) - groups_infos.nbdem, groups_infos.nbdem, s),
+ base_url=base_url,
+ filename=filename,
+ pdf_link=False, # pas d'export pdf
+ html_sortable=True,
+ html_class="table_leftalign table_listegroupe",
+ xml_outer_tag="group_list",
+ xml_row_tag="etud",
+ text_fields_separator=prefs["moodle_csv_separator"],
+ text_with_titles=prefs["moodle_csv_with_headerline"],
+ preferences=prefs,
+ )
+ #
+ if format == "html":
+ amail_inst = [
+ x["email"] for x in groups_infos.members if x["email"] and x["etat"] != "D"
+ ]
+ amail_perso = [
+ x["emailperso"]
+ for x in groups_infos.members
+ if x["emailperso"] and x["etat"] != "D"
+ ]
+
+ if len(groups_infos.members):
+ if groups_infos.tous_les_etuds_du_sem:
+ htitle = "Les %d étudiants inscrits" % len(groups_infos.members)
+ else:
+ htitle = "Groupe %s (%d étudiants)" % (
+ groups_infos.groups_titles,
+ len(groups_infos.members),
+ )
+ else:
+ htitle = "Aucun étudiant !"
+ H = [
+ '
")
+ if groups_infos.members:
+ H.extend(
+ [
+ tab.html(),
+ "
",
+ 'Feuille d\'appel Excel '
+ % (tab.base_url,),
+ 'Table Excel '
+ % (tab.base_url,),
+ 'Fichier CSV pour Moodle (groupe sélectionné) '
+ % (tab.base_url,),
+ """
+ Fichier CSV pour Moodle (tous les groupes)
+ (voir le paramétrage pour modifier le format des fichiers Moodle exportés)
+ """
+ % groups_infos.formsemestre_id,
+ ]
+ )
+ if amail_inst:
+ H.append(
+ 'Envoyer un mail collectif au groupe de %s (via %d adresses institutionnelles) '
+ % (
+ ",".join(amail_inst),
+ groups_infos.groups_titles,
+ len(amail_inst),
+ )
+ )
+
+ if amail_perso:
+ H.append(
+ 'Envoyer un mail collectif au groupe de %s (via %d adresses personnelles) '
+ % (
+ ",".join(amail_perso),
+ groups_infos.groups_titles,
+ len(amail_perso),
+ )
+ )
+ else:
+ H.append("Adresses personnelles non renseignées ")
+
+ H.append(" ")
+
+ return "".join(H) + "
"
+
+ elif (
+ format == "pdf"
+ or format == "xml"
+ or format == "json"
+ or format == "xls"
+ or format == "moodlecsv"
+ ):
+ if format == "moodlecsv":
+ format = "csv"
+ return tab.make_page(format=format)
+
+ elif format == "xlsappel":
+ xls = sco_excel.excel_feuille_listeappel(
+ groups_infos.formsemestre,
+ groups_infos.groups_titles,
+ groups_infos.members,
+ partitions=groups_infos.partitions,
+ with_codes=with_codes,
+ with_paiement=with_paiement,
+ server_name=request.url_root,
+ )
+ filename = "liste_%s" % groups_infos.groups_filename + ".xlsx"
+ return scu.send_file(
+ xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE, attached=True
+ )
+ # return sco_excel.send_excel_file(REQUEST, xls, filename)
+ elif format == "allxls":
+ # feuille Excel avec toutes les infos etudiants
+ if not groups_infos.members:
+ return ""
+ keys = [
+ "etudid",
+ "code_nip",
+ "etat",
+ "civilite_str",
+ "nom",
+ "nom_usuel",
+ "prenom",
+ "inscriptionstr",
+ ]
+ if with_paiement:
+ keys.append("paiementinscription")
+ keys += [
+ "email",
+ "emailperso",
+ "domicile",
+ "villedomicile",
+ "codepostaldomicile",
+ "paysdomicile",
+ "telephone",
+ "telephonemobile",
+ "fax",
+ "date_naissance",
+ "lieu_naissance",
+ "bac",
+ "specialite",
+ "annee_bac",
+ "nomlycee",
+ "villelycee",
+ "codepostallycee",
+ "codelycee",
+ "type_admission",
+ "boursier_prec",
+ "debouche",
+ "parcours",
+ "codeparcours",
+ ]
+ titles = keys[:]
+ other_partitions = sco_groups.get_group_other_partitions(groups_infos.groups[0])
+ keys += [p["partition_id"] for p in other_partitions]
+ titles += [p["partition_name"] for p in other_partitions]
+ # remplis infos lycee si on a que le code lycée
+ # et ajoute infos inscription
+ for m in groups_infos.members:
+ etud = sco_etud.get_etud_info(m["etudid"], filled=True)[0]
+ m.update(etud)
+ sco_etud.etud_add_lycee_infos(etud)
+ # et ajoute le parcours
+ Se = sco_parcours_dut.SituationEtudParcours(
+ etud, groups_infos.formsemestre_id
+ )
+ m["parcours"] = Se.get_parcours_descr()
+ m["codeparcours"], _ = sco_report.get_codeparcoursetud(etud)
+
+ def dicttakestr(d, keys):
+ r = []
+ for k in keys:
+ r.append(str(d.get(k, "")))
+ return r
+
+ L = [dicttakestr(m, keys) for m in groups_infos.members]
+ title = "etudiants_%s" % groups_infos.groups_filename
+ xls = sco_excel.excel_simple_table(titles=titles, lines=L, sheet_name=title)
+ filename = title + scu.XLSX_SUFFIX
+ return scu.send_file(
+ xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE, attached=True
+ )
+ # return sco_excel.send_excel_file(REQUEST, xls, filename)
+ else:
+ raise ValueError("unsupported format")
+
+
+def tab_absences_html(groups_infos, etat=None, REQUEST=None):
+ """contenu du tab "absences et feuilles diverses" """
+ authuser = REQUEST.AUTHENTICATED_USER
+ H = ['
']
+ if not groups_infos.members:
+ return "".join(H) + "
Aucun étudiant ! "
+ H.extend(
+ [
+ "
Absences ",
+ '
',
+ "",
+ form_choix_saisie_semaine(groups_infos, REQUEST=REQUEST), # Ajout Le Havre
+ " ",
+ "",
+ form_choix_jour_saisie_hebdo(groups_infos, REQUEST=REQUEST),
+ " ",
+ """État des absences du groupe """
+ % (
+ groups_infos.groups_query_args,
+ groups_infos.formsemestre["date_debut"],
+ groups_infos.formsemestre["date_fin"],
+ ),
+ " ",
+ "
Feuilles ",
+ '
",
+ ]
+ )
+
+ H.append('
Opérations diverses ')
+ # Lien pour verif codes INE/NIP
+ # (pour tous les etudiants du semestre)
+ group_id = sco_groups.get_default_group(groups_infos.formsemestre_id)
+ if authuser.has_permission(Permission.ScoEtudInscrit):
+ H.append(
+ 'Vérifier codes Apogée (de tous les groupes) '
+ % (group_id, etat or "")
+ )
+ # Lien pour ajout fichiers étudiants
+ if authuser.has_permission(Permission.ScoEtudAddAnnotations):
+ H.append(
+ """Télécharger des fichiers associés aux étudiants (e.g. dossiers d'admission) """
+ % (group_id)
+ )
+
+ H.append(" ")
+ return "".join(H)
+
+
+def tab_photos_html(groups_infos, etat=None, REQUEST=None):
+ """contenu du tab "photos" """
+ from app.scodoc import sco_trombino
+
+ if not groups_infos.members:
+ return '
Aucun étudiant ! '
+
+ return sco_trombino.trombino_html(groups_infos, REQUEST=REQUEST)
+
+
+def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None, REQUEST=None):
+ """Formulaire choix jour semaine pour saisie."""
+ authuser = REQUEST.AUTHENTICATED_USER
+ if not authuser.has_permission(Permission.ScoAbsChange):
+ return ""
+ sem = groups_infos.formsemestre
+ first_monday = sco_abs.ddmmyyyy(sem["date_debut"]).prev_monday()
+ today_idx = datetime.date.today().weekday()
+
+ FA = [] # formulaire avec menu saisi absences
+ FA.append(
+ '")
+ return "\n".join(FA)
+
+
+# Ajout Le Havre
+# Formulaire saisie absences semaine
+def form_choix_saisie_semaine(groups_infos, REQUEST=None):
+ authuser = REQUEST.AUTHENTICATED_USER
+ if not authuser.has_permission(Permission.ScoAbsChange):
+ return ""
+ # construit l'URL "destination"
+ # (a laquelle on revient apres saisie absences)
+ query_args = parse_qs(request.query_string)
+ moduleimpl_id = query_args.get("moduleimpl_id", [""])[0]
+ if "head_message" in query_args:
+ del query_args["head_message"]
+ destination = "%s?%s" % (
+ request.base_url,
+ urllib.parse.urlencode(query_args, True),
+ )
+ destination = destination.replace(
+ "%", "%%"
+ ) # car ici utilisee dans un format string !
+
+ DateJour = time.strftime("%d/%m/%Y")
+ datelundi = sco_abs.ddmmyyyy(DateJour).prev_monday()
+ FA = [] # formulaire avec menu saisie hebdo des absences
+ FA.append('")
+ return "\n".join(FA)
+
+
+def export_groups_as_moodle_csv(formsemestre_id=None):
+ """Export all students/groups, in a CSV format suitable for Moodle
+ Each (student,group) will be listed on a separate line
+ jo@univ.fr,S3-A
+ jo@univ.fr,S3-B1
+ if jo belongs to group A in a partition, and B1 in another one.
+ Caution: if groups in different partitions share the same name, there will be
+ duplicates... should we prefix the group names with the partition's name ?
+ """
+ if not formsemestre_id:
+ raise ScoValueError("missing parameter: formsemestre_id")
+ _, partitions_etud_groups = sco_groups.get_formsemestre_groups(
+ formsemestre_id, with_default=True
+ )
+ sem = sco_formsemestre.get_formsemestre(formsemestre_id)
+ moodle_sem_name = sem["session_id"]
+
+ columns_ids = ("email", "semestre_groupe")
+ T = []
+ for partition_id in partitions_etud_groups:
+ partition = sco_groups.get_partition(partition_id)
+ members = partitions_etud_groups[partition_id]
+ for etudid in members:
+ etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
+ group_name = members[etudid]["group_name"]
+ elts = [moodle_sem_name]
+ if partition["partition_name"]:
+ elts.append(partition["partition_name"])
+ if group_name:
+ elts.append(group_name)
+ T.append({"email": etud["email"], "semestre_groupe": "-".join(elts)})
+ # Make table
+ prefs = sco_preferences.SemPreferences(formsemestre_id)
+ tab = GenTable(
+ rows=T,
+ columns_ids=("email", "semestre_groupe"),
+ filename=moodle_sem_name + "-moodle",
+ titles={x: x for x in columns_ids},
+ text_fields_separator=prefs["moodle_csv_separator"],
+ text_with_titles=prefs["moodle_csv_with_headerline"],
+ preferences=prefs,
+ )
+ return tab.make_page(format="csv")
diff --git a/app/scodoc/sco_placement.py b/app/scodoc/sco_placement.py
index 1fec11725c..ac12fcb0dd 100644
--- a/app/scodoc/sco_placement.py
+++ b/app/scodoc/sco_placement.py
@@ -32,310 +32,306 @@ Contribution M. Salomon, UFC / IUT DE BELFORT-MONTBÉLIARD, 2016
"""
import random
import time
-import urllib
+from copy import copy
-import flask
-from flask import request
-
-from app.scodoc.sco_exceptions import ScoValueError
+import wtforms.validators
+from flask import request, render_template
+from flask_login import current_user
+from flask_wtf import FlaskForm
+from openpyxl.styles import PatternFill, Alignment, Border, Side, Font
+from wtforms import (
+ StringField,
+ SubmitField,
+ SelectField,
+ RadioField,
+ HiddenField,
+ SelectMultipleField,
+)
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
-from app import log
-from app.scodoc import html_sco_header
+from app import ScoValueError
+from app.scodoc import html_sco_header, sco_preferences
from app.scodoc import sco_edit_module
from app.scodoc import sco_evaluations
from app.scodoc import sco_excel
+from app.scodoc.sco_excel import ScoExcelBook, COLORS
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.gen_tables import GenTable
from app.scodoc import sco_etud
import sco_version
-from app.scodoc.gen_tables import GenTable
-from app.scodoc.sco_excel import * # XXX à vérifier
-from app.scodoc.TrivialFormulator import TrivialFormulator
+
+_ = lambda x: x # sans babel
+_l = _
+
+COORD = "Coordonnées"
+SEQ = "Continue"
+
+TOUS = "Tous"
-def do_placement_selectetuds(REQUEST):
- """
- Choisi les étudiants et les infos sur la salle pour leur placement.
- """
- evaluation_id = int(REQUEST.form["evaluation_id"])
- E = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
- if not E:
- raise ScoValueError("invalid evaluation_id")
- E = E[0]
- # M = sco_moduleimpl.do_moduleimpl_list( moduleimpl_id=E["moduleimpl_id"])[0]
+def _get_group_info(evaluation_id):
# groupes
groups = sco_groups.do_evaluation_listegroupes(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
+ has_groups = False
+ groups_tree = {}
+ for group in groups:
+ partition = group["partition_name"] or TOUS
+ group_id = group["group_id"]
+ group_name = group["group_name"] or TOUS
+ if partition not in groups_tree:
+ groups_tree[partition] = {}
+ groups_tree[partition][group_name] = group_id
+ if partition != TOUS:
+ has_groups = True
+ nb_groups = len(groups_tree)
+ return groups_tree, has_groups, nb_groups
- # description de l'evaluation
- H = [
+
+class PlacementForm(FlaskForm):
+ """Formulaire pour placement des étudiants en Salle"""
+
+ evaluation_id = HiddenField("evaluation_id")
+ file_format = RadioField(
+ "Format de fichier",
+ choices=["pdf", "xls"],
+ validators=[
+ wtforms.validators.DataRequired("indiquez le format du fichier attendu"),
+ ],
+ )
+ surveillants = StringField("Surveillants", validators=[])
+ batiment = StringField("Batiment")
+ salle = StringField("Salle")
+ nb_rangs = SelectField(
+ "nb_rangs", coerce=int, choices=[3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
+ )
+ etiquetage = RadioField(
+ "Numérotation",
+ choices=[SEQ, COORD],
+ validators=[
+ wtforms.validators.DataRequired("indiquez le style de numérotation"),
+ ],
+ )
+ groups = SelectMultipleField(
+ "Groupe(s)",
+ validators=[
+ wtforms.validators.DataRequired("indiquez au moins un groupe"),
+ ],
+ )
+ submit = SubmitField("OK")
+
+ def __init__(self, formdata=None, data=None):
+ super().__init__(formdata=formdata, data=data)
+ self.groups_tree = {}
+ self.has_groups = None
+ self.group_tree_length = None
+ self.nb_groups = None
+ self.set_evaluation_infos(data["evaluation_id"])
+
+ def set_evaluation_infos(self, evaluation_id):
+ """Initialise les données du formulaire avec les données de l'évaluation."""
+ eval_data = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
+ if not eval_data:
+ raise ScoValueError("invalid evaluation_id")
+ self.groups_tree, self.has_groups, self.nb_groups = _get_group_info(
+ evaluation_id
+ )
+ if self.has_groups:
+ choices = []
+ for partition in self.groups_tree:
+ for groupe in self.groups_tree[partition]:
+ groupe_id = str(self.groups_tree[partition][groupe])
+ choices.append((groupe_id, "%s (%s)" % (str(groupe), partition)))
+ self.groups.choices = choices
+
+
+class _DistributeurContinu:
+ """Distribue les places selon un ordre numérique."""
+
+ def __init__(self):
+ self.position = 1
+
+ def suivant(self):
+ """Retounre la désignation de la place suivante"""
+ retour = self.position
+ self.position += 1
+ return retour
+
+
+class _Distributeur2D:
+ """Distribue les places selon des coordonnées sur nb_rangs."""
+
+ def __init__(self, nb_rangs):
+ self.nb_rangs = nb_rangs
+ self.rang = 1
+ self.index = 1
+
+ def suivant(self):
+ """Retounre la désignation de la place suivante"""
+ retour = (self.index, self.rang)
+ self.rang += 1
+ if self.rang > self.nb_rangs:
+ self.rang = 1
+ self.index += 1
+ return retour
+
+
+def placement_eval_selectetuds(evaluation_id):
+ """Creation de l'écran de placement"""
+ form = PlacementForm(
+ request.form,
+ data={"evaluation_id": int(evaluation_id), "groups": TOUS},
+ )
+ if form.validate_on_submit():
+ runner = PlacementRunner(form)
+ if not runner.check_placement():
+ return (
+ """Génération du placement impossible pour %s
+ (vérifiez que le semestre n'est pas verrouillé et que vous
+ avez l'autorisation d'effectuer cette opération)
+ Continuer
+ """
+ % runner.__dict__
+ )
+ return runner.exec_placement() # calcul et generation du fichier
+ # return flask.redirect(url_for("scodoc.index"))
+ htmls = [
+ html_sco_header.sco_header(init_jquery_ui=True),
sco_evaluations.evaluation_describe(evaluation_id=evaluation_id),
"Placement et émargement des étudiants ",
+ render_template("scodoc/forms/placement.html", form=form),
]
- #
- 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é
- """
- """
- )
-
- tf = TrivialFormulator(
- request.base_url,
- REQUEST.form,
- descr,
- cancelbutton="Annuler",
- submitbuttonattributes=submitbuttonattributes,
- submitlabel="OK",
- formid="gr",
- )
- if tf[0] == 0:
- # H.append( """
- #
Choix du groupe et de la localisation
- # """)
- H.append("""
""")
- return "\n".join(H) + "\n" + tf[1] + "\n
"
- elif tf[0] == -1:
- return flask.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=" + 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 flask.redirect(scu.NotesURL() + "/do_placement?" + query)
- else:
- raise ValueError(
- "invalid placement_method (%s)" % tf[2]["placement_method"]
- )
+ footer = html_sco_header.sco_footer()
+ return "\n".join(htmls) + "
" + footer
-def do_placement(REQUEST):
- """
- Choisi le placement
- """
- authuser = REQUEST.AUTHENTICATED_USER
- authusername = str(authuser)
- try:
- evaluation_id = int(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({"evaluation_id": evaluation_id})[0]
+class PlacementRunner:
+ """Execution de l'action définie par le formulaire"""
- # Check access
- # (admin, respformation, and responsable_id)
- if not sco_permissions_check.can_edit_notes(authuser, E["moduleimpl_id"]):
- return (
- "
Génération du placement impossible pour %s " % authusername
- + """
(vérifiez que le semestre n'est pas verrouillé et que vous
- avez l'autorisation d'effectuer cette opération)
-
Continuer
- """
- % 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(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(
- evaluation_id, groups, getallstudents=getallstudents, include_dems=True
- )
- if not etudids:
- return "
Aucun groupe sélectionné !
"
-
- M = sco_moduleimpl.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
- Mod = sco_edit_module.do_module_list(args={"module_id": M["module_id"]})[0]
- sem = sco_formsemestre.get_formsemestre(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]
- # infos inscription
- inscr = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
- {"etudid": etudid, "formsemestre_id": M["formsemestre_id"]}
+ def __init__(self, form):
+ """Calcul et génération du fichier sur la base des données du formulaire"""
+ self.evaluation_id = form["evaluation_id"].data
+ self.etiquetage = form["etiquetage"].data
+ self.surveillants = form["surveillants"].data
+ self.batiment = form["batiment"].data
+ self.salle = form["salle"].data
+ self.nb_rangs = form["nb_rangs"].data
+ self.file_format = form["file_format"].data
+ self.groups_ids = form["groups"].data
+ self.eval_data = sco_evaluations.do_evaluation_list(
+ {"evaluation_id": self.evaluation_id}
)[0]
- if inscr["etat"] != "D":
- nom = ident["nom"].upper()
- prenom = ident["prenom"].lower().capitalize()
- listetud.append((nom, prenom))
- random.shuffle(listetud)
-
- sem_preferences = sco_preferences.SemPreferences()
- space = sem_preferences.get("feuille_placement_emargement")
- maxlines = sem_preferences.get("feuille_placement_positions")
-
- if placement_method == "xls":
- filename = f"placement_{evalname}_{gr_title_filename}{scu.XLSX_SUFFIX}"
- xls = Excel_feuille_placement(
- E, desceval, listetud, columns, space, maxlines, building, room, numbering
+ self.groups = sco_groups.listgroups(self.groups_ids)
+ self.gr_title_filename = sco_groups.listgroups_filename(self.groups)
+ # gr_title = sco_groups.listgroups_abbrev(d['groups'])
+ self.current_user = current_user
+ self.moduleimpl_id = self.eval_data["moduleimpl_id"]
+ self.moduleimpl_data = sco_moduleimpl.do_moduleimpl_list(
+ moduleimpl_id=self.moduleimpl_id
+ )[0]
+ self.module_data = sco_edit_module.do_module_list(
+ args={"module_id": self.moduleimpl_data["module_id"]}
+ )[0]
+ self.sem = sco_formsemestre.get_formsemestre(
+ self.moduleimpl_data["formsemestre_id"]
)
- return sco_excel.send_excel_file(REQUEST, xls, filename)
- else:
- nbcolumns = int(columns)
+ self.evalname = "%s-%s" % (
+ self.module_data["code"],
+ ndb.DateDMYtoISO(self.eval_data["jour"]),
+ )
+ if self.eval_data["description"]:
+ self.evaltitre = self.eval_data["description"]
+ else:
+ self.evaltitre = "évaluation du %s" % self.eval_data["jour"]
+ self.desceval = [ # une liste de chaines: description de l'evaluation
+ "%s" % self.sem["titreannee"],
+ "Module : %s - %s" % (self.module_data["code"], self.module_data["abbrev"]),
+ "Surveillants : %s" % self.surveillants,
+ "Batiment : %(batiment)s - Salle : %(salle)s" % self.__dict__,
+ "Controle : %s (coef. %g)"
+ % (self.evaltitre, self.eval_data["coefficient"]),
+ ]
+ self.styles = None
+ self.plan = None
+ self.listetud = None
- pdf_title = "%s
" % sem["titreannee"]
- pdf_title += "Module : %s - %s
" % (Mod["code"], Mod["abbrev"])
- pdf_title += "Surveillants : %s
" % teachers
- pdf_title += "Batiment : %s - Salle : %s
" % (building, room)
- pdf_title += "Controle : %s (coef. %g)
" % (evaltitre, E["coefficient"])
- pdf_title += "Date : %s - Horaire : %s à %s" % (
- E["jour"],
- E["heure_debut"],
- E["heure_fin"],
+ def check_placement(self):
+ """Vérifie que l'utilisateur courant a le droit d'édition sur les notes"""
+ # Check access (admin, respformation, and responsable_id)
+ return sco_permissions_check.can_edit_notes(
+ self.current_user, self.moduleimpl_id
)
- filename = "placement_%s_%s.pdf" % (evalname, gr_title_filename)
+ def exec_placement(self):
+ """Excéute l'action liée au formulaire"""
+ self._repartition()
+ if self.file_format == "xls":
+ return self._production_xls()
+ return self._production_pdf()
+
+ def _repartition(self):
+ """
+ Calcule le placement. retourne une liste de couples ((nom, prenom), position)
+ """
+ # Construit liste des etudiants et les réparti
+ self.groups = sco_groups.listgroups(self.groups_ids)
+ self.listetud = self._build_listetud()
+ self.plan = self._affectation_places()
+
+ def _build_listetud(self):
+ get_all_students = None in [
+ g["group_name"] for g in self.groups
+ ] # tous les etudiants
+ etudids = sco_groups.do_evaluation_listeetuds_groups(
+ self.evaluation_id,
+ self.groups,
+ getallstudents=get_all_students,
+ include_dems=True,
+ )
+ 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(ndb.GetDBConnexion(), {"etudid": etudid})[0]
+ # infos inscription
+ inscr = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
+ {
+ "etudid": etudid,
+ "formsemestre_id": self.moduleimpl_data["formsemestre_id"],
+ }
+ )[0]
+ if inscr["etat"] != "D":
+ nom = ident["nom"].upper()
+ prenom = ident["prenom"].lower().capitalize()
+ etudid = ident["etudid"]
+ listetud.append((nom, prenom, etudid))
+ random.shuffle(listetud)
+ return listetud
+
+ def _affectation_places(self):
+ plan = []
+ if self.etiquetage == SEQ:
+ distributeur = _DistributeurContinu()
+ else:
+ distributeur = _Distributeur2D(self.nb_rangs)
+ for etud in self.listetud:
+ plan.append((etud, distributeur.suivant()))
+ return plan
+
+ def _production_xls(self):
+ filename = "placement_%s_%s" % (self.evalname, self.gr_title_filename)
+ xls = self._excel_feuille_placement()
+ return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
+
+ def _production_pdf(self):
+ pdf_title = "
".join(self.desceval)
+ pdf_title += (
+ "\nDate : %(jour)s - Horaire : %(heure_debut)s à %(heure_fin)s"
+ % self.eval_data
+ )
+ filename = "placement_%(evalname)s_%(gr_title_filename)s" % self.__dict__
titles = {
"nom": "Nom",
"prenom": "Prenom",
@@ -343,41 +339,24 @@ def do_placement(REQUEST):
"ligne": "Ligne",
"place": "Place",
}
- if numbering == "coordinate":
+ if self.etiquetage == COORD:
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":
+ for etud in sorted(self.plan, key=lambda item: item[0][0]): # sort by name
+ if self.etiquetage == COORD:
rows.append(
{
- "nom": etudid[0],
- "prenom": etudid[1],
- "colonne": etudid[2],
- "ligne": etudid[3],
+ "nom": etud[0][0],
+ "prenom": etud[0][1],
+ "colonne": etud[1][0],
+ "ligne": etud[1][1],
}
)
else:
- rows.append({"nom": etudid[0], "prenom": etudid[1], "place": etudid[2]})
-
+ rows.append({"nom": etud[0][0], "prenom": etud[0][1], "place": etud[1]})
tab = GenTable(
titles=titles,
columns_ids=columns_ids,
@@ -388,416 +367,258 @@ def do_placement(REQUEST):
+ "",
pdf_title=pdf_title,
# pdf_shorttitle = '',
- preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
- # html_generate_cells=False # la derniere ligne (moyennes) est incomplete
+ preferences=sco_preferences.SemPreferences(
+ self.moduleimpl_data["formsemestre_id"]
+ ),
)
- t = tab.make_page(format="pdf", with_html_headers=False)
- return t
+ return tab.make_page(format="pdf", with_html_headers=False)
-
-def placement_eval_selectetuds(evaluation_id, REQUEST=None):
- """Dialogue placement etudiants: choix methode et localisation"""
- evals = sco_evaluations.do_evaluation_list({"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(page_title=page_title)]
-
- formid = "placementfile"
- if not REQUEST.form.get("%s-submitted" % formid, False):
- # not submitted, choix groupe
- r = do_placement_selectetuds(REQUEST)
- if r:
- H.append(r)
-
- H.append(
- """
Explications
-
-Choisir le format du fichier résultat :
-
-le format pdf consiste en un tableau précisant pour chaque étudiant la localisation de sa table;
-le format xls produit un classeur avec deux onglets
-
-le premier onglet donne une vue de la salle avec la localisation des étudiants et peut servir de feuille d'émargement;
-le second onglet est un tableau similaire à celui du fichier pdf;
-
-
-préciser les surveillants et la localisation (bâtiment et salle) et indiquer le nombre de colonnes;
-deux types de placements sont possibles :
-
-continue suppose que les tables ont toutes un numéro unique;
-coordonnées localise chaque table via un numéro de colonne et un numéro de ligne (ou rangée).
-
-
-"""
- )
- H.append(html_sco_header.sco_footer())
- 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, "Feuille placement etudiants éditée le %s" % dt, style_titres)
- ws1.write(li, 0, "Feuille placement etudiants éditée le %s" % dt, style_titres)
- for desceval in description:
- if line % 2 == 0:
- li += 2
+ def _one_header(self, worksheet):
+ cells = [
+ worksheet.make_cell("Nom", self.styles["2bi"]),
+ worksheet.make_cell("Prénom", self.styles["2bi"]),
+ ]
+ if self.etiquetage == COORD:
+ cells.append(worksheet.make_cell("Colonne", self.styles["2bi"]))
+ cells.append(worksheet.make_cell("Ligne", self.styles["2bi"]))
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,
- "Date : %s - Horaire : %s à %s" % (E["jour"], E["heure_debut"], E["heure_fin"]),
- style_titres,
- )
- ws1.write(
- li,
- 0,
- "Date : %s - Horaire : %s à %s" % (E["jour"], E["heure_debut"], E["heure_fin"]),
- style_titres,
- )
- li += 1
+ cells.append(worksheet.make_cell("Place", self.styles["2bi"]))
+ return cells
- # entetes colonnes - feuille0
- for col in range(nbcolumns):
- ws0.write(li, col + 1, "colonne %s" % (col + 1), style2b)
- # entetes colonnes - feuille1
- if numbering == "coordinate":
- ws1.write(li, 0, "Nom", style2bi)
- ws1.write(li, 1, "Prénom", style2bi)
- ws1.write(li, 2, "Colonne", style2bi)
- ws1.write(li, 3, "Ligne", style2bi)
+ def _headers(self, worksheet, nb_listes):
+ cells = []
+ for _ in range(nb_listes):
+ cells += self._one_header(worksheet)
+ cells.append(worksheet.make_cell(""))
+ worksheet.append_row(cells)
- ws1.write(li, 5, "Nom", style2bi)
- ws1.write(li, 6, "Prénom", style2bi)
- ws1.write(li, 7, "Colonne", style2bi)
- ws1.write(li, 8, "Ligne", style2bi)
- else:
- ws1.write(li, 0, "Nom", style2bi)
- ws1.write(li, 1, "Prénom", style2bi)
- ws1.write(li, 2, "Place", style2bi)
+ def _make_styles(self, ws0, ws1):
+ # polices
+ font0 = Font(name="Calibri", bold=True, size=12)
+ font1b = copy(font0)
+ font1b.size = 9
+ font1i = Font(name="Arial", italic=True, size=10)
+ font1o = Font(name="Arial", outline=True, size=10)
+ font2bi = Font(name="Arial", bold=True, italic=True, size=8)
+ font2 = Font(name="Arial", size=10)
- ws1.write(li, 4, "Nom", style2bi)
- ws1.write(li, 5, "Prénom", style2bi)
- ws1.write(li, 6, "Place", style2bi)
+ # bordures
+ side_double = Side(border_style="double", color=COLORS.BLACK.value)
+ side_thin = Side(border_style="thin", color=COLORS.BLACK.value)
- # 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))
+ # bordures
+ border1t = Border(left=side_double, top=side_double, right=side_double)
+ border1bb = Border(left=side_double, bottom=side_double, right=side_double)
+ border1bm = Border(left=side_double, right=side_double)
+ border1m = Border(left=side_double, bottom=side_thin, right=side_double)
+ border2m = Border(top=side_thin, bottom=side_thin)
+ border2r = Border(top=side_thin, bottom=side_thin, right=side_thin)
+ border2l = Border(left=side_thin, top=side_thin, bottom=side_thin)
+ border2b = Border(
+ left=side_thin, top=side_thin, bottom=side_thin, right=side_thin
+ )
- if col == nbcolumns:
- placementetud.append(linetud)
- linetud = []
- col = 0
- line += 1
- col += 1
- if len(linetud) > 0:
- placementetud.append(linetud)
+ # alignements
+ align_center_center = Alignment(horizontal="center", vertical="center")
+ align_right_bottom = Alignment(horizontal="right", vertical="bottom")
+ align_left_center = Alignment(horizontal="left", vertical="center")
+ align_right_center = Alignment(horizontal="right", vertical="center")
- # 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)
+ # patterns
+ pattern = PatternFill(
+ fill_type="solid", fgColor=sco_excel.COLORS.LIGHT_YELLOW.value
+ )
+
+ # styles
+ self.styles = {
+ "titres": sco_excel.excel_make_style(font_name="Arial", bold=True, size=12),
+ "1t": ws0.excel_make_composite_style(
+ font=font0, alignment=align_center_center, border=border1t
+ ),
+ "1m": ws0.excel_make_composite_style(
+ font=font1b, alignment=align_center_center, border=border1m
+ ),
+ "1bm": ws0.excel_make_composite_style(
+ font=font1b, alignment=align_center_center, border=border1bm
+ ),
+ "1bb": ws0.excel_make_composite_style(
+ font=font1o, alignment=align_right_bottom, border=border1bb
+ ),
+ "2b": ws1.excel_make_composite_style(
+ font=font1i, alignment=align_center_center, border=border2b
+ ),
+ "2bi": ws1.excel_make_composite_style(
+ font=font2bi,
+ alignment=align_center_center,
+ border=border2b,
+ fill=pattern,
+ ),
+ "2l": ws1.excel_make_composite_style(
+ font=font2, alignment=align_left_center, border=border2l
+ ),
+ "2m1": ws1.excel_make_composite_style(
+ font=font2, alignment=align_left_center, border=border2m
+ ),
+ "2m2": ws1.excel_make_composite_style(
+ font=font2, alignment=align_right_center, border=border2m
+ ),
+ "2r": ws1.excel_make_composite_style(
+ font=font2, alignment=align_right_center, border=border2r
+ ),
+ }
+
+ def _titres(self, worksheet):
+ datetime = time.strftime("%d/%m/%Y a %Hh%M")
+ worksheet.append_single_cell_row(
+ "Feuille placement etudiants éditée le %s" % datetime, self.styles["titres"]
+ )
+ for line, desceval in enumerate(self.desceval):
+ if line in [1, 4, 7]:
+ worksheet.append_blank_row()
+ worksheet.append_single_cell_row(desceval, self.styles["titres"])
+ worksheet.append_single_cell_row(
+ "Date : %(jour)s - Horaire : %(heure_debut)s à %(heure_fin)s"
+ % self.eval_data,
+ self.styles["titres"],
+ )
+
+ def _feuille0(self, ws0, space):
+ self._titres(ws0)
+ # entetes colonnes - feuille0
+ cells = [ws0.make_cell()]
+ for col in range(self.nb_rangs):
+ cells.append(ws0.make_cell("colonne %s" % (col + 1), self.styles["2b"]))
+ ws0.append_row(cells)
+
+ # etudiants - feuille0
+ place = 1
+ col = 0
+ rang = 1
+ # Chaque rang est affiché sur 3 lignes xlsx (notées A, B, C)
+ # ligne A: le nom, ligne B: le prénom, ligne C: un espace ou la place
+ cells_a = [ws0.make_cell(rang, self.styles["2b"])]
+ cells_b = [ws0.make_cell("", self.styles["2b"])]
+ cells_c = [ws0.make_cell("", self.styles["2b"])]
+ row = 13 # première ligne de signature
+ for linetud in self.plan:
+ cells_a.append(ws0.make_cell(linetud[0][0], self.styles["1t"])) # nom
+ cells_b.append(ws0.make_cell(linetud[0][1], self.styles["1m"])) # prenom
+ if self.etiquetage == COORD:
+ cell_c = ws0.make_cell("", self.styles["1bb"])
else:
- ws0.write(
- li0 + 2, col, "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
+ cell_c = ws0.make_cell("place %s" % place, self.styles["1bb"])
+ place = place + 1
+ cells_c.append(cell_c)
+ ws0.set_row_dimension_height(row, space / 25)
+ row += 3
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
+ if col == self.nb_rangs: # On a fini la rangée courante
+ ws0.append_row(cells_a) # on affiche les 3 lignes construites
+ ws0.append_row(cells_b)
+ ws0.append_row(cells_c)
+ cells_a = [
+ ws0.make_cell(rang, self.styles["2b"])
+ ] # on réinitialise les 3 lignes
+ cells_b = [ws0.make_cell("", self.styles["2b"])]
+ cells_c = [ws0.make_cell("", self.styles["2b"])]
col = 0
- if numbering == "coordinate":
- ws1.write(li, 0, "Nom", style2bi)
- ws1.write(li, 1, "Prénom", style2bi)
- ws1.write(li, 2, "Colonne", style2bi)
- ws1.write(li, 3, "Ligne", style2bi)
+ rang += 1
+ # publication du rang final incomplet
+ ws0.append_row(cells_a) # Affiche des 3 lignes (dernières lignes incomplètes)
+ ws0.append_row(cells_b)
+ ws0.append_row(cells_c)
+ ws0.set_row_dimension_height(row, space / 25)
- ws1.write(li, 5, "Nom", style2bi)
- ws1.write(li, 6, "Prénom", style2bi)
- ws1.write(li, 7, "Colonne", style2bi)
- ws1.write(li, 8, "Ligne", style2bi)
- else:
- ws1.write(li, 0, "Nom", style2bi)
- ws1.write(li, 1, "Prénom", style2bi)
- ws1.write(li, 2, "Place", style2bi)
+ def _feuille1(self, worksheet, maxlines):
+ # etudiants - feuille1
+ # structuration:
+ # 1 page = maxlistes listes
+ # 1 liste = 3 ou 4 colonnes(excel) (selon numbering) et (maximum maxlines) lignes
+ maxlistes = 2 # nombre de listes par page
+ # computes excel columns widths
+ if self.etiquetage == COORD:
+ gabarit = [16, 18, 6, 6, 2]
+ else:
+ gabarit = [16, 18, 12, 2]
+ widths = []
+ for _ in range(maxlistes):
+ widths += gabarit
+ worksheet.set_column_dimension_width(value=widths)
+ nb_etu_restant = len(self.listetud)
+ self._titres(worksheet)
+ nb_listes = min(
+ maxlistes, nb_etu_restant // maxlines + 1
+ ) # nombre de colonnes dans la page
+ self._headers(worksheet, nb_listes)
+ # construction liste alphabétique
+ # Affichage
+ lines = [[] for _ in range(maxlines)]
+ lineno = 0
+ col = 0
+ for etud in sorted(self.plan, key=lambda e: e[0][0]): # tri alphabétique
+ # check for skip of list or page
+ if col > 0: # add a empty cell between lists
+ lines[lineno].append(worksheet.make_cell())
+ lines[lineno].append(worksheet.make_cell(etud[0][0], self.styles["2l"]))
+ lines[lineno].append(worksheet.make_cell(etud[0][1], self.styles["2m1"]))
+ if self.etiquetage == COORD:
+ lines[lineno].append(
+ worksheet.make_cell(etud[1][1], self.styles["2m2"])
+ )
+ lines[lineno].append(worksheet.make_cell(etud[1][0], self.styles["2r"]))
+ else:
+ lines[lineno].append(worksheet.make_cell(etud[1], self.styles["2r"]))
+ lineno = lineno + 1
+ if lineno >= maxlines: # fin de liste
+ col = col + 1
+ lineno = 0
+ if col >= maxlistes: # fin de page
+ for line_cells in lines:
+ worksheet.append_row(line_cells)
+ lines = [[] for _ in range(maxlines)]
+ col = 0
+ worksheet.append_blank_row()
+ nb_etu_restant -= maxlistes * maxlines
+ nb_listes = min(
+ maxlistes, nb_etu_restant // maxlines + 1
+ ) # nombre de colonnes dans la page
+ self._headers(worksheet, nb_listes)
+ for line_cells in lines:
+ worksheet.append_row(line_cells)
- ws1.write(li, 4, "Nom", style2bi)
- ws1.write(li, 5, "Prénom", style2bi)
- ws1.write(li, 6, "Place", style2bi)
- return wb.savetostr()
+ def _excel_feuille_placement(self):
+ """Genere feuille excel pour placement des etudiants.
+ E: evaluation (dict)
+ lines: liste de tuples
+ (etudid, nom, prenom, etat, groupe, val, explanation)
+ """
+ sem_preferences = sco_preferences.SemPreferences()
+ space = sem_preferences.get("feuille_placement_emargement")
+ maxlines = sem_preferences.get("feuille_placement_positions")
+ nb_rangs = int(self.nb_rangs)
+ column_width_ratio = (
+ 1 / 250
+ ) # changement d unités entre pyExcelerator et openpyxl
+
+ workbook = ScoExcelBook()
+
+ sheet_name_0 = "Emargement"
+ ws0 = workbook.create_sheet(sheet_name_0)
+ # ajuste largeurs colonnes (unite inconnue, empirique)
+ width = 4500 * column_width_ratio
+ if nb_rangs > 5:
+ width = 22500 * column_width_ratio // nb_rangs
+
+ ws0.set_column_dimension_width("A", 750 * column_width_ratio)
+ for col in range(nb_rangs):
+ ws0.set_column_dimension_width(
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[col + 1 : col + 2], width
+ )
+
+ sheet_name_1 = "Positions"
+ ws1 = workbook.create_sheet(sheet_name_1)
+
+ self._make_styles(ws0, ws1)
+ self._feuille0(ws0, space)
+ self._feuille1(ws1, maxlines)
+ return workbook.generate()
diff --git a/app/scodoc/sco_prepajury.py b/app/scodoc/sco_prepajury.py
index fc445aa0e1..67e1607392 100644
--- a/app/scodoc/sco_prepajury.py
+++ b/app/scodoc/sco_prepajury.py
@@ -324,5 +324,12 @@ def feuille_preparation_jury(formsemestre_id, REQUEST):
REQUEST.AUTHENTICATED_USER,
)
)
- xls = ws.generate_standalone()
- return sco_excel.send_excel_file(REQUEST, xls, f"PrepaJury{sn}{scu.XLSX_SUFFIX}")
+ xls = ws.generate()
+ return scu.send_file(
+ xls,
+ f"PrepaJury{sn}{scu.XLSX_SUFFIX}",
+ scu.XLSX_SUFFIX,
+ scu.XLSX_MIMETYPE,
+ attached=True,
+ )
+ # return sco_excel.send_excel_file(REQUEST, xls, f"PrepaJury{sn}{scu.XLSX_SUFFIX}")
diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py
index 6c72877ec4..66e9b81b21 100644
--- a/app/scodoc/sco_saisie_notes.py
+++ b/app/scodoc/sco_saisie_notes.py
@@ -829,7 +829,10 @@ def feuille_saisie_notes(evaluation_id, group_ids=[], REQUEST=None):
filename = "notes_%s_%s.xlsx" % (evalname, gr_title_filename)
xls = sco_excel.excel_feuille_saisie(E, sem["titreannee"], description, lines=L)
- return sco_excel.send_excel_file(REQUEST, xls, filename)
+ return scu.send_file(
+ xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE, attached=True
+ )
+ # return sco_excel.send_excel_file(REQUEST, xls, filename)
def has_existing_decision(M, E, etudid):
diff --git a/app/scodoc/sco_trombino.py b/app/scodoc/sco_trombino.py
index e537fb531d..bd3f19aadb 100644
--- a/app/scodoc/sco_trombino.py
+++ b/app/scodoc/sco_trombino.py
@@ -486,7 +486,10 @@ def photos_generate_excel_sample(group_ids=[], REQUEST=None):
],
extra_cols=["fichier_photo"],
)
- return sco_excel.send_excel_file(REQUEST, data, "ImportPhotos" + scu.XLSX_SUFFIX)
+ return scu.send_file(
+ data, "ImportPhotos", scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE, attached=True
+ )
+ # return sco_excel.send_excel_file(REQUEST, data, "ImportPhotos" + scu.XLSX_SUFFIX)
def photos_import_files_form(group_ids=[], REQUEST=None):
diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index ef9df0da52..a537bb96f9 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -1092,13 +1092,11 @@ h2.formsemestre, .gtrcontent h2 {
#formnotes td.tf-fieldlabel {
border-bottom: 1px dotted #fdcaca;
}
-
-/* Formulaires ScoDoc 9 */
-form.sco-form {
- margin-top: 1em;
+.wtf-field li {
+ display: inline;
}
-div.sco-submit {
- margin-top: 2em;
+.wtf-field .errors {
+ color: red ; font-weight: bold;
}
/*
.formsemestre_menubar {
diff --git a/app/templates/scodoc/forms/placement.html b/app/templates/scodoc/forms/placement.html
new file mode 100644
index 0000000000..f2c11b159f
--- /dev/null
+++ b/app/templates/scodoc/forms/placement.html
@@ -0,0 +1,77 @@
+{% import 'bootstrap/wtf.html' as wtf %}
+
+{% macro render_field(field) %}
+
+ {{ field.label }}
+ {{ field()|safe }}
+ {% if field.errors %}
+
+ {% for error in field.errors %}
+ {{ error }}
+ {% endfor %}
+
+ {% endif %}
+
+
+{% endmacro %}
+
+
+
+
diff --git a/app/views/notes.py b/app/views/notes.py
index f7c219699b..c4b052bf75 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -1645,8 +1645,8 @@ sco_publish(
"/placement_eval_selectetuds",
sco_placement.placement_eval_selectetuds,
Permission.ScoEnsView,
+ methods=["GET", "POST"],
)
-sco_publish("/do_placement", sco_placement.do_placement, Permission.ScoEnsView)
# --- Saisie des notes
sco_publish(
diff --git a/app/views/scolar.py b/app/views/scolar.py
index 2442a8b86d..24d03bc40a 100644
--- a/app/views/scolar.py
+++ b/app/views/scolar.py
@@ -1937,7 +1937,10 @@ def import_generate_excel_sample(REQUEST, with_codesemestre="1"):
data = sco_import_etuds.sco_import_generate_excel_sample(
format, with_codesemestre, exclude_cols=["photo_filename"]
)
- return sco_excel.send_excel_file(REQUEST, data, "ImportEtudiants" + scu.XLSX_SUFFIX)
+ return scu.send_file(
+ data, "ImportEtudiants", scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE, attached=True
+ )
+ # return sco_excel.send_excel_file(REQUEST, data, "ImportEtudiants" + scu.XLSX_SUFFIX)
# --- Données admission
@@ -1955,9 +1958,10 @@ def import_generate_admission_sample(REQUEST, formsemestre_id):
exclude_cols=["nationalite", "foto", "photo_filename"],
group_ids=[group["group_id"]],
)
- return sco_excel.send_excel_file(
- REQUEST, data, "AdmissionEtudiants" + scu.XLSX_SUFFIX
+ return scu.send_file(
+ data, "AdmissionEtudiants", scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE, attached=True
)
+ # return sco_excel.send_excel_file(REQUEST, data, "AdmissionEtudiants" + scu.XLSX_SUFFIX)
# --- Données admission depuis fichier excel (version nov 2016)
diff --git a/app/views/users.py b/app/views/users.py
index 00a28bb3c8..9726efd174 100644
--- a/app/views/users.py
+++ b/app/views/users.py
@@ -65,8 +65,6 @@ from app import log
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.sco_permissions_check import can_handle_passwd
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
-from app.scodoc.sco_excel import send_excel_file
-from app.scodoc.sco_import_users import generate_excel_sample
from app.views import users_bp as bp
@@ -490,9 +488,10 @@ def create_user_form(REQUEST, user_name=None, edit=0, all_roles=1):
def import_users_generate_excel_sample(REQUEST):
"une feuille excel pour importation utilisateurs"
data = sco_import_users.generate_excel_sample()
- return sco_excel.send_excel_file(
- REQUEST, data, "ImportUtilisateurs" + scu.XLSX_SUFFIX
+ return scu.send_file(
+ data, "ImportUtilisateurs", scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE, attached=True
)
+ # return sco_excel.send_excel_file(REQUEST, data, "ImportUtilisateurs" + scu.XLSX_SUFFIX)
@bp.route("/import_users_form", methods=["GET", "POST"])