212 lines
6.5 KiB
Python
212 lines
6.5 KiB
Python
from datetime import date
|
|
|
|
from flask import Flask, render_template, redirect, url_for, request, flash, jsonify
|
|
from flask_login import (
|
|
LoginManager,
|
|
login_user,
|
|
logout_user,
|
|
login_required,
|
|
current_user,
|
|
)
|
|
|
|
from config import Config
|
|
from models import db, User, Trip, Stop, VERKEHRSMITTEL_OPTIONEN
|
|
from stops_import import normalize_name, register_cli
|
|
|
|
login_manager = LoginManager()
|
|
login_manager.login_view = "login"
|
|
|
|
|
|
def create_app():
|
|
app = Flask(__name__)
|
|
app.config.from_object(Config)
|
|
|
|
db.init_app(app)
|
|
login_manager.init_app(app)
|
|
|
|
with app.app_context():
|
|
db.create_all()
|
|
|
|
register_routes(app)
|
|
register_cli(app)
|
|
return app
|
|
|
|
|
|
@login_manager.user_loader
|
|
def load_user(user_id):
|
|
return User.query.get(int(user_id))
|
|
|
|
|
|
def register_routes(app):
|
|
@app.route("/")
|
|
def index():
|
|
if current_user.is_authenticated:
|
|
return redirect(url_for("dashboard"))
|
|
return redirect(url_for("login"))
|
|
|
|
@app.route("/register", methods=["GET", "POST"])
|
|
def register():
|
|
if request.method == "POST":
|
|
username = request.form["username"].strip()
|
|
email = request.form["email"].strip().lower()
|
|
password = request.form["password"]
|
|
|
|
if User.query.filter(
|
|
(User.username == username) | (User.email == email)
|
|
).first():
|
|
flash("Benutzername oder E-Mail bereits vergeben.", "error")
|
|
return redirect(url_for("register"))
|
|
|
|
user = User(username=username, email=email)
|
|
user.set_password(password)
|
|
db.session.add(user)
|
|
db.session.commit()
|
|
|
|
login_user(user)
|
|
return redirect(url_for("dashboard"))
|
|
|
|
return render_template("register.html")
|
|
|
|
@app.route("/login", methods=["GET", "POST"])
|
|
def login():
|
|
if request.method == "POST":
|
|
username = request.form["username"].strip()
|
|
password = request.form["password"]
|
|
|
|
user = User.query.filter_by(username=username).first()
|
|
if user is None or not user.check_password(password):
|
|
flash("Login fehlgeschlagen. Bitte prüfe deine Daten.", "error")
|
|
return redirect(url_for("login"))
|
|
|
|
login_user(user)
|
|
return redirect(url_for("dashboard"))
|
|
|
|
return render_template("login.html")
|
|
|
|
@app.route("/logout")
|
|
@login_required
|
|
def logout():
|
|
logout_user()
|
|
return redirect(url_for("login"))
|
|
|
|
@app.route("/dashboard")
|
|
@login_required
|
|
def dashboard():
|
|
fahrten = (
|
|
Trip.query.filter_by(user_id=current_user.id)
|
|
.order_by(Trip.trip_date.desc())
|
|
.all()
|
|
)
|
|
return render_template("dashboard.html", fahrten=fahrten)
|
|
|
|
@app.route("/api/stops")
|
|
@login_required
|
|
def api_stops():
|
|
"""Autocomplete for the origin/destination fields."""
|
|
query = request.args.get("q", "").strip()
|
|
if len(query) < 2:
|
|
return jsonify([])
|
|
|
|
needle = normalize_name(query).replace("\\", "\\\\")
|
|
needle = needle.replace("%", r"\%").replace("_", r"\_")
|
|
order = (db.func.length(Stop.name), Stop.name)
|
|
|
|
results = (
|
|
Stop.query.filter(Stop.name_normalized.like(needle + "%", escape="\\"))
|
|
.order_by(*order)
|
|
.limit(8)
|
|
.all()
|
|
)
|
|
if len(results) < 8:
|
|
seen = {stop.id for stop in results}
|
|
for stop in (
|
|
Stop.query.filter(
|
|
Stop.name_normalized.like("%" + needle + "%", escape="\\")
|
|
)
|
|
.order_by(*order)
|
|
.limit(20)
|
|
.all()
|
|
):
|
|
if stop.id not in seen:
|
|
results.append(stop)
|
|
if len(results) >= 8:
|
|
break
|
|
|
|
return jsonify(
|
|
[
|
|
{
|
|
"id": stop.id,
|
|
"name": stop.name,
|
|
"type": stop.stop_type,
|
|
"municipality": stop.municipality,
|
|
"lat": stop.latitude,
|
|
"lon": stop.longitude,
|
|
}
|
|
for stop in results
|
|
]
|
|
)
|
|
|
|
def parse_trip_date(raw_value):
|
|
raw_value = (raw_value or "").strip()
|
|
try:
|
|
return date.fromisoformat(raw_value) if raw_value else date.today()
|
|
except ValueError:
|
|
return date.today()
|
|
|
|
def resolve_stop_id(raw_id):
|
|
"""Return a valid Stop.id for a submitted value, or None (free text)."""
|
|
if not raw_id:
|
|
return None
|
|
try:
|
|
stop = db.session.get(Stop, int(raw_id))
|
|
except (TypeError, ValueError):
|
|
return None
|
|
return stop.id if stop is not None else None
|
|
|
|
@app.route("/fahrt/neu", methods=["GET", "POST"])
|
|
@login_required
|
|
def neue_fahrt():
|
|
if request.method == "POST":
|
|
fahrt = Trip(
|
|
user_id=current_user.id,
|
|
transport_mode=request.form["transport_mode"],
|
|
line=request.form.get("line", "").strip(),
|
|
origin=request.form["origin"].strip(),
|
|
origin_stop_id=resolve_stop_id(request.form.get("origin_stop_id")),
|
|
destination=request.form["destination"].strip(),
|
|
destination_stop_id=resolve_stop_id(
|
|
request.form.get("destination_stop_id")
|
|
),
|
|
trip_date=parse_trip_date(request.form.get("trip_date")),
|
|
rating=int(request.form["rating"]),
|
|
comment=request.form.get("comment", "").strip(),
|
|
)
|
|
db.session.add(fahrt)
|
|
db.session.commit()
|
|
flash("Fahrt gespeichert.", "success")
|
|
return redirect(url_for("dashboard"))
|
|
|
|
return render_template(
|
|
"add_entry.html",
|
|
verkehrsmittel_optionen=VERKEHRSMITTEL_OPTIONEN,
|
|
heute=date.today().isoformat(),
|
|
)
|
|
|
|
@app.route("/fahrt/<int:fahrt_id>/loeschen", methods=["POST"])
|
|
@login_required
|
|
def fahrt_loeschen(fahrt_id):
|
|
fahrt = Trip.query.get_or_404(fahrt_id)
|
|
if fahrt.user_id != current_user.id:
|
|
flash("Nicht erlaubt.", "error")
|
|
return redirect(url_for("dashboard"))
|
|
db.session.delete(fahrt)
|
|
db.session.commit()
|
|
flash("Fahrt gelöscht.", "success")
|
|
return redirect(url_for("dashboard"))
|
|
|
|
|
|
app = create_app()
|
|
|
|
if __name__ == "__main__":
|
|
app.run(host="0.0.0.0", port=5000, debug=True)
|