haldis/app/views/general.py
redfast00 f5a8303362
Don't crash when the theme does not have any options
Hotfix for prod, pushing straight to master
2020-12-04 05:17:34 +01:00

219 lines
6.1 KiB
Python

"Script to generate the general views of Haldis"
import os
from datetime import datetime, timedelta
import yaml
from typing import Optional
from flask import Flask, render_template, make_response
from flask import request, jsonify
from flask import Blueprint, abort
from flask import current_app as app
from flask import send_from_directory, url_for
from flask_login import login_required
from utils import first
from hlds.definitions import location_definitions
from hlds.models import Location
from models import Order
# import views
from views.order import get_orders
import json
from flask import jsonify
general_bp = Blueprint("general_bp", __name__)
with open(os.path.join(os.path.dirname(__file__), "themes.yml"), "r") as _stream:
_theme_data = yaml.safe_load(_stream)
THEME_OPTIONS = _theme_data["options"]
THEMES = _theme_data["themes"]
@general_bp.route("/")
def home() -> str:
"Generate the home view"
prev_day = datetime.now() - timedelta(days=1)
recently_closed = get_orders(
((Order.stoptime > prev_day) & (Order.stoptime < datetime.now()))
)
return render_template(
"home.html", orders=get_orders(), recently_closed=recently_closed
)
def is_theme_active(theme, now):
theme_type = theme["type"]
if theme_type == "static":
return True
if theme_type == "seasonal":
start_day, start_month = map(int, theme["start"].split("/"))
start_datetime = datetime(year=now.year, day=start_day, month=start_month)
end_day, end_month = map(int, theme["end"].split("/"))
end_year = now.year + (1 if start_month > end_month else 0)
end_datetime = datetime(year=end_year, day=end_day, month=end_month)
return start_datetime <= now <= end_datetime
raise Exception("Unknown theme type {}".format(theme_type))
def get_theme_css(theme, options):
# Build filename
# Each option's chosen value is appended, to get something like mytheme_darkmode_heavy.css
filename = theme["file"]
for option in theme.get("options", []):
theme_name = theme["name"]
assert option in THEME_OPTIONS, f"Theme `{theme_name}` uses undefined option `{option}`"
chosen_value = options[option]
possible_values = list(THEME_OPTIONS[option].keys())
value = chosen_value if chosen_value in possible_values \
else THEME_OPTIONS[option]["_default"]
filename += "_" + value
filename += ".css"
theme_css_dir = "static/css/themes/"
return os.path.join(app.root_path, theme_css_dir, filename)
def get_active_themes():
now = datetime.now()
return [theme for theme in THEMES if is_theme_active(theme, now)]
@general_bp.route("/theme.css")
def theme_css():
"Send appropriate CSS for current theme"
themes = get_active_themes()
theme_name = request.cookies.get("theme", None)
theme = first((t for t in themes if t["file"] == theme_name), default=themes[-1])
options = {
name: request.cookies.get("theme_" + name, None)
for name in ["atmosphere", "performance"]
}
path = get_theme_css(theme, options)
with open(path) as f:
response = make_response(f.read())
response.headers["Content-Type"] = "text/css"
return response
@general_bp.route("/current_theme.js")
def current_theme_js():
themes = get_active_themes()
selected_theme_name = request.cookies.get("theme", None)
matching_theme = first((t for t in themes if t["file"] == selected_theme_name))
cur_theme = matching_theme or themes[-1]
response = make_response(rf'''
var currentTheme = {json.dumps(cur_theme['file'])};
var currentThemeOptions = {json.dumps(cur_theme.get('options', []))};
''')
response.headers["Content-Type"] = "text/javascript"
# Theme name that is not valid at this moment: delete cookie
if matching_theme is None:
response.delete_cookie("theme", path="/")
return response
@general_bp.route("/map")
def map_view() -> str:
"Generate the map view"
return render_template("maps.html", locations=location_definitions)
@general_bp.route("/location")
def locations() -> str:
"Generate the location view"
return render_template("locations.html", locations=location_definitions)
@general_bp.route("/location/<location_id>")
def location(location_id) -> str:
"Generate the location view given an id"
loc = first(filter(lambda l: l.id == location_id, location_definitions))
if loc is None:
abort(404)
return render_template("location.html", location=loc, title=loc.name)
@general_bp.route("/location/<location_id>/<dish_id>")
def location_dish(location_id, dish_id) -> str:
loc: Optional[Location] = first(
filter(lambda l: l.id == location_id, location_definitions)
)
if loc is None:
abort(404)
dish = loc.dish_by_id(dish_id)
if dish is None:
abort(404)
return jsonify([
{
"type": c[0],
"id": c[1].id,
"name": c[1].name,
"description": c[1].description,
"options": [
{
"id": o.id,
"name": o.name,
"description": o.description,
"price": o.price,
"tags": o.tags,
}
for o in c[1].options
],
}
for c in dish.choices
])
@general_bp.route("/about/")
def about() -> str:
"Generate the about view"
return render_template("about.html")
@general_bp.route("/profile/")
@login_required
def profile() -> str:
"Generate the profile view"
return render_template("profile.html", themes_list=get_active_themes())
@general_bp.route("/favicon.ico")
def favicon() -> str:
"Generate the favicon"
# pylint: disable=R1705
if not get_orders((Order.stoptime > datetime.now())):
return send_from_directory(
os.path.join(app.root_path, "static"),
"favicon.ico",
mimetype="image/x-icon",
)
else:
return send_from_directory(
os.path.join(app.root_path, "static"),
"favicon_orange.ico",
mimetype="image/x-icon",
)