"Script to generate the general views of Haldis" import json import os from datetime import datetime, timedelta from typing import Optional import yaml from flask import Blueprint, Flask, abort from flask import current_app as app from flask import (jsonify, make_response, render_template, request, send_from_directory, url_for) from flask_login import login_required from hlds.definitions import location_definitions from hlds.models import Location from models import Order from utils import first # import views from views.order import get_orders general_bp = Blueprint("general_bp", __name__) with open(os.path.join(os.path.dirname(__file__), "themes.yml")) 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(f"Unknown theme type {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/") 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//") 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", )