formattage terminé ?

This commit is contained in:
Jean-Marie Place 2023-05-15 06:32:54 +02:00
parent cfe74da499
commit 342bff3b56
5 changed files with 549 additions and 141 deletions

View File

@ -1,26 +1,36 @@
from enum import Enum
from app.but.prepajury_xl import ScoExcelSheet, excel_make_style
from app.but.prepajury_xl import (
ScoExcelSheet,
)
from app.but.prepajury_xl_format import (
SCO_HALIGN,
SCO_VALIGN,
SCO_FONTNAME,
SCO_FONTSIZE,
FMT,
Sco_Style,
)
base_style = excel_make_style()
base_signature = (
FMT.FONT_NAME.write(SCO_FONTNAME.FONTNAME_CALIBRI)
+ FMT.FONT_SIZE.write(SCO_FONTSIZE.FONTSIZE_10)
+ FMT.ALIGNMENT_HALIGN.write(SCO_HALIGN.HALIGN_CENTER)
+ FMT.ALIGNEMENT_VALIGN.write(SCO_VALIGN.VALIGN_CENTER)
)
class BG(Enum):
NONE = 0,
BLACK = 1,
GREY = 2,
BUT1
class Cell:
def __init__(self, text: str = None, style: int = None, comment: str = None):
def __init__(
self, text: str = None, signature: int = base_signature, comment: str = None
):
self.text = text
self.style = style or base_style
self.signature = signature or base_signature
self.comment = comment
@staticmethod
def get_style():
return base_style
def format(self, FMT: FMT, value=None):
self.signature = FMT.composante.write(self.signature, value)
def make_cell(self, worksheet: ScoExcelSheet):
style = self.get_style()
cell = worksheet.make_cell(self.text or "", style=style)
cell = worksheet.make_cell(self.text or "")
style: Sco_Style = FMT.ALL.get_style(self.signature)
style.apply(cell)
return cell

View File

@ -1,5 +1,8 @@
from openpyxl.styles.colors import BLACK
from app.but.prepajury_cells import Cell, base_style
from app.but.prepajury_xl import excel_make_style
from app.but.prepajury_xl import Sco_Style
from app.but.prepajury_xl_format import SCO_COLORS
from app.models import ApcCompetence, ApcParcours
from app.scodoc.sco_excel import ScoExcelBook, ScoExcelSheet
@ -9,6 +12,23 @@ def parite(semestre_idx):
listeAnnees = ["BUT1", "BUT2", "BUT3"]
header_colors = {
"BUT1": {
"BUT": SCO_COLORS.BUT1,
"RCUE": SCO_COLORS.RCUE1,
"UE": SCO_COLORS.UE1,
},
"BUT2": {
"BUT": SCO_COLORS.BUT2,
"RCUE": SCO_COLORS.RCUE2,
"UE": SCO_COLORS.UE2,
},
"BUT3": {
"BUT": SCO_COLORS.BUT3,
"RCUE": SCO_COLORS.RCUE3,
"UE": SCO_COLORS.UE3,
},
}
class _Header:
@ -20,7 +40,7 @@ class _Header:
def setLabelOnce(self, rang: int, label: str):
self.presets[rang] = label
def setStyles(self, rangs: list[int], style):
def setStyles(self, rangs: list[int], style: Sco_Style):
for rang in rangs:
self.styles[rang] = style
@ -71,10 +91,16 @@ class NiveauDesc:
self.ue[parite(scodocUe.semestre_idx)] = scodocUe
def generate_header(self, header):
rcue_style = Sco_Style(
fromStyle=base_style,
bgcolor=header_colors[self.fromScodoc.annee]["RCUE"],
)
ue_style = Sco_Style(
fromStyle=base_style,
bgcolor=header_colors[self.fromScodoc.annee]["UE"],
)
header.setLabelOnce(1, self.fromScodoc.competence.titre)
rcue_style = excel_make_style(base_style)
ue_style = excel_make_style(base_style)
header.setStyles([1, 2, 3], rcue_style)
header.setStyles([1], rcue_style)
header.setStyles([2, 3], ue_style)
for ue in self.ue:
if ue is None:
@ -135,7 +161,9 @@ class AnneeDesc:
header.add_column()
def generate_header(self, header):
but_style = excel_make_style(base_style)
but_style = Sco_Style(
fromStyle=base_style, bgcolor=header_colors[self.codeAnnee]["BUT"]
)
header.setStyles([0], but_style)
for niveau in self.niveaux.values():
niveau.generate_header(header)

