Skip to content

Commit c3aaa00

Browse files
author
David Schowalter
committed
Refactor, localize, and enhance testing and build workflows
Refactored code to include English localization for comments and documentation. Updated testing strategy with better Docker integration and scripts for local and container-based test execution. Modularized requirements files for efficient Docker builds, improved database ORM handling, and streamlined PDF/JPEG generation. Version bump to 2.10.0.
1 parent 1b79f15 commit c3aaa00

26 files changed

+797
-696
lines changed

.docker_source_hash

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
5468c6364c9821270d6baf500b24b89e

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ RUN apt-get update && \
1717
apt-get clean && \
1818
rm -rf /var/lib/apt/lists/*
1919

20-
# Copy only the requirements file first to leverage Docker cache
21-
COPY requirements.txt .
20+
# Copy requirements files first to leverage Docker cache
21+
COPY requirements.txt requirements-base.txt ./
2222
RUN pip3 install --no-cache-dir -r requirements.txt
2323

2424
# Start the final stage of the build

app/api/appointments.py

Lines changed: 47 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,15 @@ def handle_jpeg_generation(pdf_filename):
135135
jpeg_stream.seek(0)
136136
jpeg_files.append((f'page_{i + 1}.jpg', jpeg_stream))
137137

138-
zip_buffer = BytesIO()
139-
with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED) as zip_file:
138+
zip_filename = os.path.splitext(pdf_filename)[0] + ".zip"
139+
zip_path = os.path.join(Config.FILE_DIRECTORY, zip_filename)
140+
141+
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zip_file:
140142
for file_name, file_bytes in jpeg_files:
141143
zip_file.writestr(file_name, file_bytes.read())
142-
zip_buffer.seek(0)
143-
logger.info(f"JPEG-Bilder erfolgreich erstellt und in ZIP-Datei gepackt")
144-
return zip_buffer
144+
145+
logger.info(f"JPEG-Bilder erfolgreich erstellt und in ZIP-Datei gepackt: {zip_filename}")
146+
return zip_filename
145147

146148
@router.get("/appointments")
147149
async def appointments_page(
@@ -196,7 +198,7 @@ async def process_appointments(
196198
if not login_token:
197199
return RedirectResponse(url="/", status_code=status.HTTP_303_SEE_OTHER)
198200

199-
# Standardwerte für Datumsbereich, falls nicht im Formular
201+
# Default values for date range if not in the form
200202
if not start_date or not end_date:
201203
start_date_default, end_date_default = get_date_range_from_form()
202204
start_date = start_date or start_date_default
@@ -209,15 +211,15 @@ async def process_appointments(
209211
if calendar_ids:
210212
calendar_ids_int = [int(id) for id in calendar_ids if id.isdigit()]
211213

212-
# Wenn keine Kalender ausgewählt sind, alle verfügbaren Kalender verwenden
214+
# If no calendars are selected, use all available calendars
213215
if not calendar_ids_int and calendars:
214216
calendar_ids_int = [calendar['id'] for calendar in calendars]
215-
logger.info(f"Keine Kalender ausgewählt, verwende alle verfügbaren Kalender: {calendar_ids_int}")
217+
logger.info(f"No calendars selected, using all available calendars: {calendar_ids_int}")
216218

217-
# Standardwerte für Farbeinstellungen
219+
# Default values for color settings
218220
color_settings = load_color_settings(db, "default")
219221

220-
# Überschreibe mit Formulardaten, falls vorhanden
222+
# Override with form data if available
221223
if background_color:
222224
color_settings['background_color'] = background_color
223225
if alpha is not None:
@@ -232,7 +234,7 @@ async def process_appointments(
232234
appointments_data = await fetch_appointments(login_token, start_date, end_date, calendar_ids_int)
233235
appointments = [appointment_to_dict(app) for app in appointments_data]
234236

235-
# Zusätzliche Informationen laden
237+
# Load additional information
236238
additional_infos = get_additional_infos(db, [appointment['id'] for appointment in appointments])
237239
for appointment in appointments:
238240
appointment['additional_info'] = additional_infos.get(appointment['id'], "")
@@ -259,7 +261,7 @@ async def process_appointments(
259261

260262
elif generate_pdf_btn:
261263
if not appointment_id:
262-
# Wenn keine Termine ausgewählt wurden, zurück zur Terminübersicht
264+
# If no appointments were selected, return to appointment overview
263265
return templates.TemplateResponse(
264266
"appointments.html",
265267
{
@@ -275,7 +277,7 @@ async def process_appointments(
275277
}
276278
)
277279

278-
# Zusätzliche Informationen speichern
280+
# Save additional information
279281
appointment_info_list = []
280282
form_data = await request.form()
281283
for app_id in appointment_id:
@@ -296,39 +298,39 @@ async def process_appointments(
296298
except Exception as e:
297299
print(f"Fehler beim Lesen des Hintergrundbildes: {e}")
298300

299-
# Hole die tatsächlichen Termine von der API
300-
logger.info(f"Ausgewählte Termin-IDs: {appointment_id}")
301-
logger.info(f"Rufe Termine ab für Zeitraum {start_date} bis {end_date} und Kalender {calendar_ids_int}")
301+
# Get the actual appointments from the API
302+
logger.info(f"Selected appointment IDs: {appointment_id}")
303+
logger.info(f"Retrieving appointments for period {start_date} to {end_date} and calendars {calendar_ids_int}")
302304

303-
# Hole alle Termine für den angegebenen Zeitraum
305+
# Get all appointments for the specified time period
304306
appointments_data = await fetch_appointments(login_token, start_date, end_date, calendar_ids_int)
305307
logger.info(f"Anzahl abgerufener Termine: {len(appointments_data)}")
306308

307309
# Konvertiere die Termine in das richtige Format
308310
appointments = [appointment_to_dict(app) for app in appointments_data]
309311

310-
# Füge zusätzliche Informationen aus dem Formular hinzu
312+
# Add additional information from the form
311313
for appointment in appointments:
312314
app_id = appointment['id']
313315
additional_info = form_data.get(f'additional_info_{app_id}', "")
314316
appointment['additional_info'] = additional_info
315317

316-
logger.info(f"Anzahl der Termine für PDF: {len(appointments)}")
317-
318-
# Debug-Logging für IDs
319-
logger.info(f"Ausgewählte Termin-IDs: {appointment_id}")
320-
logger.info(f"Verfügbare Termin-IDs: {[app['id'] for app in appointments]}")
318+
logger.info(f"Number of appointments for PDF: {len(appointments)}")
319+
320+
# Debug logging for IDs
321+
logger.info(f"Selected appointment IDs: {appointment_id}")
322+
logger.info(f"Available appointment IDs: {[app['id'] for app in appointments]}")
321323

322-
# Nur ausgewählte Termine verwenden - mit Stringvergleich
324+
# Use only selected appointments - with string comparison
323325
selected_appointments = []
324326
for app in appointments:
325327
for app_id in appointment_id:
326328
if str(app['id']) == str(app_id):
327329
selected_appointments.append(app)
328330
break
329331

330-
# Logging für ausgewählte Termine
331-
logger.info(f"Generiere PDF für {len(selected_appointments)} Termine:")
332+
# Logging for selected appointments
333+
logger.info(f"Generating PDF for {len(selected_appointments)} appointments:")
332334
for idx, app in enumerate(selected_appointments, 1):
333335
logger.info(f" {idx}. {app['description']} am {app['startDateView']} ({app['startTimeView']}-{app['endTimeView']})")
334336

@@ -343,7 +345,7 @@ async def process_appointments(
343345

344346
elif generate_jpeg_btn:
345347
if not appointment_id:
346-
# Wenn keine Termine ausgewählt wurden, zurück zur Terminübersicht
348+
# If no appointments were selected, return to appointment overview
347349
return templates.TemplateResponse(
348350
"appointments.html",
349351
{
@@ -359,7 +361,7 @@ async def process_appointments(
359361
}
360362
)
361363

362-
# Ähnlich wie bei PDF, aber mit JPEG-Konvertierung
364+
# Similar to PDF, but with JPEG conversion
363365
appointment_info_list = []
364366
form_data = await request.form()
365367
for app_id in appointment_id:
@@ -380,39 +382,39 @@ async def process_appointments(
380382
except Exception as e:
381383
print(f"Fehler beim Lesen des Hintergrundbildes: {e}")
382384

383-
# Hole die tatsächlichen Termine von der API
384-
logger.info(f"Ausgewählte Termin-IDs: {appointment_id}")
385-
logger.info(f"Rufe Termine ab für Zeitraum {start_date} bis {end_date} und Kalender {calendar_ids_int}")
385+
# Get the actual appointments from the API
386+
logger.info(f"Selected appointment IDs: {appointment_id}")
387+
logger.info(f"Retrieving appointments for period {start_date} to {end_date} and calendars {calendar_ids_int}")
386388

387-
# Hole alle Termine für den angegebenen Zeitraum
389+
# Get all appointments for the specified time period
388390
appointments_data = await fetch_appointments(login_token, start_date, end_date, calendar_ids_int)
389391
logger.info(f"Anzahl abgerufener Termine: {len(appointments_data)}")
390392

391393
# Konvertiere die Termine in das richtige Format
392394
appointments = [appointment_to_dict(app) for app in appointments_data]
393395

394-
# Füge zusätzliche Informationen aus dem Formular hinzu
396+
# Add additional information from the form
395397
for appointment in appointments:
396398
app_id = appointment['id']
397399
additional_info = form_data.get(f'additional_info_{app_id}', "")
398400
appointment['additional_info'] = additional_info
399401

400-
logger.info(f"Anzahl der Termine für JPEG: {len(appointments)}")
401-
402-
# Debug-Logging für IDs
403-
logger.info(f"Ausgewählte Termin-IDs: {appointment_id}")
404-
logger.info(f"Verfügbare Termin-IDs: {[app['id'] for app in appointments]}")
402+
logger.info(f"Number of appointments for JPEG: {len(appointments)}")
403+
404+
# Debug logging for IDs
405+
logger.info(f"Selected appointment IDs: {appointment_id}")
406+
logger.info(f"Available appointment IDs: {[app['id'] for app in appointments]}")
405407

406-
# Nur ausgewählte Termine verwenden - mit Stringvergleich
408+
# Use only selected appointments - with string comparison
407409
selected_appointments = []
408410
for app in appointments:
409411
for app_id in appointment_id:
410412
if str(app['id']) == str(app_id):
411413
selected_appointments.append(app)
412414
break
413415

414-
# Logging für ausgewählte Termine
415-
logger.info(f"Generiere JPEG für {len(selected_appointments)} Termine:")
416+
# Logging for selected appointments
417+
logger.info(f"Generating JPEG for {len(selected_appointments)} appointments:")
416418
for idx, app in enumerate(selected_appointments, 1):
417419
logger.info(f" {idx}. {app['description']} am {app['startDateView']} ({app['startTimeView']}-{app['endTimeView']})")
418420

@@ -422,13 +424,13 @@ async def process_appointments(
422424
background_image_stream)
423425

424426
# JPEG generieren
425-
zip_buffer = handle_jpeg_generation(filename)
427+
zip_filename = handle_jpeg_generation(filename)
426428

427-
# Als Datei zurückgeben
429+
# Return as file
428430
response = FileResponse(
429-
zip_buffer,
431+
os.path.join(Config.FILE_DIRECTORY, zip_filename),
430432
media_type="application/zip",
431-
filename="images.zip"
433+
filename=zip_filename
432434
)
433435
response.set_cookie(key="jpegGenerated", value="true", max_age=1, path='/')
434436
return response

app/database.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from sqlalchemy import create_engine, Column, String, Integer, Text
2-
from sqlalchemy.ext.declarative import declarative_base
32
from sqlalchemy.orm import sessionmaker
3+
from sqlalchemy.orm import declarative_base
44
from app.config import Config
55
import os
66

@@ -15,7 +15,7 @@
1515
# Session-Factory erstellen
1616
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
1717

18-
# Base-Klasse für Modelle
18+
# Base class for models
1919
Base = declarative_base()
2020

2121
# Datenbankmodelle
@@ -34,7 +34,7 @@ class ColorSetting(Base):
3434
date_color = Column(String, nullable=False)
3535
description_color = Column(String, nullable=False)
3636

37-
# Dependency für Datenbankzugriff
37+
# Dependency for database access
3838
def get_db():
3939
db = SessionLocal()
4040
try:
@@ -46,7 +46,7 @@ def get_db():
4646
def create_schema():
4747
Base.metadata.create_all(bind=engine)
4848

49-
# CRUD-Operationen für Appointments
49+
# CRUD operations for Appointments
5050
def save_additional_infos(db, appointment_info_list):
5151
try:
5252
for appointment_id, additional_info in appointment_info_list:
@@ -68,7 +68,7 @@ def get_additional_infos(db, appointment_ids):
6868
print(f"Database error: {e}")
6969
return {}
7070

71-
# CRUD-Operationen für ColorSettings
71+
# CRUD operations for ColorSettings
7272
def save_color_settings(db, settings):
7373
try:
7474
color_setting = db.query(ColorSetting).filter(ColorSetting.setting_name == settings['name']).first()

app/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
# Templates einrichten
2323
templates = Jinja2Templates(directory="app/templates")
2424

25-
# Stellen Sie sicher, dass das Verzeichnis für gespeicherte Dateien existiert
25+
# Make sure the directory for saved files exists
2626
Path(Config.FILE_DIRECTORY).mkdir(parents=True, exist_ok=True)
2727

2828
# Routen einbinden

app/services/pdf_generator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ def wrap_text(text, font_name, line_height, max_width):
164164

165165

166166
def create_pdf(appointments, date_color, background_color, description_color, alpha, image_stream=None):
167-
# Versuche, die Schriftart zu registrieren und verwende Fallback, wenn nicht verfügbar
167+
# Try to register the font and use fallback if not available
168168
try:
169169
# Versuche, Bahnschrift zu registrieren
170170
if 'Bahnschrift' not in pdfmetrics.getRegisteredFontNames():

app/static/css/appointments.css

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ textarea:focus {
629629

630630
@media (max-width: 480px) {
631631
.format-options {
632-
grid-template-columns: 1fr; /* Einspaltig auf sehr kleinen Geräten */
632+
grid-template-columns: 1fr; /* Single column on very small devices */
633633
}
634634
}
635635

