Coverage for functions \ flipdare \ mailer \ core \ gallery_email_formatter.py: 100%
55 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
1#!/usr/bin/env python
2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved.
3#
4# This file is part of Flipdare's proprietary software and contains
5# confidential and copyrighted material. Unauthorised copying,
6# modification, distribution, or use of this file is strictly
7# prohibited without prior written permission from Flipdare Pty Ltd.
8#
9# This software includes third-party components licensed under MIT,
10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details.
11#
13from __future__ import annotations
15from typing import override
16import pandas as pd
17from pandas.io.formats.style_render import CSSDict
18from flipdare.app_log import LOG
19from flipdare.app_types import ReportListType
20from flipdare.mailer.core.email_sanitizer import EmailPostSanitizer
21from flipdare.mailer.core.table_email_formatter import TableEmailFormatter
22from flipdare.mailer.email_image import EmailImage
23from flipdare.constants import IS_TRACE
25__all__ = ["GalleryEmailFormatter"]
28class GalleryEmailFormatter(TableEmailFormatter):
29 def __init__(
30 self,
31 headers: list[str],
32 data: ReportListType,
33 images: list[EmailImage],
34 images_per_row: int,
35 ) -> None:
37 if IS_TRACE:
38 msg = (
39 f"Initializing EmailReport with {len(headers)} columns "
40 f"and {len(data)} rows and {len(images)} images."
41 )
42 LOG().trace(msg)
44 super().__init__(headers=headers, data=data)
45 self.images = images
46 self.images_per_row = images_per_row
48 @override
49 def tabulate_text(self) -> str:
50 # tabulate_text does pre-formatting..
51 data_text = super().tabulate_text()
53 all_rows: list[list[str]] = []
54 images = self.images
56 for i in range(len(images)):
57 # we want 2 columns
58 # | image <i> | notes |
59 # | | notes 2 |
60 notes = images[i].notes
61 all_rows.append([f"**Image {i+1}**", notes[0]])
62 all_rows.extend([["", note] for note in notes[1:]])
64 # Create the DataFrame without specific column headers if preferred
65 df = pd.DataFrame(all_rows)
67 # Use tablefmt="github" or "pipe" for the cleanest markdown output
68 image_text = df.to_markdown(
69 index=False,
70 headers=["Image", "Notes"],
71 tablefmt="github",
72 )
74 return EmailPostSanitizer(
75 data=[image_text, data_text],
76 headers=["Statistics", "Report Data"],
77 ).format(is_html=False)
79 @override
80 def tabulate_html(self) -> str:
81 # tabulate_html does pre-formatting..
82 data_html = super().tabulate_html()
84 # 1. Build the row-based data structure
85 image_data = self._build_image_report_data()
87 # 2. Create DataFrame (headers can be generic or empty for this layout)
88 df = pd.DataFrame(image_data)
89 styler = df.style.hide(axis="index").hide(axis="columns")
91 # 3. Apply your CSSDict styles
92 css_style: list[CSSDict] = [
93 {
94 "selector": "td",
95 "props": [
96 ("padding", "10px"),
97 ("border", "1px solid #ddd"),
98 ("text-align", "center"),
99 ],
100 },
101 ]
103 # 4. Render to HTML (CRITICAL: escape=False to allow <img> tags)
104 image_html = styler.set_table_styles(css_style).to_html(escape=False)
106 return EmailPostSanitizer(
107 data=[image_html, data_html],
108 headers=["Statistics", "Report Data"],
109 ).format(is_html=True)
111 def _build_image_report_data(self) -> list[list[str]]:
112 data: list[list[str]] = []
113 images = self.images
114 images_per_row = self.images_per_row
116 for i in range(0, len(images), images_per_row):
117 chunk = images[i : i + images_per_row]
118 combined_row = []
120 for img in chunk:
121 # Combine Image and Notes into one HTML block
122 notes_html = "<br />".join(img.notes)
123 # We wrap them in a div to control spacing/alignment if needed
124 cell_content = (
125 f'<div style="margin-bottom: 10px;">{img.cid_url}</div>'
126 f'<div style="font-size: 14px; color: #fff;">{notes_html}</div>'
127 )
128 combined_row.append(cell_content)
130 # Pad the row with empty strings to maintain table structure
131 while len(combined_row) < images_per_row:
132 combined_row.append("")
134 data.append(combined_row)
136 return data