View File

@ -25,38 +25,25 @@
#
##############################################################################
from __future__ import annotations
from openpyxl.cell import WriteOnlyCell
from app.but.prepajury_xl_format import Sco_Style
""" Excel file handling
"""
import datetime
import io
import time
from enum import Enum
from tempfile import NamedTemporaryFile
import openpyxl.utils.datetime
from openpyxl.styles.numbers import FORMAT_NUMBER_00, FORMAT_GENERAL, FORMAT_DATE_DDMMYY
from openpyxl.styles.numbers import FORMAT_DATE_DDMMYY
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.worksheet.worksheet import Worksheet
from openpyxl import Workbook
import app.scodoc.sco_utils as scu
from app import log
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc import notesdb, sco_preferences
class COLORS(Enum):
BLACK = "FF000000"
WHITE = "FFFFFFFF"
RED = "FFFF0000"
BROWN = "FF993300"
PURPLE = "FF993366"
BLUE = "FF0000FF"
ORANGE = "FFFF3300"
LIGHT_YELLOW = "FFFFFF99"
# Un style est enregistré comme un dictionnaire qui précise la valeur d'un attribut dans la liste suivante:
@ -127,67 +114,6 @@ class ScoExcelBook:
return tmp.read()
def excel_make_style(
fromStyle=None,
bold=False,
italic=False,
outline=False,
color: COLORS = COLORS.BLACK,
bgcolor: COLORS = None,
halign=None,
valign=None,
number_format=None,
font_name="Arial",
size=10,
):
"""Contruit un style.
Les couleurs peuvent être spécfiées soit par une valeur de COLORS,
soit par une chaine argb (exple "FF00FF00" pour le vert)
color -- La couleur du texte
bgcolor -- la couleur de fond
halign -- alignement horizontal ("left", "right", "center")
valign -- alignement vertical ("top", "bottom", "center")
number_format -- formattage du contenu ("General", "@", ...)
font_name -- police
size -- taille de police
"""
style = {}
if fromStyle is not None:
for item in fromStyle:
style[item] = fromStyle[item]
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)
if halign or valign:
al = Alignment()
if halign:
al.horizontal = {
"left": "left",
"right": "right",
"center": "center",
}[halign]
if valign:
al.vertical = {
"top": "top",
"bottom": "bottom",
"center": "center",
}[valign]
style["alignment"] = al
if number_format is None:
style["number_format"] = FORMAT_GENERAL
else:
style["number_format"] = number_format
return style
class ScoExcelSheet:
"""Représente une feuille qui peut être indépendante ou intégrée dans un ScoExcelBook.
En application des directives de la bibliothèque sur l'écriture optimisée, l'ordre des opérations
@ -208,9 +134,7 @@ class ScoExcelSheet:
# 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 = adjust_sheetname(sheet_name)
if default_style is None:
default_style = excel_make_style()
self.default_style = default_style
self.default_style = default_style or Sco_Style()
if wb is None:
self.wb = Workbook()
self.ws = self.wb.active
@ -223,29 +147,6 @@ class ScoExcelSheet:
self.column_dimensions = {}
self.row_dimensions = {}
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
@ -287,7 +188,7 @@ class ScoExcelSheet:
"""
self.ws.row_dimensions[cle].hidden = value
def make_cell(self, value: any = None, style=None, comment=None):
def make_cell(self, value: any = None, style: Sco_Style = None, comment=None):
"""Construit une cellule.
value -- contenu de la cellule (texte, numérique, booléen ou date)
style -- style par défaut (dictionnaire cf. excel_make_style) de la feuille si non spécifié
@ -313,17 +214,15 @@ class ScoExcelSheet:
if "font" in style:
cell.font = style["font"]
if "alignment" in style:
cell.alignment = style["alignment"]
cell.alignment = style["alignment"].get_openxl()
if "border" in style:
cell.border = style["border"]
if "fill" in style:
cell.fill = style["fill"]
cell.border = style["border"].get_openxl()
if "number_format" in style:
cell.number_format = style["number_format"]
if "fill" in style:
cell.fill = style["fill"]
if "alignment" in style:
cell.alignment = style["alignment"]
cell.alignment = style["alignment"].get_openxl()
if not comment is None:
cell.comment = Comment(comment, "scodoc")
lines = comment.splitlines()

View File

@ -0,0 +1,476 @@
import abc
from enum import Enum
import openpyxl.styles
from openpyxl.styles import Side, Border, Font, PatternFill
from openpyxl.styles.numbers import FORMAT_GENERAL, FORMAT_NUMBER_00
class SCO_COLORS(Enum):
def __new__(cls, value, argb):
obj = object.__new__(cls)
obj._value_ = value
obj.argb = argb
return obj
BLACK = (1, "FF000000")
WHITE = (2, "FFFFFFFF")
RED = (3, "FFFF0000")
BROWN = (4, "FF993300")
PURPLE = (5, "FF993366")
BLUE = (6, "FF0000FF")
ORANGE = (7, "FFFF3300")
LIGHT_YELLOW = (8, "FFFFFF99")
BUT1 = (9, "FF95B3D7")
RCUE1 = (10, "FFB8CCE4")
UE1 = (11, "FFDCE6F1")
BUT2 = (12, "FFC4D79B")
RCUE2 = (13, "FFD8E4BC")
UE2 = (14, "FFEBF1DE")
BUT3 = (15, "FFFABF8F")
RCUE3 = (16, "FFFCD5B4")
UE3 = (17, "FFFDE9D9")
class SCO_BORDERTHICKNESS(Enum):
def __new__(cls, value, width):
obj = object.__new__(cls)
obj._value_ = value
obj.width = width
return obj
BORDER_NONE = (1, None)
BORDER_HAIR = (2, "hair")
BORDER_THIN = (3, "thin")
BORDER_MEDIUM = (4, "medium")
BORDER_THICK = (5, "thick")
class SCO_FONTNAME(Enum):
def __new__(cls, value, fontname):
obj = object.__new__(cls)
obj._value_ = value
obj.fontname = fontname
return obj
FONTNAME_ARIAL = (1, "Arial")
FONTNAME_COURIER = (2, "Courier New")
FONTNAME_CALIBRI = (3, "Calibri")
FONTNAME_TIMES = (4, "Times New Roman")
class SCO_FONTSIZE(Enum):
def __new__(cls, value, fontsize):
obj = object.__new__(cls)
obj._value_ = value
obj.fontsize = fontsize
return obj
FONTSIZE_9 = (1, 9)
FONTSIZE_10 = (1, 10)
FONTSIZE_11 = (1, 11)
FONTSIZE_13 = (1, 13)
FONTSIZE_16 = (1, 16)
class SCO_NUMBER_FORMAT(Enum):
def __new__(cls, value, format):
obj = object.__new__(cls)
obj._value_ = value
obj.format = format
return obj
NUMBER_GENERAL = (0, FORMAT_GENERAL)
NUMBER_00 = (1, FORMAT_NUMBER_00)
class SCO_HALIGN(Enum):
def __new__(cls, value, position):
obj = object.__new__(cls)
obj._value_ = value
obj.position = position
return obj
HALIGN_CENTER = (1, "center")
HALIGN_RIGHT = (2, "right")
HALIGN_LEFT = (3, "left")
class SCO_VALIGN(Enum):
def __new__(cls, value, position):
obj = object.__new__(cls)
obj._value_ = value
obj.position = position
return obj
VALIGN_TOP = (1, "top")
VALIGN_BOTTOM = (2, "bottom")
VALIGN_CENTER = (3, "center")
free = 0
class Composante:
def __init__(self, base=None, width: int = 1):
global free
self.cache = {}
if base is None:
self.base = free
free += width
else:
self.base = base
self.width = width
self.end = self.base + self.width
self.mask = ((1 << width) - 1) << self.base
def read(self, signature: int) -> int:
return (signature & self.mask) >> self.base
def clear(self, signature: int) -> int:
return signature & ~self.mask
def write(self, index, signature=0) -> int:
return self.clear(signature) + index << self.base
def lookup_or_cache(self, signature: int):
value = self.read(signature)
if not value in self.cache:
self.cache[signature] = self.build(value)
return self.cache[value]
@abc.abstractmethod
def build(self, value: int):
pass
class Composante_boolean(Composante):
def __init__(self):
super().__init__(width=1)
def build(self, signature):
value = self.read(signature)
return value == 1
class Composante_number_format(Composante):
def __init__(self):
super().__init__(width=2)
def build(self, signature: int):
value = self.read(signature)
return SCO_NUMBER_FORMAT(value) or None
class Composante_Colors(Composante):
def __init__(self):
super().__init__(width=5)
def build(self, signature: int):
value = self.read(signature)
return SCO_COLORS(value) or SCO_COLORS.BLACK
class Composante_borderThickness(Composante):
def __init__(self):
super().__init__(width=2)
def build(self, signature: int):
value = self.read(signature)
return SCO_BORDERTHICKNESS(value) or None
class Composante_fontname(Composante):
def __init__(self):
super().__init__(width=3)
def build(self, signature: int):
value = self.read(signature)
return SCO_FONTNAME(value) or None
class Composante_fontsize(Composante):
def __init__(self):
super().__init__(width=3)
def build(self, signature: int):
value = self.read(signature)
return SCO_FONTSIZE(value) or None
class Composante_halign(Composante):
def __init__(self):
super().__init__(width=2)
def build(self, signature: int):
value = self.read(signature)
return SCO_HALIGN(value) or None
class Composante_valign(Composante):
def __init__(self):
super().__init__(width=2)
def build(self, signature: int):
value = self.read(signature)
return SCO_VALIGN(signature) or None
class Sco_BorderSide:
def __init__(
self,
thickness: SCO_BORDERTHICKNESS = None,
color: SCO_COLORS = SCO_COLORS.WHITE,
):
self.thickness = thickness
self.color: SCO_COLORS = color
def get_openxl(self):
side: Side = Side(border_style=self.thickness.width, color=self.color.argb)
return side
class Sco_Borders:
def __init__(
self,
left: Sco_BorderSide = None,
right: Sco_BorderSide = None,
top: Sco_BorderSide = None,
bottom: Sco_BorderSide = None,
):
self.left = left
self.right = right
self.top = top
self.bottom = bottom
def get_openxl(self):
border: Border = Border(
left=self.left.get_openxl(),
right=self.right.get_openxl(),
top=self.top.get_openxl(),
bottom=self.bottom.get_openxl(),
)
class Sco_Alignment:
def __init__(
self,
halign: SCO_HALIGN = None,
valign: SCO_VALIGN = None,
):
self.halign = halign
self.valign = valign
def get_openxl(self):
al = openpyxl.styles.Alignment()
al.horizontal = self.halign.position
al.vertical = self.valign.position
return al
class Sco_Font:
def __init__(
self,
name: str = None,
fontsize: int = None,
bold: bool = None,
italic: bool = None,
outline: bool = None,
color: "SCO_COLORS" = None,
):
self.name = name
self.bold = bold
self.italic = italic
self.outline = outline
self.color = color
self.fontsize = fontsize
class Sco_Style:
from openpyxl.cell import WriteOnlyCell, Cell
def __init__(
self,
font: Sco_Font = None,
bgcolor: SCO_COLORS = None,
alignment: Sco_Alignment = None,
borders: Sco_Borders = None,
number_format: SCO_NUMBER_FORMAT = None,
):
self.font = font
self.bgcolor = bgcolor
self.alignment = alignment
self.borders = borders
self.number_format = number_format
def apply(self, cell: Cell):
if self.font:
cell.font = self.font.get_openxl()
if self.bgcolor:
cell.fill = PatternFill(fill_type="solid", fgColor=self.bgcolor.argb)
if self.alignment:
cell.alignment = self.alignment.get_openxl()
if self.borders:
cell.border = self.borders.get_openxl()
if self.number_format:
cell.number_format = self.number_format.get_openxl()
class Composante_group(Composante):
def __init__(self, composantes: list[Composante]):
self.composantes = composantes
mini = min([comp.base for comp in composantes])
maxi = max([comp.end for comp in composantes])
width = sum([comp.width for comp in composantes])
if not width == (maxi - mini):
raise Exception("Composante group non complete ou non connexe")
super().__init__(base=mini, width=width)
class Composante_font(Composante_group):
def __init__(
self,
name: Composante_fontname,
fontsize: Composante_fontsize,
color: Composante_Colors,
bold: Composante_boolean,
italic: Composante_boolean,
outline: Composante_boolean,
):
super().__init__([name, fontsize, color, bold, italic, outline])
self.name = name
self.fontsize = fontsize
self.color = color
self.bold = bold
self.italic = italic
self.outline = outline
def build(self, signature):
return Sco_Font(
name=self.name.lookup_or_cache(signature),
fontsize=self.fontsize.lookup_or_cache(signature),
color=self.color.lookup_or_cache(signature),
bold=self.bold.lookup_or_cache(signature),
italic=self.italic.lookup_or_cache(signature),
outline=self.outline.lookup_or_cache(signature),
)
class Composante_border(Composante_group):
def __init__(self, thick: Composante_borderThickness, color: Composante_Colors):
super().__init__([thick, color])
self.thick = thick
self.color = color
def build(self, signature: int):
return Sco_BorderSide(
thickness=self.thick.lookup_or_cache(signature),
color=self.color.lookup_or_cache(signature),
)
class Composante_borders(Composante_group):
def __init__(
self,
left: Composante_border,
right: Composante_border,
top: Composante_border,
bottom: Composante_border,
):
super().__init__([left, right, top, bottom])
self.left = left
self.right = right
self.top = top
self.bottom = bottom
def build(self, signature: int):
return Sco_Borders(
left=self.left.lookup_or_cache(signature),
right=self.right.lookup_or_cache(signature),
top=self.top.lookup_or_cache(signature),
bottom=self.bottom.lookup_or_cache(signature),
)
class Composante_alignment(Composante_group):
def __init__(self, halign: Composante_halign, valign: Composante_valign):
super().__init__([halign, valign])
self.halign = halign
self.valign = valign
def build(self, signature: int):
return Sco_Alignment(
halign=self.halign.lookup_or_cache(signature),
valign=self.valign.lookup_or_cache(signature),
)
# ALL = Composante_all(FONT, BGCOLOR, BORDERS, ALIGNMENT, NUMBER_FORMAT)
class Composante_all(Composante_group):
def __init__(
self,
font: Composante_font,
bgcolor: Composante_Colors,
borders: Composante_borders,
alignment: Composante_alignment,
number_format: Composante_number_format,
):
super().__init__([font, bgcolor, borders, alignment, number_format])
self.font = font
self.bgcolor = bgcolor
self.borders = borders
self.alignment = alignment
self.number_format = number_format
def build(self, signature: int):
return Sco_Style(
bgcolor=self.bgcolor.lookup_or_cache(signature),
font=self.font.lookup_or_cache(signature),
borders=self.borders.lookup_or_cache(signature),
alignment=self.alignment.lookup_or_cache(signature),
number_format=self.number_format.lookup_or_cache(signature),
)
def get_style(self, signature: int):
return self.lookup_or_cache(signature)
class FMT(Enum):
def __init__(self, composante: Composante):
self.composante = composante
def write(self, value, signature=0) -> int:
return self.composante.write(value, signature)
def get_style(self, signature: int):
return self.composante.lookup_or_cache(signature)
FONT_NAME = Composante_fontname()
FONT_SIZE = Composante_fontsize()
FONT_COLOR = Composante_Colors()
FONT_BOLD = Composante_boolean()
FONT_ITALIC = Composante_boolean()
FONT_OUTLINE = Composante_boolean()
BORDER_LEFT_STYLE = Composante_borderThickness()
BORDER_LEFT_COLOR = Composante_Colors()
BORDER_RIGHT_STYLE = Composante_borderThickness()
BORDER_RIGHT_COLOR = Composante_Colors()
BORDER_TOP_STYLE = Composante_borderThickness()
BORDER_TOP_COLOR = Composante_Colors()
BORDER_BOTTOM_STYLE = Composante_borderThickness()
BORDER_BOTTOM_COLOR = Composante_Colors()
BGCOLOR = Composante_Colors()
ALIGNMENT_HALIGN = Composante_halign()
ALIGNEMENT_VALIGN = Composante_valign()
NUMBER_FORMAT = Composante_number_format()
FONT = Composante_font(
FONT_NAME, FONT_SIZE, FONT_COLOR, FONT_BOLD, FONT_ITALIC, FONT_OUTLINE
)
BORDER_LEFT = Composante_border(BORDER_LEFT_STYLE, BORDER_LEFT_COLOR)
BORDER_RIGHT = Composante_border(BORDER_RIGHT_STYLE, BORDER_RIGHT_COLOR)
BORDER_TOP = Composante_border(BORDER_TOP_STYLE, BORDER_TOP_COLOR)
BORDER_BOTTOM = Composante_border(BORDER_BOTTOM_STYLE, BORDER_BOTTOM_COLOR)
BORDERS = Composante_borders(BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM)
ALIGNMENT = Composante_alignment(ALIGNMENT_HALIGN, ALIGNMENT_HALIGN)
ALL = Composante_all(FONT, BGCOLOR, BORDERS, ALIGNMENT, NUMBER_FORMAT)

View File

@ -53,15 +53,10 @@ import json
from pandas import read_csv
from setup_test_api import (
API_PASSWORD,
API_URL,
API_USER,
APIError,
CHECK_CERTIFICATE,
get_auth_headers,
GET,
POST_JSON,
SCODOC_URL,
)
DATA_DIR = "/tmp/samples/"