@@ -695,7 +695,7 @@ textarea:focus {
695695
overflow: visible;
696696
}
697697

698-
/* Container für Hintergrundbild */
698+
/* Container for background image */
699699
.format-option-full {
700700
margin-bottom: 10px;
701701
overflow: visible;
@@ -788,11 +788,11 @@ textarea:focus {
788788
transform: scale(1.1);
789789
}
790790

791-
/* Zweispaltiges Layout für Termine und Formatierungsoptionen */
791+
/* Two-column layout for appointments and formatting options */
792792
.appointments-layout {
793793
display: grid;
794-
grid-template-columns: 3fr 2fr; /* Mehr Platz für die rechte Spalte */
795-
gap: var(--spacing-lg); /* Größerer Abstand zwischen den Spalten */
794+
grid-template-columns: 3fr 2fr; /* More space for the right column */
795+
gap: var(--spacing-lg); /* Larger gap between columns */
796796
margin-top: var(--spacing-md);
797797
}
798798

@@ -815,10 +815,10 @@ textarea:focus {
815815
-webkit-overflow-scrolling: touch; /* Verbessert das Scrolling auf iOS */
816816
}
817817

818-
/* Mobile Anpassungen für das Layout */
818+
/* Mobile adjustments for the layout */
819819
@media (max-width: 768px) {
820820
.appointments-layout {
821-
grid-template-columns: 1fr; /* Einspaltiges Layout auf mobilen Geräten */
821+
grid-template-columns: 1fr; /* Single column layout on mobile devices */
822822
gap: var(--spacing-md);
823823
}
824824

@@ -828,7 +828,7 @@ textarea:focus {
828828
}
829829

830830
.appointments-sidebar {
831-
position: relative; /* Keine sticky Position auf mobilen Geräten */
831+
position: relative; /* No sticky position on mobile devices */
832832
top: 0;
833833
max-height: none;
834834
margin-top: var(--spacing-md);

app/static/css/common.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ footer {
347347
input[type="submit"],
348348
button {
349349
width: 100%;
350-
padding: 12px 16px; /* Größere Buttons für Touch-Geräte */
350+
padding: 12px 16px; /* Larger buttons for touch devices */
351351
margin-top: 8px;
352352
margin-bottom: 8px;
353353
}

app/static/css/prefixed.css

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -272,27 +272,27 @@
272272
}
273273
}
274274

275-
/* Allgemeine Hilfsmittel für bessere Browserkompatibilität */
275+
/* General utilities for better browser compatibility */
276276

277-
/* Verhindert Textvergrößerung bei Orientierungsänderungen im iOS */
277+
/* Prevents text enlargement during orientation changes in iOS */
278278
html {
279279
-webkit-text-size-adjust: 100%;
280280
-ms-text-size-adjust: 100%;
281281
text-size-adjust: 100%;
282282
}
283283

284-
/* Verbesserte Touch-Ziele für mobile Geräte */
284+
/* Improved touch targets for mobile devices */
285285
.touch-target {
286286
min-height: 44px;
287287
min-width: 44px;
288288
}
289289

290-
/* Verhindert blaue Hervorhebung bei Tap auf mobilen Geräten */
290+
/* Prevents blue highlight on tap on mobile devices */
291291
.no-tap-highlight {
292292
-webkit-tap-highlight-color: transparent;
293293
}
294294

295-
/* Verbesserte Schriftglättung */
295+
/* Improved font smoothing */
296296
.font-smoothing {
297297
-webkit-font-smoothing: antialiased;
298298
-moz-osx-font-smoothing: grayscale;

0 commit comments

Comments
 (0)