From 5b8a8b75729d62cf29fef7b95877039f16565c48 Mon Sep 17 00:00:00 2001 From: Jonas Heinrich Date: Mon, 29 Dec 2025 02:20:56 +0100 Subject: [PATCH] word generation --- assets/antrag_vorlage.docx | Bin 0 -> 32706 bytes flake.nix | 1 - meinantrag.py | 294 +++++++++++++--------------------- templates/base.html | 6 +- templates/index.html | 47 ------ vorlage.html | 315 +++++++++++++++++++++++++++++++++++++ 6 files changed, 428 insertions(+), 235 deletions(-) create mode 100644 assets/antrag_vorlage.docx create mode 100644 vorlage.html diff --git a/assets/antrag_vorlage.docx b/assets/antrag_vorlage.docx new file mode 100644 index 0000000000000000000000000000000000000000..7e4ce2a6ee1f500dc8001d1594ae3429f539bb15 GIT binary patch literal 32706 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 Kd+<5a@6O!c$7V10 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 H3E}|&RYZ@I literal 0 HcmV?d00001 diff --git a/flake.nix b/flake.nix index 1ce34c0..0a078b4 100644 --- a/flake.nix +++ b/flake.nix @@ -31,7 +31,6 @@ jinja2 google-generativeai # Dependency for Gemini API grpcio # Required by google-generativeai - reportlab # Dependency for PDF generation python-docx # Dependency for Word document generation ]; diff --git a/meinantrag.py b/meinantrag.py index b8d8da6..6acfe08 100644 --- a/meinantrag.py +++ b/meinantrag.py @@ -13,11 +13,7 @@ from jinja2 import Environment, FileSystemLoader import google.generativeai as genai import re from io import BytesIO -from reportlab.lib.pagesizes import A4 -from reportlab.lib.units import cm -from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle -from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer -from reportlab.lib.enums import TA_LEFT, TA_JUSTIFY +from datetime import datetime try: from docx import Document from docx.shared import Pt, Inches @@ -84,8 +80,8 @@ class MeinAntragApp(BaseTemplateResource): template = self.jinja_env.get_template('index.html') resp.content_type = 'text/html; charset=utf-8' resp.text = template.render( - meta_title='MeinAntrag – Anfragelinks für FragDenStaat', - meta_description='Erstelle vorausgefüllte Anfragelinks für FragDenStaat.de, suche Behörden, füge Betreff und Text hinzu und teile den Link.', + meta_title='MeinAntrag – Anträge an die Karlsruher Stadtverwaltung', + meta_description='Erstelle einfach Vorlagen für Anfragen oder Anträge an die Karlsruher Stadtverwaltung zu deinem persönlichen Thema und schicke diese direkt an eine Stadtratsfraktion!', canonical_url=f"{SITE_BASE_URL}/" ) @@ -301,188 +297,120 @@ WICHTIG: 'error': str(e) }) -class GeneratePDFResource: - def _generate_pdf(self, title, demand, justification, party_name=""): - """Generate a PDF that looks like a city council proposal""" - buffer = BytesIO() - doc = SimpleDocTemplate(buffer, pagesize=A4, - rightMargin=2.5*cm, leftMargin=2.5*cm, - topMargin=2.5*cm, bottomMargin=2.5*cm) - - # Container for the 'Flowable' objects - story = [] - - # Define styles - styles = getSampleStyleSheet() - - # Custom styles for the document - title_style = ParagraphStyle( - 'CustomTitle', - parent=styles['Heading1'], - fontSize=16, - textColor='black', - spaceAfter=30, - alignment=TA_LEFT, - fontName='Helvetica-Bold' - ) - - heading_style = ParagraphStyle( - 'CustomHeading', - parent=styles['Heading2'], - fontSize=12, - textColor='black', - spaceAfter=12, - spaceBefore=20, - alignment=TA_LEFT, - fontName='Helvetica-Bold' - ) - - body_style = ParagraphStyle( - 'CustomBody', - parent=styles['Normal'], - fontSize=11, - textColor='black', - spaceAfter=12, - alignment=TA_JUSTIFY, - fontName='Helvetica' - ) - - # Header with party name if provided - if party_name: - party_para = Paragraph(f"Antrag der {party_name}", body_style) - story.append(party_para) - story.append(Spacer(1, 0.5*cm)) - - # Title - if title: - title_para = Paragraph(f"{title}", title_style) - story.append(title_para) - - # Demand section - if demand: - story.append(Spacer(1, 0.3*cm)) - demand_heading = Paragraph("Der Gemeinderat möge beschließen:", heading_style) - story.append(demand_heading) - - # Process demand text - replace newlines with proper breaks - demand_lines = demand.split('\n') - for line in demand_lines: - if line.strip(): - demand_para = Paragraph(line.strip(), body_style) - story.append(demand_para) - - # Justification section - if justification: - story.append(Spacer(1, 0.5*cm)) - justification_heading = Paragraph("Begründung/Sachverhalt", heading_style) - story.append(justification_heading) - - # Process justification text - justification_lines = justification.split('\n') - for line in justification_lines: - if line.strip(): - justification_para = Paragraph(line.strip(), body_style) - story.append(justification_para) - - # Build PDF - doc.build(story) - buffer.seek(0) - return buffer - - def on_post(self, req, resp): - """Generate PDF from form data""" - try: - # Get form data - title = req.get_param('title', default='') or '' - demand = req.get_param('demand', default='') or '' - justification = req.get_param('justification', default='') or '' - party_name = req.get_param('party_name', default='') or '' - - # If empty, try to read from stream - if not title: - try: - stream = getattr(req, 'bounded_stream', req.stream) - raw_body = stream.read().decode('utf-8') - parsed = parse_qs(raw_body) - title = parsed.get('title', [''])[0] - demand = parsed.get('demand', [''])[0] - justification = parsed.get('justification', [''])[0] - party_name = parsed.get('party_name', [''])[0] - except Exception: - pass - - # Generate PDF - pdf_buffer = self._generate_pdf(title, demand, justification, party_name) - - # Return PDF - resp.content_type = 'application/pdf' - resp.set_header('Content-Disposition', 'inline; filename="antrag.pdf"') - resp.data = pdf_buffer.read() - - except Exception as e: - import traceback - traceback.print_exc() - resp.status = falcon.HTTP_500 - resp.content_type = 'application/json' - resp.text = json.dumps({ - 'success': False, - 'error': str(e) - }) - class GenerateWordResource: + def __init__(self): + # Get template path + script_dir = os.path.dirname(os.path.abspath(__file__)) + self.template_path = os.path.join(script_dir, 'assets', 'antrag_vorlage.docx') + # Fallback if not in assets + if not os.path.exists(self.template_path): + assets_dir = os.path.join(script_dir, '..', 'assets') + self.template_path = os.path.join(assets_dir, 'antrag_vorlage.docx') + def _generate_word(self, title, demand, justification, party_name=""): - """Generate a Word document that looks like a city council proposal""" - doc = Document() + """Generate a Word document using the template""" + # Load template + if os.path.exists(self.template_path): + doc = Document(self.template_path) + else: + # Fallback: create new document if template not found + doc = Document() - # Set default font - style = doc.styles['Normal'] - font = style.font - font.name = 'Arial' - font.size = Pt(11) + # Get current date in DD.MM.YYYY format + current_date = datetime.now().strftime("%d.%m.%Y") - # Header with party name if provided - if party_name: - party_para = doc.add_paragraph(f"Antrag der {party_name}") - party_para.runs[0].bold = True - party_para.runs[0].font.size = Pt(11) - doc.add_paragraph() + # Combine demand for ANTRAGSTEXT (with heading) + antragtext = "Der Gemeinderat möge beschließen:\n" + demand - # Title - if title: - title_para = doc.add_paragraph(title) - title_para.runs[0].bold = True - title_para.runs[0].font.size = Pt(16) - title_para.paragraph_format.space_after = Pt(30) - - # Demand section - if demand: - doc.add_paragraph() - demand_heading = doc.add_paragraph("Der Gemeinderat möge beschließen:") - demand_heading.runs[0].bold = True - demand_heading.runs[0].font.size = Pt(12) - demand_heading.paragraph_format.space_before = Pt(20) - demand_heading.paragraph_format.space_after = Pt(12) + # Replace placeholders in all paragraphs + for paragraph in doc.paragraphs: + full_text = paragraph.text + if not full_text: + continue - # Process demand text - demand_lines = demand.split('\n') - for line in demand_lines: - if line.strip(): - doc.add_paragraph(line.strip()) - - # Justification section - if justification: - doc.add_paragraph() - justification_heading = doc.add_paragraph("Begründung/Sachverhalt") - justification_heading.runs[0].bold = True - justification_heading.runs[0].font.size = Pt(12) - justification_heading.paragraph_format.space_before = Pt(20) - justification_heading.paragraph_format.space_after = Pt(12) + # Replace FRAKTION + if party_name and 'FRAKTION' in full_text: + for run in paragraph.runs: + if 'FRAKTION' in run.text: + run.text = run.text.replace('FRAKTION', party_name) - # Process justification text - justification_lines = justification.split('\n') - for line in justification_lines: - if line.strip(): - doc.add_paragraph(line.strip()) + # Replace XX.XX.XXXX with current date + if 'XX.XX.XXXX' in full_text: + for run in paragraph.runs: + if 'XX.XX.XXXX' in run.text: + run.text = run.text.replace('XX.XX.XXXX', current_date) + + # Replace ANTRAGSTITEL (bold) + if 'ANTRAGSTITEL' in full_text: + paragraph.clear() + run = paragraph.add_run(title) + run.bold = True + + # Replace ANTRAGSTEXT + if 'ANTRAGSTEXT' in full_text: + paragraph.clear() + lines = antragtext.split('\n') + for i, line in enumerate(lines): + if line.strip(): + run = paragraph.add_run(line.strip()) + if i == 0: # First line (heading) should be bold + run.bold = True + if i < len(lines) - 1: + paragraph.add_run('\n') + + # Replace BEGRÜNDUNGSTEXT + if 'BEGRÜNDUNGSTEXT' in full_text: + paragraph.clear() + lines = justification.split('\n') + for i, line in enumerate(lines): + if line.strip(): + paragraph.add_run(line.strip()) + if i < len(lines) - 1: + paragraph.add_run('\n') + + # Also check tables for placeholders + for table in doc.tables: + for row in table.rows: + for cell in row.cells: + for paragraph in cell.paragraphs: + full_text = paragraph.text + if not full_text: + continue + + if party_name and 'FRAKTION' in full_text: + for run in paragraph.runs: + if 'FRAKTION' in run.text: + run.text = run.text.replace('FRAKTION', party_name) + + if 'XX.XX.XXXX' in full_text: + for run in paragraph.runs: + if 'XX.XX.XXXX' in run.text: + run.text = run.text.replace('XX.XX.XXXX', current_date) + + if 'ANTRAGSTITEL' in full_text: + paragraph.clear() + run = paragraph.add_run(title) + run.bold = True + + if 'ANTRAGSTEXT' in full_text: + paragraph.clear() + lines = antragtext.split('\n') + for i, line in enumerate(lines): + if line.strip(): + run = paragraph.add_run(line.strip()) + if i == 0: + run.bold = True + if i < len(lines) - 1: + paragraph.add_run('\n') + + if 'BEGRÜNDUNGSTEXT' in full_text: + paragraph.clear() + lines = justification.split('\n') + for i, line in enumerate(lines): + if line.strip(): + paragraph.add_run(line.strip()) + if i < len(lines) - 1: + paragraph.add_run('\n') # Save to buffer buffer = BytesIO() @@ -583,7 +511,6 @@ meinantrag = MeinAntragApp() impressum = ImpressumResource() datenschutz = DatenschutzResource() generate_antrag = GenerateAntragResource() -generate_pdf = GeneratePDFResource() generate_word = GenerateWordResource() robots = RobotsResource() sitemap = SitemapResource() @@ -592,7 +519,6 @@ app.add_route('/', meinantrag) app.add_route('/impressum', impressum) app.add_route('/datenschutz', datenschutz) app.add_route('/api/generate-antrag', generate_antrag) -app.add_route('/api/generate-pdf', generate_pdf) app.add_route('/api/generate-word', generate_word) app.add_route('/robots.txt', robots) app.add_route('/sitemap.xml', sitemap) diff --git a/templates/base.html b/templates/base.html index 5e1c696..f2a7a12 100644 --- a/templates/base.html +++ b/templates/base.html @@ -8,20 +8,20 @@ - + - + - + {% if noindex %}{% endif %} diff --git a/templates/index.html b/templates/index.html index 510ab24..85ba159 100644 --- a/templates/index.html +++ b/templates/index.html @@ -69,13 +69,6 @@ Mail an Fraktion senden -