665 lines
22 KiB
Python
665 lines
22 KiB
Python
import os
|
|
import uuid
|
|
import yaml
|
|
import re
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
from flask import Flask, render_template, request, redirect, url_for, flash, session, send_from_directory, abort
|
|
from flask_mail import Mail, Message
|
|
from flask_wtf.csrf import CSRFProtect, CSRFError
|
|
from werkzeug.utils import secure_filename
|
|
from config import Config
|
|
|
|
app = Flask(__name__)
|
|
app.config.from_object(Config)
|
|
mail = Mail(app)
|
|
csrf = CSRFProtect(app)
|
|
|
|
# Ensure applications folder exists
|
|
Path(app.config['APPLICATIONS_FOLDER']).mkdir(exist_ok=True)
|
|
|
|
|
|
# Rate limiting helper
|
|
def check_rate_limit():
|
|
"""
|
|
Check if user is submitting forms too quickly.
|
|
Returns (allowed: bool, wait_seconds: int)
|
|
"""
|
|
now = datetime.now()
|
|
last_submit_str = session.get('last_submission_time')
|
|
|
|
if last_submit_str:
|
|
try:
|
|
last_submit = datetime.fromisoformat(last_submit_str)
|
|
elapsed = (now - last_submit).total_seconds()
|
|
|
|
if elapsed < app.config['RATE_LIMIT_SECONDS']:
|
|
wait_seconds = int(app.config['RATE_LIMIT_SECONDS'] - elapsed) + 1
|
|
return False, wait_seconds
|
|
except (ValueError, TypeError):
|
|
# Invalid timestamp, allow submission
|
|
pass
|
|
|
|
# Update last submission time
|
|
session['last_submission_time'] = now.isoformat()
|
|
return True, 0
|
|
|
|
|
|
# Error handlers
|
|
@app.errorhandler(CSRFError)
|
|
def handle_csrf_error(e):
|
|
"""Handle CSRF validation errors."""
|
|
flash('Sicherheitsfehler: Die Sitzung ist abgelaufen. Bitte laden Sie die Seite neu und versuchen Sie es erneut.', 'error')
|
|
return redirect(url_for('page1_email')), 400
|
|
|
|
|
|
@app.errorhandler(429)
|
|
def handle_rate_limit_error(e):
|
|
"""Handle rate limit errors."""
|
|
return str(e), 429
|
|
|
|
|
|
def get_application_path(session_id):
|
|
"""Get the path to an application folder"""
|
|
return os.path.join(app.config['APPLICATIONS_FOLDER'], session_id)
|
|
|
|
|
|
def get_data_file_path(session_id):
|
|
"""Get the path to the application data YAML file"""
|
|
return os.path.join(get_application_path(session_id), 'data.yaml')
|
|
|
|
|
|
def get_attachments_path(session_id):
|
|
"""Get the path to the attachments folder"""
|
|
return os.path.join(get_application_path(session_id), 'attachments')
|
|
|
|
|
|
def load_application_data(session_id):
|
|
"""Load application data from YAML file"""
|
|
data_file = get_data_file_path(session_id)
|
|
if os.path.exists(data_file):
|
|
with open(data_file, 'r', encoding='utf-8') as f:
|
|
return yaml.safe_load(f)
|
|
return None
|
|
|
|
|
|
def save_application_data(session_id, data):
|
|
"""Save application data to YAML file"""
|
|
app_path = get_application_path(session_id)
|
|
Path(app_path).mkdir(parents=True, exist_ok=True)
|
|
|
|
data_file = get_data_file_path(session_id)
|
|
with open(data_file, 'w', encoding='utf-8') as f:
|
|
yaml.dump(data, f, allow_unicode=True, default_flow_style=False)
|
|
|
|
|
|
def send_resume_email(email, session_id, job_name):
|
|
"""Send email with resume link"""
|
|
resume_link = url_for('resume_application', session_id=session_id, _external=True)
|
|
|
|
subject = f"Ihre Bewerbung bei {app.config['COMPANY_NAME']} - Link zum Fortsetzen"
|
|
body = f"""Guten Tag,
|
|
|
|
vielen Dank für Ihr Interesse an der Position "{job_name}" bei {app.config['COMPANY_NAME']}.
|
|
|
|
Sie können Ihre Bewerbung jederzeit über den folgenden Link fortsetzen:
|
|
{resume_link}
|
|
|
|
Dieser Link bleibt gültig und Sie können Ihre Bewerbung jederzeit bearbeiten.
|
|
|
|
Mit freundlichen Grüßen
|
|
{app.config['COMPANY_NAME']}
|
|
"""
|
|
|
|
try:
|
|
msg = Message(subject=subject, recipients=[email], body=body)
|
|
mail.send(msg)
|
|
return True
|
|
except Exception as e:
|
|
app.logger.error(f"Failed to send email: {e}")
|
|
return False
|
|
|
|
|
|
def send_hr_notification(session_id, app_data):
|
|
"""Send HR notification email when application is submitted"""
|
|
if not app.config.get('HR_EMAIL'):
|
|
app.logger.warning("HR_EMAIL not configured, skipping HR notification")
|
|
return False
|
|
|
|
# Build application URL
|
|
application_url = f"{app.config['APPLICATION_URL_BASE']}/application/{session_id}/"
|
|
|
|
# Get personal info
|
|
personal_info = app_data.get('personal_info', {})
|
|
firstname = personal_info.get('firstname', 'N/A')
|
|
name = personal_info.get('name', 'N/A')
|
|
email = app_data.get('email', 'N/A')
|
|
job_name = app_data.get('job_name', 'N/A')
|
|
file_count = len(app_data.get('uploaded_files', []))
|
|
|
|
# Format timestamp
|
|
submitted_at = app_data.get('submitted_at', datetime.now().isoformat())
|
|
try:
|
|
timestamp = datetime.fromisoformat(submitted_at).strftime('%d.%m.%Y %H:%M')
|
|
except (ValueError, TypeError):
|
|
timestamp = submitted_at
|
|
|
|
subject = f"Neue Bewerbung eingegangen: {job_name}"
|
|
body = f"""Sehr geehrtes HR-Team,
|
|
|
|
es ist eine neue Bewerbung eingegangen:
|
|
|
|
Position: {job_name}
|
|
Name: {firstname} {name}
|
|
E-Mail: {email}
|
|
Eingereicht am: {timestamp}
|
|
Anzahl der hochgeladenen Dokumente: {file_count}
|
|
|
|
Sie können die vollständige Bewerbung hier einsehen:
|
|
{application_url}
|
|
|
|
Mit freundlichen Grüßen,
|
|
Ihr Bewerbungssystem
|
|
"""
|
|
|
|
try:
|
|
msg = Message(subject=subject, recipients=[app.config['HR_EMAIL']], body=body)
|
|
mail.send(msg)
|
|
return True
|
|
except Exception as e:
|
|
app.logger.error(f"Failed to send HR notification email: {e}")
|
|
return False
|
|
|
|
|
|
def validate_email(email):
|
|
"""Validate email format"""
|
|
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
|
return re.match(pattern, email) is not None
|
|
|
|
|
|
def validate_phone(phone):
|
|
"""Validate international phone format"""
|
|
# Accepts formats like: +41 79 123 45 67, +41791234567, etc.
|
|
# Remove spaces and check if it matches the pattern
|
|
pattern = r'^\+\d{1,3}[\s\d]{1,20}$'
|
|
if not re.match(pattern, phone):
|
|
return False
|
|
# Ensure there are at least some digits after the country code
|
|
digits_only = re.sub(r'\s', '', phone)
|
|
return len(digits_only) >= 5 # At least +XX XXX format
|
|
|
|
|
|
def validate_year(year):
|
|
"""Validate birth year"""
|
|
try:
|
|
year_int = int(year)
|
|
return (app.config['MIN_BIRTH_YEAR'] <= year_int <= app.config['MAX_BIRTH_YEAR']
|
|
and len(str(year)) == 4)
|
|
except ValueError:
|
|
return False
|
|
|
|
|
|
def validate_zip(zip_code):
|
|
"""Validate ZIP code"""
|
|
try:
|
|
zip_int = int(zip_code)
|
|
return len(str(zip_code)) <= app.config['MAX_ZIP_DIGITS']
|
|
except ValueError:
|
|
return False
|
|
|
|
|
|
def allowed_file(filename):
|
|
"""Check if file extension is allowed"""
|
|
return '.' in filename and \
|
|
filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']
|
|
|
|
|
|
@app.route('/')
|
|
def index():
|
|
"""Redirect to apply page"""
|
|
return redirect(url_for('page1_email'))
|
|
|
|
|
|
@app.route('/apply')
|
|
def page1_email():
|
|
"""Page 1: Email capture and session initialization"""
|
|
job_name = request.args.get('job', 'Offene Position')
|
|
return render_template('page1_email.html', job_name=job_name)
|
|
|
|
|
|
@app.route('/apply/submit-email', methods=['POST'])
|
|
def submit_email():
|
|
"""Process email submission and create session"""
|
|
# Check rate limit
|
|
allowed, wait_seconds = check_rate_limit()
|
|
if not allowed:
|
|
flash(f'Bitte warten Sie noch {wait_seconds} Sekunden vor der nächsten Eingabe.', 'error')
|
|
return redirect(url_for('page1_email')), 429
|
|
|
|
email = request.form.get('email', '').strip()
|
|
job_name = request.form.get('job_name', 'Offene Position')
|
|
|
|
# Validate email
|
|
if not validate_email(email):
|
|
flash('Bitte geben Sie eine gültige E-Mail-Adresse ein.', 'error')
|
|
return redirect(url_for('page1_email', job=job_name))
|
|
|
|
# Generate session ID
|
|
session_id = str(uuid.uuid4())
|
|
|
|
# Create application data
|
|
app_data = {
|
|
'session_id': session_id,
|
|
'email': email,
|
|
'job_name': job_name,
|
|
'current_page': 2,
|
|
'created_at': datetime.now().isoformat(),
|
|
'updated_at': datetime.now().isoformat(),
|
|
'personal_info': {},
|
|
'motivation_answers': {},
|
|
'uploaded_files': []
|
|
}
|
|
|
|
# Save application data
|
|
save_application_data(session_id, app_data)
|
|
|
|
# Send resume email
|
|
if send_resume_email(email, session_id, job_name):
|
|
flash('Eine E-Mail mit einem Link zum Fortsetzen wurde an Ihre Adresse gesendet.', 'success')
|
|
else:
|
|
flash('Warnung: Die E-Mail konnte nicht gesendet werden. Bitte notieren Sie sich diese URL.', 'warning')
|
|
|
|
# Store session ID in Flask session
|
|
session['application_id'] = session_id
|
|
|
|
return redirect(url_for('page2_personal', session_id=session_id))
|
|
|
|
|
|
@app.route('/apply/<session_id>/personal')
|
|
def page2_personal(session_id):
|
|
"""Page 2: Basic personal information"""
|
|
app_data = load_application_data(session_id)
|
|
if not app_data:
|
|
flash('Bewerbung nicht gefunden.', 'error')
|
|
return redirect(url_for('page1_email'))
|
|
|
|
return render_template('page2_personal.html',
|
|
session_id=session_id,
|
|
job_name=app_data['job_name'],
|
|
data=app_data.get('personal_info', {}))
|
|
|
|
|
|
@app.route('/apply/<session_id>/submit-personal', methods=['POST'])
|
|
def submit_personal(session_id):
|
|
"""Process personal information submission"""
|
|
# Check rate limit
|
|
allowed, wait_seconds = check_rate_limit()
|
|
if not allowed:
|
|
flash(f'Bitte warten Sie noch {wait_seconds} Sekunden vor der nächsten Eingabe.', 'error')
|
|
return redirect(url_for('page2_personal', session_id=session_id)), 429
|
|
|
|
app_data = load_application_data(session_id)
|
|
if not app_data:
|
|
flash('Bewerbung nicht gefunden.', 'error')
|
|
return redirect(url_for('page1_email'))
|
|
|
|
# Get form data
|
|
name = request.form.get('name', '').strip()
|
|
firstname = request.form.get('firstname', '').strip()
|
|
address = request.form.get('address', '').strip()
|
|
zip_code = request.form.get('zip_code', '').strip()
|
|
city = request.form.get('city', '').strip()
|
|
phone = request.form.get('phone', '').strip()
|
|
birth_year = request.form.get('birth_year', '').strip()
|
|
civil_status = request.form.get('civil_status', '').strip()
|
|
|
|
# Validate required fields
|
|
errors = []
|
|
|
|
if not name or len(name) > app.config['MAX_STRING_LENGTH']:
|
|
errors.append('Name ist erforderlich und darf maximal 255 Zeichen lang sein.')
|
|
|
|
if not firstname or len(firstname) > app.config['MAX_STRING_LENGTH']:
|
|
errors.append('Vorname ist erforderlich und darf maximal 255 Zeichen lang sein.')
|
|
|
|
if not address or len(address) > app.config['MAX_STRING_LENGTH']:
|
|
errors.append('Adresse ist erforderlich und darf maximal 255 Zeichen lang sein.')
|
|
|
|
if not zip_code or not validate_zip(zip_code):
|
|
errors.append('PLZ ist erforderlich und darf maximal 10 Ziffern lang sein.')
|
|
|
|
if not city or len(city) > app.config['MAX_STRING_LENGTH']:
|
|
errors.append('Stadt ist erforderlich und darf maximal 255 Zeichen lang sein.')
|
|
|
|
if not phone or not validate_phone(phone):
|
|
errors.append('Telefonnummer ist erforderlich und muss im internationalen Format sein (z.B. +41 79 123 45 67).')
|
|
|
|
if not birth_year or not validate_year(birth_year):
|
|
errors.append(f'Geburtsjahr ist erforderlich und muss zwischen {app.config["MIN_BIRTH_YEAR"]} und {app.config["MAX_BIRTH_YEAR"]} liegen.')
|
|
|
|
if civil_status and len(civil_status) > app.config['MAX_STRING_LENGTH']:
|
|
errors.append('Zivilstand darf maximal 255 Zeichen lang sein.')
|
|
|
|
if errors:
|
|
for error in errors:
|
|
flash(error, 'error')
|
|
return redirect(url_for('page2_personal', session_id=session_id))
|
|
|
|
# Save personal information
|
|
app_data['personal_info'] = {
|
|
'name': name,
|
|
'firstname': firstname,
|
|
'address': address,
|
|
'zip_code': zip_code,
|
|
'city': city,
|
|
'phone': phone,
|
|
'birth_year': birth_year,
|
|
'civil_status': civil_status
|
|
}
|
|
app_data['current_page'] = 3
|
|
app_data['updated_at'] = datetime.now().isoformat()
|
|
|
|
save_application_data(session_id, app_data)
|
|
|
|
return redirect(url_for('page3_motivation', session_id=session_id))
|
|
|
|
|
|
@app.route('/apply/<session_id>/motivation')
|
|
def page3_motivation(session_id):
|
|
"""Page 3: Motivation and qualification questions"""
|
|
app_data = load_application_data(session_id)
|
|
if not app_data:
|
|
flash('Bewerbung nicht gefunden.', 'error')
|
|
return redirect(url_for('page1_email'))
|
|
|
|
return render_template('page3_motivation.html',
|
|
session_id=session_id,
|
|
job_name=app_data['job_name'],
|
|
data=app_data.get('motivation_answers', {}))
|
|
|
|
|
|
@app.route('/apply/<session_id>/submit-motivation', methods=['POST'])
|
|
def submit_motivation(session_id):
|
|
"""Process motivation questions submission"""
|
|
# Check rate limit
|
|
allowed, wait_seconds = check_rate_limit()
|
|
if not allowed:
|
|
flash(f'Bitte warten Sie noch {wait_seconds} Sekunden vor der nächsten Eingabe.', 'error')
|
|
return redirect(url_for('page3_motivation', session_id=session_id)), 429
|
|
|
|
app_data = load_application_data(session_id)
|
|
if not app_data:
|
|
flash('Bewerbung nicht gefunden.', 'error')
|
|
return redirect(url_for('page1_email'))
|
|
|
|
# Get form data
|
|
current_job = request.form.get('current_job', '').strip()
|
|
motivation = request.form.get('motivation', '').strip()
|
|
qualifications = request.form.get('qualifications', '').strip()
|
|
salary = request.form.get('salary', '').strip()
|
|
|
|
# Validate length
|
|
errors = []
|
|
|
|
if current_job and len(current_job) > app.config['MAX_TEXT_AREA_LENGTH']:
|
|
errors.append(f'Die Beschreibung Ihrer beruflichen Situation darf maximal {app.config["MAX_TEXT_AREA_LENGTH"]} Zeichen lang sein.')
|
|
|
|
if motivation and len(motivation) > app.config['MAX_TEXT_AREA_LENGTH']:
|
|
errors.append(f'Die Motivationsbeschreibung darf maximal {app.config["MAX_TEXT_AREA_LENGTH"]} Zeichen lang sein.')
|
|
|
|
if qualifications and len(qualifications) > app.config['MAX_TEXT_AREA_LENGTH']:
|
|
errors.append(f'Die Qualifikationsbeschreibung darf maximal {app.config["MAX_TEXT_AREA_LENGTH"]} Zeichen lang sein.')
|
|
|
|
if salary and len(salary) > app.config['MAX_TEXT_AREA_LENGTH']:
|
|
errors.append(f'Die Gehaltsvorstellungen dürfen maximal {app.config["MAX_TEXT_AREA_LENGTH"]} Zeichen lang sein.')
|
|
|
|
if errors:
|
|
for error in errors:
|
|
flash(error, 'error')
|
|
return redirect(url_for('page3_motivation', session_id=session_id))
|
|
|
|
# Save motivation answers
|
|
app_data['motivation_answers'] = {
|
|
'current_job': current_job,
|
|
'motivation': motivation,
|
|
'qualifications': qualifications,
|
|
'salary': salary
|
|
}
|
|
app_data['current_page'] = 4
|
|
app_data['updated_at'] = datetime.now().isoformat()
|
|
|
|
save_application_data(session_id, app_data)
|
|
|
|
return redirect(url_for('page4_upload', session_id=session_id))
|
|
|
|
|
|
@app.route('/apply/<session_id>/upload')
|
|
def page4_upload(session_id):
|
|
"""Page 4: Document upload"""
|
|
app_data = load_application_data(session_id)
|
|
if not app_data:
|
|
flash('Bewerbung nicht gefunden.', 'error')
|
|
return redirect(url_for('page1_email'))
|
|
|
|
return render_template('page4_upload.html',
|
|
session_id=session_id,
|
|
job_name=app_data['job_name'],
|
|
uploaded_files=app_data.get('uploaded_files', []),
|
|
max_files=app.config['MAX_FILES'])
|
|
|
|
|
|
@app.route('/apply/<session_id>/upload-file', methods=['POST'])
|
|
def upload_file(session_id):
|
|
"""Handle file upload"""
|
|
# Check rate limit
|
|
allowed, wait_seconds = check_rate_limit()
|
|
if not allowed:
|
|
flash(f'Bitte warten Sie noch {wait_seconds} Sekunden vor der nächsten Eingabe.', 'error')
|
|
return redirect(url_for('page4_upload', session_id=session_id)), 429
|
|
|
|
app_data = load_application_data(session_id)
|
|
if not app_data:
|
|
flash('Bewerbung nicht gefunden.', 'error')
|
|
return redirect(url_for('page1_email'))
|
|
|
|
# Check file count
|
|
if len(app_data.get('uploaded_files', [])) >= app.config['MAX_FILES']:
|
|
flash(f'Sie können maximal {app.config["MAX_FILES"]} Dokumente hochladen.', 'error')
|
|
return redirect(url_for('page4_upload', session_id=session_id))
|
|
|
|
# Check if file was uploaded
|
|
if 'file' not in request.files:
|
|
flash('Keine Datei ausgewählt.', 'error')
|
|
return redirect(url_for('page4_upload', session_id=session_id))
|
|
|
|
file = request.files['file']
|
|
|
|
if file.filename == '':
|
|
flash('Keine Datei ausgewählt.', 'error')
|
|
return redirect(url_for('page4_upload', session_id=session_id))
|
|
|
|
# Validate file
|
|
if not allowed_file(file.filename):
|
|
flash('Ungültiger Dateityp. Erlaubte Formate: PDF, DOC, DOCX, TXT, JPG, JPEG, PNG', 'error')
|
|
return redirect(url_for('page4_upload', session_id=session_id))
|
|
|
|
# Check file size
|
|
file.seek(0, os.SEEK_END)
|
|
file_size = file.tell()
|
|
file.seek(0)
|
|
|
|
if file_size > app.config['MAX_FILE_SIZE']:
|
|
flash(f'Die Datei ist zu groß. Maximale Größe: 4 MB', 'error')
|
|
return redirect(url_for('page4_upload', session_id=session_id))
|
|
|
|
# Save file
|
|
filename = secure_filename(file.filename)
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
unique_filename = f"{timestamp}_{filename}"
|
|
|
|
attachments_path = get_attachments_path(session_id)
|
|
Path(attachments_path).mkdir(parents=True, exist_ok=True)
|
|
|
|
file_path = os.path.join(attachments_path, unique_filename)
|
|
file.save(file_path)
|
|
|
|
# Update application data
|
|
if 'uploaded_files' not in app_data:
|
|
app_data['uploaded_files'] = []
|
|
|
|
app_data['uploaded_files'].append({
|
|
'original_name': filename,
|
|
'stored_name': unique_filename,
|
|
'uploaded_at': datetime.now().isoformat(),
|
|
'size': file_size
|
|
})
|
|
app_data['updated_at'] = datetime.now().isoformat()
|
|
|
|
save_application_data(session_id, app_data)
|
|
|
|
flash('Datei erfolgreich hochgeladen.', 'success')
|
|
return redirect(url_for('page4_upload', session_id=session_id))
|
|
|
|
|
|
@app.route('/apply/<session_id>/remove-file/<int:file_index>', methods=['POST'])
|
|
def remove_file(session_id, file_index):
|
|
"""Remove an uploaded file"""
|
|
app_data = load_application_data(session_id)
|
|
if not app_data:
|
|
flash('Bewerbung nicht gefunden.', 'error')
|
|
return redirect(url_for('page1_email'))
|
|
|
|
uploaded_files = app_data.get('uploaded_files', [])
|
|
|
|
if 0 <= file_index < len(uploaded_files):
|
|
file_info = uploaded_files[file_index]
|
|
|
|
# Delete physical file
|
|
file_path = os.path.join(get_attachments_path(session_id), file_info['stored_name'])
|
|
if os.path.exists(file_path):
|
|
os.remove(file_path)
|
|
|
|
# Remove from data
|
|
uploaded_files.pop(file_index)
|
|
app_data['updated_at'] = datetime.now().isoformat()
|
|
save_application_data(session_id, app_data)
|
|
|
|
flash('Datei erfolgreich entfernt.', 'success')
|
|
else:
|
|
flash('Datei nicht gefunden.', 'error')
|
|
|
|
return redirect(url_for('page4_upload', session_id=session_id))
|
|
|
|
|
|
@app.route('/apply/<session_id>/submit-application', methods=['POST'])
|
|
def submit_application(session_id):
|
|
"""Submit final application"""
|
|
# Check rate limit
|
|
allowed, wait_seconds = check_rate_limit()
|
|
if not allowed:
|
|
flash(f'Bitte warten Sie noch {wait_seconds} Sekunden vor der nächsten Eingabe.', 'error')
|
|
return redirect(url_for('page4_upload', session_id=session_id)), 429
|
|
|
|
app_data = load_application_data(session_id)
|
|
if not app_data:
|
|
flash('Bewerbung nicht gefunden.', 'error')
|
|
return redirect(url_for('page1_email'))
|
|
|
|
# Mark as submitted
|
|
app_data['current_page'] = 5
|
|
app_data['submitted_at'] = datetime.now().isoformat()
|
|
app_data['updated_at'] = datetime.now().isoformat()
|
|
app_data['status'] = 'submitted'
|
|
|
|
save_application_data(session_id, app_data)
|
|
|
|
# Send HR notification
|
|
send_hr_notification(session_id, app_data)
|
|
|
|
return redirect(url_for('page5_confirmation', session_id=session_id))
|
|
|
|
|
|
@app.route('/apply/<session_id>/confirmation')
|
|
def page5_confirmation(session_id):
|
|
"""Page 5: Confirmation page"""
|
|
app_data = load_application_data(session_id)
|
|
if not app_data:
|
|
flash('Bewerbung nicht gefunden.', 'error')
|
|
return redirect(url_for('page1_email'))
|
|
|
|
return render_template('page5_confirmation.html',
|
|
session_id=session_id,
|
|
job_name=app_data['job_name'],
|
|
email=app_data['email'])
|
|
|
|
|
|
@app.route('/application/<session_id>/')
|
|
def view_application(session_id):
|
|
"""View complete application details (for HR)"""
|
|
app_data = load_application_data(session_id)
|
|
if not app_data:
|
|
abort(404)
|
|
|
|
return render_template('application_view.html',
|
|
session_id=session_id,
|
|
app_data=app_data)
|
|
|
|
|
|
@app.route('/application/<session_id>/download/<filename>')
|
|
def download_file(session_id, filename):
|
|
"""Download an uploaded file"""
|
|
app_data = load_application_data(session_id)
|
|
if not app_data:
|
|
abort(404)
|
|
|
|
# Verify the file is in the uploaded files list
|
|
uploaded_files = app_data.get('uploaded_files', [])
|
|
file_found = False
|
|
|
|
for file_info in uploaded_files:
|
|
if file_info.get('stored_name') == filename:
|
|
file_found = True
|
|
break
|
|
|
|
if not file_found:
|
|
abort(404)
|
|
|
|
# Serve the file
|
|
attachments_path = get_attachments_path(session_id)
|
|
file_path = os.path.join(attachments_path, filename)
|
|
|
|
if not os.path.exists(file_path):
|
|
abort(404)
|
|
|
|
return send_from_directory(attachments_path, filename, as_attachment=True)
|
|
|
|
|
|
@app.route('/resume/<session_id>')
|
|
def resume_application(session_id):
|
|
"""Resume application from email link"""
|
|
app_data = load_application_data(session_id)
|
|
if not app_data:
|
|
flash('Bewerbung nicht gefunden.', 'error')
|
|
return redirect(url_for('page1_email'))
|
|
|
|
# Store session ID in Flask session
|
|
session['application_id'] = session_id
|
|
|
|
# Redirect to current page
|
|
current_page = app_data.get('current_page', 2)
|
|
|
|
if current_page == 2:
|
|
return redirect(url_for('page2_personal', session_id=session_id))
|
|
elif current_page == 3:
|
|
return redirect(url_for('page3_motivation', session_id=session_id))
|
|
elif current_page == 4:
|
|
return redirect(url_for('page4_upload', session_id=session_id))
|
|
elif current_page == 5:
|
|
return redirect(url_for('page5_confirmation', session_id=session_id))
|
|
else:
|
|
return redirect(url_for('page2_personal', session_id=session_id))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
app.run(debug=True)
|