Files
raceplaner/app/main.py

193 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from flask import Flask, render_template, request, redirect, url_for, jsonify, make_response, session
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from werkzeug.security import generate_password_hash, check_password_hash
import os, json
from datetime import datetime, timedelta
from io import BytesIO
from xhtml2pdf import pisa
from sqlalchemy import text
app = Flask(__name__)
# --- Konfiguration ---
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', 'sqlite:////app/data/raceplanner.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = 'renn-strategie-2026-final-v3'
app.config['REMEMBER_COOKIE_DURATION'] = timedelta(days=31)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=31)
SCENARIO_DIR = '/app/data/scenarios/'
db = SQLAlchemy(app)
migrate = Migrate(app, db)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
if not os.path.exists(SCENARIO_DIR):
os.makedirs(SCENARIO_DIR)
# --- Models ---
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(200), nullable=False)
theme = db.Column(db.String(20), default='light') # 'light' oder 'dark'
class RaceConfig(db.Model):
id = db.Column(db.Integer, primary_key=True)
start_time = db.Column(db.DateTime, default=datetime.now)
total_duration_h = db.Column(db.Integer, default=24)
class Driver(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
car_number = db.Column(db.Integer)
avg_lap_time = db.Column(db.Float)
cons_per_lap = db.Column(db.Float)
class Stint(db.Model):
id = db.Column(db.Integer, primary_key=True)
car_number = db.Column(db.Integer)
driver_id = db.Column(db.Integer, db.ForeignKey('driver.id'))
order = db.Column(db.Integer)
change_tires = db.Column(db.Boolean, default=False)
driver = db.relationship('Driver', backref='stints')
@login_manager.user_loader
def load_user(user_id):
return db.session.get(User, int(user_id))
# --- Hilfsfunktionen ---
def get_calculated_schedule(car_num):
config = RaceConfig.query.first()
if not config: return []
stints = Stint.query.filter_by(car_number=car_num).order_by(Stint.order).all()
schedule = []
current_time = config.start_time
fuel_capacity = 120.0
for i, stint in enumerate(stints):
driver = stint.driver
if not driver: continue
laps_possible = int(fuel_capacity / driver.cons_per_lap)
duration_sec = laps_possible * driver.avg_lap_time
start_str = current_time.strftime('%H:%M')
end_time = current_time + timedelta(seconds=duration_sec)
schedule.append({
'id': stint.id,
'number': i + 1,
'start': start_str,
'end': end_time.strftime('%H:%M'),
'date': current_time.strftime('%d.%m. '),
'driver_name': driver.name,
'driver_id': driver.id,
'laps': laps_possible,
'fuel': round(fuel_capacity, 1),
'change_tires': stint.change_tires,
'is_finish': (current_time + timedelta(seconds=duration_sec)) >= (config.start_time + timedelta(hours=config.total_duration_h))
})
current_time = end_time
return schedule
# --- Routen ---
@app.route('/')
@login_required
def index():
drivers = Driver.query.all()
config = RaceConfig.query.first()
car1_sch = get_calculated_schedule(1)
car2_sch = get_calculated_schedule(2)
return render_template('index.html', car1=car1_sch, car2=car2_sch, drivers=drivers, config=config)
@app.route('/update_theme', methods=['POST'])
@login_required
def update_theme():
data = request.get_json()
new_theme = data.get('theme')
if new_theme in ['light', 'dark']:
current_user.theme = new_theme
db.session.commit()
return jsonify({'status': 'success'})
return jsonify({'status': 'error'}), 400
@app.route('/update_stint_driver', methods=['POST'])
@login_required
def update_stint_driver():
data = request.json
s = db.session.get(Stint, data['stint_id'])
s.driver_id = data['driver_id']
db.session.commit()
return jsonify({'status': 'ok'})
@app.route('/reorder_stints', methods=['POST'])
@login_required
def reorder_stints():
order = request.json['order']
for idx, sid in enumerate(order):
s = db.session.get(Stint, int(sid))
s.order = idx
db.session.commit()
return jsonify({'status': 'ok'})
@app.route('/export_pdf/<int:car_num>')
@login_required
def export_pdf(car_num):
config = RaceConfig.query.first()
sch = get_calculated_schedule(car_num)
html = render_template('pdf_template.html', schedule=sch, config=config, title=f"Strategie Fahrzeug #{car_num}")
res = BytesIO()
pisa.CreatePDF(BytesIO(html.encode("utf-8")), dest=res)
response = make_response(res.getvalue())
response.headers['Content-Type'] = 'application/pdf'
return response
@app.route('/driver/<int:id>')
@login_required
def driver_detail(id):
driver = db.session.get(Driver, id)
full_sch = get_calculated_schedule(driver.car_number)
stints = [s for s in full_sch if s['driver_id'] == id]
return render_template('driver_detail.html', driver=driver, stints=stints)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
u = User.query.filter_by(username=request.form.get('username')).first()
if u and check_password_hash(u.password, request.form.get('password')):
session.permanent = True
login_user(u, remember=True)
return redirect(url_for('index'))
return render_template('login.html')
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('login'))
if __name__ == '__main__':
with app.app_context():
db.create_all()
# Manueller Fix: Spalte 'theme' hinzufügen, falls sie fehlt (SQLite-spezifisch)
try:
db.session.execute(text("ALTER TABLE user ADD COLUMN theme VARCHAR(20) DEFAULT 'light'"))
db.session.commit()
except Exception:
# Fehler tritt auf, wenn Spalte bereits existiert das ist ok
db.session.rollback()
if not User.query.filter_by(username='mscaltenbach').first():
pw = generate_password_hash('admin123')
db.session.add(User(username='mscaltenbach', password=pw, theme='light'))
if not RaceConfig.query.first():
db.session.add(RaceConfig())
db.session.commit()
app.run(host='0.0.0.0', port=5000, debug=True)