[WIP] add styles

This commit is contained in:
Julien Aldon
2026-03-03 17:58:33 +01:00
125 changed files with 5762 additions and 622 deletions

View File

@@ -1,21 +1,28 @@
import html
import io
import pathlib
import jinja2
import src.models as models
import html
import odfdo
# from odfdo import Cell, Document, Row, Style, Table
from odfdo.element import Element
from src import models
from weasyprint import HTML
import io
def generate_html_contract(
contract: models.Contract,
cheques: list[dict],
occasionals: list[dict],
reccurents: list[dict],
recurrent_price: float,
total_price: float
):
template_dir = "./src/contracts/templates"
recurrent_price: float | None = None,
total_price: float | None = None
):
template_dir = pathlib.Path("./src/contracts/templates").resolve()
template_loader = jinja2.FileSystemLoader(searchpath=template_dir)
template_env = jinja2.Environment(loader=template_loader, autoescape=jinja2.select_autoescape(["html", "xml"]))
template_env = jinja2.Environment(
loader=template_loader, autoescape=jinja2.select_autoescape(["html", "xml"]))
template_file = "layout.html"
template = template_env.get_template(template_file)
output_text = template.render(
@@ -26,95 +33,212 @@ def generate_html_contract(
referer_email=contract.form.referer.email,
productor_name=contract.form.productor.name,
productor_address=contract.form.productor.address,
payment_methods_map={"cheque": "Ordre du chèque", "transfer": "virements"},
payment_methods_map={
"cheque": "Ordre du chèque",
"transfer": "virements"},
productor_payment_methods=contract.form.productor.payment_methods,
member_name=f'{html.escape(contract.firstname)} {html.escape(contract.lastname)}',
member_email=html.escape(contract.email),
member_phone=html.escape(contract.phone),
member_name=f'{
html.escape(
contract.firstname)} {
html.escape(
contract.lastname)}',
member_email=html.escape(
contract.email),
member_phone=html.escape(
contract.phone),
contract_start_date=contract.form.start,
contract_end_date=contract.form.end,
occasionals=occasionals,
recurrents=reccurents,
recurrent_price=recurrent_price,
total_price=total_price,
contract_payment_method={"cheque": "chèque", "transfer": "virements"}[contract.payment_method],
cheques=cheques
)
options = {
'page-size': 'Letter',
'margin-top': '0.5in',
'margin-right': '0.5in',
'margin-bottom': '0.5in',
'margin-left': '0.5in',
'encoding': "UTF-8",
'print-media-type': True,
"disable-javascript": True,
"disable-external-links": True,
'enable-local-file-access': False,
"disable-local-file-access": True,
"no-images": True,
}
contract_payment_method={
"cheque": "chèque",
"transfer": "virements"}[
contract.payment_method],
cheques=cheques)
return HTML(
string=output_text,
base_url=template_dir
base_url=template_dir,
).write_pdf()
def flatten(xss):
return [x for xs in xss for x in xs]
from odfdo import Document, Table, Row, Cell
from odfdo.element import Element
def create_column_style_width(size: str) -> odfdo.Style:
"""Create a table columm style for a given width.
Paramenters:
size(str): size of the style (format <number><unit>) unit can be in, cm... see odfdo documentation.
Returns:
odfdo.Style with the correct column-width attribute.
"""
return odfdo.Element.from_tag(
'<style:style style:name="product-table.A" style:family="table-column">'
f'<style:table-column-properties style:column-width="{size}"/>'
'</style:style>'
)
def create_row_style_height(size: str) -> odfdo.Style:
"""Create a table height style for a given height.
Paramenters:
size(str): size of the style (format <number><unit>) unit can be in, cm... see odfdo documentation.
Returns:
odfdo.Style with the correct column-height attribute.
"""
return odfdo.Element.from_tag(
'<style:style style:name="product-table.A" style:family="table-row">'
f'<style:table-row-properties style:row-height="{size}"/>'
'</style:style>'
)
def create_center_cell_style(name: str = "centered-cell") -> odfdo.Style:
return odfdo.Element.from_tag(
f'<style:style style:name="{name}" style:family="table-cell">'
'<style:table-cell-properties style:vertical-align="middle" fo:wrap-option="wrap"/>'
'<style:paragraph-properties fo:text-align="center"/>'
'</style:style>'
)
def create_cell_style_with_font(name: str = "font", font_size="14pt", bold: bool = False) -> odfdo.Style:
return odfdo.Element.from_tag(
f'<style:style style:name="{name}" style:family="table-cell" '
f'xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0">'
'<style:table-cell-properties style:vertical-align="middle" fo:wrap-option="wrap"/>'
f'<style:paragraph-properties fo:text-align="center" fo:font-size="{font_size}" '
f'{"fo:font-weight=\"bold\"" if bold else ""}/>'
'</style:style>'
)
def apply_center_cell_style(document: odfdo.Document, row: odfdo.Row):
style = document.insert_style(
create_center_cell_style()
)
for cell in row.get_cells():
cell.style = style
def apply_column_height_style(document: odfdo.Document, row: odfdo.Row, height: str):
style = document.insert_style(
style=create_row_style_height(height), name=height, automatic=True
)
row.style = style
def apply_font_style(document: odfdo.Document, table: odfdo.Table, size: str = "14pt"):
style_header = document.insert_style(
style=create_cell_style_with_font(
'header_font', font_size=size, bold=True
)
)
style_body = document.insert_style(
style=create_cell_style_with_font(
'body_font', font_size=size, bold=False
)
)
for position in range(table.height):
row = table.get_row(position)
for cell in row.get_cells():
cell.style = style_header if position == 0 or position == 1 else style_body
for paragraph in cell.get_paragraphs():
paragraph.style = cell.style
def apply_column_width_style(document: odfdo.Document, table: odfdo.Table, widths: list[str]):
"""Apply column width style to a table.
Parameters:
document(odfdo.Document): Document where the table is located.
table(odfdo.Table): Table to apply columns widths.
widths(list[str]): list of width in format <number><unit> unit ca be in, cm... see odfdo documentation.
"""
styles = []
for w in widths:
styles.append(document.insert_style(
style=create_column_style_width(w), name=w, automatic=True))
for position in range(table.width):
col = table.get_column(position)
col.style = styles[position]
table.set_column(position, col)
def generate_recap(
contracts: list[models.Contract],
form: models.Form,
):
print(form.productor.products)
recurrents = [pr.name for pr in form.productor.products if pr.type == models.ProductType.RECCURENT]
recurrents = [pr.name for pr in form.productor.products if pr.type ==
models.ProductType.RECCURENT]
recurrents.sort()
occasionnals = [pr.name for pr in form.productor.products if pr.type == models.ProductType.OCCASIONAL]
occasionnals = [pr.name for pr in form.productor.products if pr.type ==
models.ProductType.OCCASIONAL]
occasionnals.sort()
shipments = form.shipments
occasionnals_header = [occ for shipment in shipments for occ in occasionnals]
shipment_header = flatten([[shipment.name] + ["" * len(occasionnals)] for shipment in shipments])
occasionnals_header = [
occ for shipment in shipments for occ in occasionnals]
shipment_header = flatten(
[[f'{shipment.name} - {shipment.date.strftime('%Y-%m-%d')}'] + ["" * len(occasionnals)] for shipment in shipments])
product_unit_map = {
"1": "g",
"2": "kg",
"3": "p"
}
header = (
["Nom", "Email"] +
["Tarif panier", "Total Paniers", "Total à payer"] +
["Cheque 1", "Cheque 2", "Cheque 3"] +
[f"Total {len(shipments)} livraisons + produits occasionnels"] +
recurrents +
occasionnals_header +
["Remarques", "Nom"]
)
data = [
["", ""] + ["" * len(recurrents)] + shipment_header,
["nom", "email"] + recurrents + occasionnals_header + ["remarques", "name"],
[""] * (9 + len(recurrents)) + shipment_header,
header,
*[
[
f'{contract.firstname} {contract.lastname}',
f'{contract.email}',
*[f'{pr.quantity} {product_unit_map[pr.product.unit]}' for pr in sorted(contract.products, key=lambda x: x.product.name) if pr.product.type == models.ProductType.RECCURENT],
*[f'{pr.quantity} {product_unit_map[pr.product.unit]}' for pr in sorted(contract.products, key=lambda x: x.product.name) if pr.product.type == models.ProductType.OCCASIONAL],
"",
f'{contract.firstname} {contract.lastname}',
f'{contract.firstname} {contract.lastname}',
f'{contract.email}',
*[f'{pr.quantity} {product_unit_map[pr.product.unit]}' for pr in sorted(
contract.products, key=lambda x: x.product.name) if pr.product.type == models.ProductType.RECCURENT],
*[f'{pr.quantity} {product_unit_map[pr.product.unit]}' for pr in sorted(
contract.products, key=lambda x: x.product.name) if pr.product.type == models.ProductType.OCCASIONAL],
"",
f'{contract.firstname} {contract.lastname}',
] for contract in contracts
]
]
doc = Document("spreadsheet")
sheet = Table(name="Recap")
doc = odfdo.Document("spreadsheet")
sheet = doc.body.get_sheet(0)
sheet.name = 'Recap'
sheet.set_values(data)
apply_column_width_style(doc, doc.body.get_table(0), ["4cm"] * len(header))
apply_column_height_style(
doc,
doc.body.get_table(0).get_rows((1, 1))[0],
"1.20cm"
)
apply_center_cell_style(doc, doc.body.get_table(0).get_rows((1, 1))[0])
apply_font_style(doc, doc.body.get_table(0))
index = 9 + len(recurrents)
for _ in enumerate(shipments):
startcol = index
endcol = index+len(occasionnals) - 1
sheet.set_span((startcol, 0, endcol, 0), merge=True)
index += len(occasionnals)
offset = 0
index = 2 + len(recurrents)
for i in range(len(shipments)):
index = index + offset
print(index, index+len(occasionnals) - 1)
sheet.set_span((index, 0, index+len(occasionnals) - 1, 0), merge=True)
offset += len(occasionnals)
doc.body.append(sheet)
buffer = io.BytesIO()
doc.save(buffer)
doc.save('test.ods')
return buffer.getvalue()