add clean badge generation for each user from database
This commit is contained in:
parent
d11d0b1b38
commit
4e55fc8273
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
.venv
|
.venv
|
||||||
test.pdf
|
test.pdf
|
||||||
.idea
|
.idea
|
||||||
|
files
|
||||||
27
ezgg_badge_generator.py
Normal file
27
ezgg_badge_generator.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
from services.DatabaseService import DatabaseService
|
||||||
|
from services.BadgeGeneratorService import BadgeGeneratorService
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
db = DatabaseService("sql_config.toml")
|
||||||
|
await db.init_db_pool()
|
||||||
|
|
||||||
|
badges = await db.get_user_badges()
|
||||||
|
|
||||||
|
badge_generator = BadgeGeneratorService(
|
||||||
|
svg_template=Path("template/template_dynamic_name_size.svg"),
|
||||||
|
chrome_path=Path(r"C:\Program Files\Google\Chrome\Application\chrome.exe")
|
||||||
|
)
|
||||||
|
for user_badge in badges:
|
||||||
|
badge_generator.generate_badge(
|
||||||
|
name=user_badge["user_name"],
|
||||||
|
seat_id=user_badge["seat_id"],
|
||||||
|
picture=user_badge["picture"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mysql-connector-python~=9.6.0
|
||||||
|
aiomysql~=0.3.2
|
||||||
|
Jinja2~=3.1.6
|
||||||
68
services/BadgeGeneratorService.py
Normal file
68
services/BadgeGeneratorService.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import base64
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__.split(".")[-1])
|
||||||
|
|
||||||
|
|
||||||
|
class BadgeGeneratorService:
|
||||||
|
|
||||||
|
def __init__(self, svg_template: Path, chrome_path: Path):
|
||||||
|
self.svg_template = svg_template
|
||||||
|
self.chrome_path = chrome_path
|
||||||
|
|
||||||
|
def generate_badge(self, name, seat_id, picture) -> None:
|
||||||
|
svg = self.generate_svg(name, seat_id, picture)
|
||||||
|
self.svg_to_pdf(svg, f"files/{name}.pdf")
|
||||||
|
|
||||||
|
def generate_svg(self, username: str, seat: str, image_base64: bytes) -> str:
|
||||||
|
with open(self.svg_template, "r", encoding="utf-8") as f:
|
||||||
|
svg = f.read()
|
||||||
|
# ToDo: Select font size for long names
|
||||||
|
svg = svg.replace(">test_username</tspan>", f">{username}</tspan>")
|
||||||
|
svg = svg.replace(">Platz: XYZ</tspan><", f">Platz: {seat}</tspan><")
|
||||||
|
if image_base64:
|
||||||
|
encoded = base64.b64encode(image_base64).decode("utf-8")
|
||||||
|
svg = svg.replace('xlink:href="data:image/jpeg;base64,"', f'xlink:href="data:image/jpeg;base64,{encoded}"')
|
||||||
|
logger.info(f"Generate svg string: Username: {username}, Seat: {seat}, Picture: {str(image_base64)[:15]}")
|
||||||
|
return svg
|
||||||
|
|
||||||
|
def svg_to_pdf(self, svg_string: str, pdf_path: str):
|
||||||
|
|
||||||
|
width = re.search(r'width="([^"]+)"', svg_string).group(1)
|
||||||
|
height = re.search(r'height="([^"]+)"', svg_string).group(1)
|
||||||
|
|
||||||
|
env = Environment(loader=FileSystemLoader("template"))
|
||||||
|
template = env.get_template("badge_pdf.html")
|
||||||
|
|
||||||
|
html = template.render(
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
svg_string=svg_string
|
||||||
|
)
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".html", mode="w", encoding="utf-8") as f:
|
||||||
|
f.write(html)
|
||||||
|
html_path = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.run([
|
||||||
|
self.chrome_path,
|
||||||
|
"--headless",
|
||||||
|
"--disable-gpu",
|
||||||
|
f"--print-to-pdf={os.path.abspath(pdf_path)}",
|
||||||
|
html_path
|
||||||
|
], check=True)
|
||||||
|
finally:
|
||||||
|
os.remove(html_path)
|
||||||
|
logger.info(f"Created: {pdf_path}")
|
||||||
38
services/DatabaseService.py
Normal file
38
services/DatabaseService.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import tomllib
|
||||||
|
import aiomysql
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseService:
|
||||||
|
|
||||||
|
def __init__(self, sql_config: str):
|
||||||
|
with open(sql_config, "rb") as f:
|
||||||
|
config = tomllib.load(f)
|
||||||
|
|
||||||
|
self.db_config = config["database"]
|
||||||
|
self._pool: Optional[aiomysql.Pool] = None
|
||||||
|
|
||||||
|
async def init_db_pool(self):
|
||||||
|
self._pool = await aiomysql.create_pool(
|
||||||
|
host=self.db_config["db_host"],
|
||||||
|
port=self.db_config["db_port"],
|
||||||
|
user=self.db_config["db_user"],
|
||||||
|
password=self.db_config["db_password"],
|
||||||
|
db=self.db_config["db_name"],
|
||||||
|
minsize=1,
|
||||||
|
maxsize=40,
|
||||||
|
autocommit=True
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_user_badges(self) -> list:
|
||||||
|
async with self._pool.acquire() as conn:
|
||||||
|
async with conn.cursor(aiomysql.DictCursor) as cursor:
|
||||||
|
await cursor.execute(
|
||||||
|
"""SELECT u.user_id, u.user_name, s.seat_id, upp.picture
|
||||||
|
FROM users AS u
|
||||||
|
LEFT JOIN seats AS s
|
||||||
|
ON u.user_id = s.`user`
|
||||||
|
LEFT JOIN user_profile_picture AS upp
|
||||||
|
ON u.user_id = upp.user_id;"""
|
||||||
|
)
|
||||||
|
return await cursor.fetchall()
|
||||||
0
services/__init__.py
Normal file
0
services/__init__.py
Normal file
6
sql_config.toml
Normal file
6
sql_config.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[database]
|
||||||
|
db_user="demo_user"
|
||||||
|
db_password="demo_password"
|
||||||
|
db_host="127.0.0.1"
|
||||||
|
db_port=3306
|
||||||
|
db_name="ezgg_lan_manager"
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 191 KiB |
39
template/badge_pdf.html
Normal file
39
template/badge_pdf.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
@page {
|
||||||
|
size: {{ width }} {{ height }};
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
width: {{ width }};
|
||||||
|
height: {{ height }};
|
||||||
|
page-break-after: always;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rotated {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="page">
|
||||||
|
{{ svg_string | safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page rotated">
|
||||||
|
{{ svg_string | safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
203
template/template_dynamic_name_size.svg
Normal file
203
template/template_dynamic_name_size.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 59 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 99 KiB |
@ -1,104 +0,0 @@
|
|||||||
import os
|
|
||||||
import re
|
|
||||||
import base64
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
SVG_TEMPLATE = Path("template/Vorlage_LAN_Ausweis.svg")
|
|
||||||
CHROME_PATH = Path(r"C:\Program Files\Google\Chrome\Application\chrome.exe")
|
|
||||||
|
|
||||||
|
|
||||||
def get_image_as_base64(image_path: str) -> str:
|
|
||||||
with open(image_path, "rb") as image_file:
|
|
||||||
encoded_bytes = base64.b64encode(image_file.read())
|
|
||||||
encoded_string = encoded_bytes.decode("utf-8")
|
|
||||||
return f'xlink:href="data:image/jpeg;base64,{encoded_string}"'
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_svg_string(username: str, seat: str, user_image: str) -> str:
|
|
||||||
with open(SVG_TEMPLATE, "r", encoding="utf-8") as svg_file:
|
|
||||||
svg_string = svg_file.read()
|
|
||||||
|
|
||||||
svg_string = svg_string.replace(">test_username</tspan>", f">{username}</tspan>")
|
|
||||||
svg_string = svg_string.replace(">Platz: XYZ</tspan><", f">Platz: {seat}</tspan><")
|
|
||||||
svg_string = svg_string.replace('xlink:href="data:image/jpeg;base64,"', user_image)
|
|
||||||
|
|
||||||
"""
|
|
||||||
with open("test.svg", "w", encoding="utf-8") as f:
|
|
||||||
f.write(svg_content)
|
|
||||||
"""
|
|
||||||
|
|
||||||
return svg_string
|
|
||||||
|
|
||||||
|
|
||||||
def create_pdf_from_svg(svg_string: str, pdf_path: str):
|
|
||||||
width = re.search(r'width="([^"]+)"', svg_string)
|
|
||||||
height = re.search(r'height="([^"]+)"', svg_string)
|
|
||||||
|
|
||||||
width = width.group(1)
|
|
||||||
height = height.group(1)
|
|
||||||
|
|
||||||
html = f"""
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
@page {{
|
|
||||||
size: {width} {height};
|
|
||||||
margin: 0;
|
|
||||||
}}
|
|
||||||
|
|
||||||
body {{
|
|
||||||
margin: 0;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.page {{
|
|
||||||
width: {width};
|
|
||||||
height: {height};
|
|
||||||
page-break-after: always;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}}
|
|
||||||
|
|
||||||
.rotated {{
|
|
||||||
transform: rotate(180deg) scaleX(-1);
|
|
||||||
transform-origin: center;
|
|
||||||
}}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="page">
|
|
||||||
{svg_string}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="page rotated">
|
|
||||||
{svg_string}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".html", mode="w", encoding="utf-8") as f:
|
|
||||||
f.write(html)
|
|
||||||
html_path = f.name
|
|
||||||
|
|
||||||
try:
|
|
||||||
subprocess.run([
|
|
||||||
CHROME_PATH,
|
|
||||||
"--headless",
|
|
||||||
"--disable-gpu",
|
|
||||||
f"--print-to-pdf={os.path.abspath(pdf_path)}",
|
|
||||||
html_path
|
|
||||||
], check=True)
|
|
||||||
finally:
|
|
||||||
os.remove(html_path)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
img_base64 = get_image_as_base64("template/test_profile_picture.png")
|
|
||||||
user_svg_string = get_user_svg_string("Tcprod.", "C01", img_base64)
|
|
||||||
create_pdf_from_svg(user_svg_string, "test.pdf")
|
|
||||||
Loading…
Reference in New Issue
Block a user