feat: tilføj rigtig PDF download via WeasyPrint
All checks were successful
Build and Deploy Erika CV / build-and-deploy (push) Successful in 58s
All checks were successful
Build and Deploy Erika CV / build-and-deploy (push) Successful in 58s
- /download route genererer PDF server-side - PDF ser ud som hjemmesiden (ikke browser-print) - Kompakt print-CSS til 1 A4-side - Knap downloader direkte i stedet for at åbne print-dialog
This commit is contained in:
@@ -14,6 +14,15 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libpango-1.0-0 \
|
||||||
|
libpangoft2-1.0-0 \
|
||||||
|
libharfbuzz0b \
|
||||||
|
libfontconfig1 \
|
||||||
|
libcairo2 \
|
||||||
|
fonts-liberation \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN pip install --upgrade --no-cache-dir pip
|
RUN pip install --upgrade --no-cache-dir pip
|
||||||
|
|
||||||
COPY requirements.txt ./
|
COPY requirements.txt ./
|
||||||
|
|||||||
15
app.py
15
app.py
@@ -1,6 +1,8 @@
|
|||||||
from flask import Flask, render_template, jsonify
|
from flask import Flask, render_template, jsonify, send_file, request
|
||||||
|
from weasyprint import HTML
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
import os
|
import os
|
||||||
|
import io
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
@@ -11,6 +13,17 @@ GIT_COMMIT = os.getenv('GIT_COMMIT', 'unknown')
|
|||||||
def index():
|
def index():
|
||||||
return render_template('index.html')
|
return render_template('index.html')
|
||||||
|
|
||||||
|
@app.route('/download')
|
||||||
|
def download_pdf():
|
||||||
|
html = render_template('index.html', pdf_mode=True)
|
||||||
|
pdf = HTML(string=html, base_url=request.host_url).write_pdf()
|
||||||
|
return send_file(
|
||||||
|
io.BytesIO(pdf),
|
||||||
|
mimetype='application/pdf',
|
||||||
|
as_attachment=True,
|
||||||
|
download_name='Erika_Nielsen_CV.pdf'
|
||||||
|
)
|
||||||
|
|
||||||
@app.route('/health')
|
@app.route('/health')
|
||||||
def health():
|
def health():
|
||||||
return jsonify({
|
return jsonify({
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
flask>=3.0.0
|
flask>=3.0.0
|
||||||
gunicorn>=22.0.0
|
gunicorn>=22.0.0
|
||||||
|
weasyprint>=62.0
|
||||||
|
|||||||
@@ -379,26 +379,44 @@
|
|||||||
}
|
}
|
||||||
.pdf-btn:hover { background: #2f72d0; transform: scale(1.04); }
|
.pdf-btn:hover { background: #2f72d0; transform: scale(1.04); }
|
||||||
|
|
||||||
/* Print */
|
/* Print / PDF via WeasyPrint */
|
||||||
@media print {
|
@media print {
|
||||||
@page { size: A4; margin: 0; }
|
@page { size: A4; margin: 8mm; }
|
||||||
html, body { background: white; padding: 0; margin: 0; display: block; }
|
html, body { background: white !important; padding: 0; margin: 0; }
|
||||||
body {
|
body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||||||
-webkit-print-color-adjust: exact;
|
.cv-wrapper { box-shadow: none; border-radius: 0; width: 194mm; max-width: 194mm; }
|
||||||
print-color-adjust: exact;
|
|
||||||
}
|
|
||||||
.cv-wrapper {
|
|
||||||
box-shadow: none;
|
|
||||||
border-radius: 0;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
transform-origin: top left;
|
|
||||||
transform: scale(0.78);
|
|
||||||
height: 128vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.sidebar { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
.sidebar { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||||||
.pdf-btn { display: none; }
|
.pdf-btn { display: none !important; }
|
||||||
|
/* Kompakt til 1 side */
|
||||||
|
body { padding: 0; }
|
||||||
|
.hero { padding: 18px 22px 12px; }
|
||||||
|
.hero-name { font-size: 22px; }
|
||||||
|
.hero-tagline { font-size: 11px; margin-bottom: 6px; }
|
||||||
|
.hero-summary { font-size: 11px; line-height: 1.5; }
|
||||||
|
.main { padding: 0 18px 10px; }
|
||||||
|
section { margin-bottom: 10px; }
|
||||||
|
.section-title { font-size: 10px; padding: 5px 0 4px; margin-bottom: 8px; }
|
||||||
|
.timeline-item { padding: 7px 0; }
|
||||||
|
.timeline-period { font-size: 9px; }
|
||||||
|
.timeline-title { font-size: 12px; }
|
||||||
|
.timeline-subtitle { font-size: 10px; }
|
||||||
|
.timeline-bullets { font-size: 10px; margin-top: 2px; }
|
||||||
|
.timeline-bullets li { margin-bottom: 0; }
|
||||||
|
.edu-card { padding: 7px 10px; }
|
||||||
|
.edu-period { font-size: 9px; }
|
||||||
|
.edu-title { font-size: 11px; }
|
||||||
|
.edu-place { font-size: 10px; }
|
||||||
|
.lang-item label { font-size: 10px; margin-bottom: 3px; }
|
||||||
|
.lang-bar-track { height: 4px; }
|
||||||
|
.tag { font-size: 9px; padding: 2px 6px; }
|
||||||
|
.sidebar-name h1 { font-size: 17px; }
|
||||||
|
.sidebar-name p { font-size: 10px; }
|
||||||
|
.sidebar-section { margin-bottom: 16px; }
|
||||||
|
.sidebar-section-title { font-size: 8px; }
|
||||||
|
.sidebar-item { font-size: 11px; }
|
||||||
|
.sidebar-item small { font-size: 9px; }
|
||||||
|
.avatar-wrap { padding: 18px 0 10px; }
|
||||||
|
.avatar { width: 70px; height: 70px; font-size: 22px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile */
|
/* Mobile */
|
||||||
@@ -604,7 +622,9 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="pdf-btn" onclick="window.print()">⬇ Download CV som PDF</button>
|
{% if not pdf_mode %}
|
||||||
|
<button class="pdf-btn" onclick="window.location='/download'">⬇ Download CV som PDF</button>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user