Files
workout/app.py
Peter Stockings f70438e4e4 Refactor dashboard
2025-01-27 14:46:20 +11:00

652 lines
27 KiB
Python

from datetime import datetime, date, timedelta
from dateutil.relativedelta import relativedelta
import os
from flask import Flask, abort, render_template, redirect, request, url_for
import jinja_partials
from jinja2_fragments import render_block
from decorators import validate_person, validate_topset, validate_workout
from db import DataBase
from utils import convert_str_to_date, generate_plot
from flask_htmx import HTMX
import minify_html
from urllib.parse import quote
import os
from dotenv import load_dotenv
# Load environment variables from .env file in non-production environments
if os.environ.get('FLASK_ENV') != 'production':
load_dotenv()
app = Flask(__name__)
app.config.from_pyfile('config.py')
jinja_partials.register_extensions(app)
db = DataBase(app)
htmx = HTMX(app)
@app.after_request
def response_minify(response):
"""
minify html response to decrease site traffic
"""
if response.content_type == u'text/html; charset=utf-8':
response.set_data(
minify_html.minify(response.get_data(
as_text=True), minify_js=True, remove_processing_instructions=True)
)
return response
return response
@ app.route("/")
def dashboard():
selected_people_ids = request.args.getlist('person_id', type=int)
min_date = request.args.get('min_date', type=convert_str_to_date)
max_date = request.args.get('max_date', type=convert_str_to_date)
selected_exercise_ids = request.args.getlist('exercise_id', type=int)
if not selected_people_ids and htmx.trigger_name != 'person_id':
selected_people_ids = db.dashboard.get_people_ids()
if not min_date or not max_date:
db_min_date, db_max_date = db.dashboard.get_earliest_and_latest_workout_dates(selected_people_ids)
min_date = min_date or db_min_date
max_date = max_date or db_max_date
if not selected_exercise_ids and htmx.trigger_name != 'exercise_id':
selected_exercise_ids = db.dashboard.list_of_performed_exercise_ids(selected_people_ids, min_date, max_date)
people = db.dashboard.get_people_with_selection(selected_people_ids)
exercises = db.dashboard.get_exercises_with_selection(selected_people_ids, min_date, max_date, selected_exercise_ids)
tags = db.get_tags_for_dashboard()
dashboard = db.dashboard.get(selected_people_ids, min_date, max_date, selected_exercise_ids)
# Render the appropriate response for HTMX or full page
render_args = {
**dashboard,
"people": people,
"exercises": exercises,
"tags": tags,
"selected_people_ids": selected_people_ids,
"max_date": max_date,
"min_date": min_date,
"selected_exercise_ids": selected_exercise_ids
}
if htmx:
return render_block(app.jinja_env, 'dashboard.html', 'content', **render_args)
return render_template('dashboard.html', **render_args)
@ app.route("/person/list", methods=['GET'])
def get_person_list():
people = db.get_people_and_workout_count(-1)
return render_template('partials/people_link.html', people=people)
@ app.route("/person/<int:person_id>/workout/overview", methods=['GET'])
def person_overview(person_id):
min_date = request.args.get('min_date', type=convert_str_to_date)
max_date = request.args.get('max_date', type=convert_str_to_date)
selected_exercise_ids = request.args.getlist('exercise_id', type=int)
if not min_date or not max_date:
db_min_date, db_max_date = db.person_overview.get_earliest_and_latest_workout_dates(person_id)
min_date = min_date or db_min_date
max_date = max_date or db_max_date
if not selected_exercise_ids and htmx.trigger_name != 'exercise_id':
selected_exercise_ids = db.person_overview.list_of_performed_exercise_ids(person_id, min_date, max_date)
person = db.person_overview.get(person_id, min_date, max_date, selected_exercise_ids)
exercises = db.person_overview.get_exercises_with_selection(person_id, min_date, max_date, selected_exercise_ids)
tags = db.get_tags_for_person(person_id)
# Render the appropriate response for HTMX or full page
render_args = {
**person,
"exercises": exercises,
"tags": tags,
"selected_exercise_ids": selected_exercise_ids,
"max_date": max_date,
"min_date": min_date
}
if htmx:
return render_block(app.jinja_env, 'person_overview.html', 'content', **render_args), 200, {"HX-Push-Url": url_for('person_overview', person_id=person_id, min_date=min_date, max_date=max_date, exercise_id=selected_exercise_ids), "HX-Trigger": "refreshStats"}
return render_template('person_overview.html', **render_args), 200, {"HX-Push-Url": url_for('person_overview', person_id=person_id, min_date=min_date, max_date=max_date, exercise_id=selected_exercise_ids), "HX-Trigger": "refreshStats"}
@ app.route("/person/<int:person_id>/calendar")
@ validate_person
def get_calendar(person_id):
selected_date = convert_str_to_date(request.args.get(
'date'), '%Y-%m-%d') or date.today()
selected_view = request.args.get('view') or 'month'
if selected_view == 'overview':
return redirect(url_for('person_overview', person_id=person_id))
elif selected_view == 'notes':
return redirect(url_for('get_person_notes', person_id=person_id))
calendar_view = db.calendar.fetch_workouts_for_person(person_id, selected_date, selected_view)
if htmx:
return render_block(app.jinja_env, 'calendar.html', 'content', **calendar_view), 200, {"HX-Push-Url": url_for('get_calendar', person_id=person_id, view=selected_view, date=selected_date), "HX-Trigger": "refreshStats"}
return render_template('calendar.html', **calendar_view), 200, {"HX-Push-Url": url_for('get_calendar', person_id=person_id, view=selected_view, date=selected_date), "HX-Trigger": "refreshStats"}
@ app.route("/person/<int:person_id>/notes", methods=['GET'])
@ validate_person
def get_person_notes(person_id):
(person_name, workout_notes) = db.get_workout_notes_for_person(person_id)
if htmx:
return render_block(app.jinja_env, 'notes.html', 'content', person_id=person_id, person_name=person_name, workout_notes=workout_notes)
return render_template('notes.html', person_id=person_id, person_name=person_name, workout_notes=workout_notes)
@ app.route("/person/<int:person_id>/workout", methods=['POST'])
def create_workout(person_id):
new_workout_id = db.create_workout(person_id)
workout = db.get_workout(person_id, new_workout_id)
(person_tags, workout_tags, selected_workout_tag_ids) = db.get_workout_tags(
person_id, new_workout_id)
view_model = db.workout.get(person_id, new_workout_id)
return render_block(app.jinja_env, 'workout.html', 'content', **view_model)
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/delete", methods=['GET'])
@ validate_workout
def delete_workout(person_id, workout_id):
db.delete_workout(workout_id)
return redirect(url_for('get_calendar', person_id=person_id))
#return "", 200, {"HX-Trigger": "updatedPeople", "HX-Push-Url": url_for('get_calendar', person_id=person_id)}
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/start_date_edit_form", methods=['GET'])
@ validate_workout
def get_workout_start_date_edit_form(person_id, workout_id):
workout = db.get_workout(person_id, workout_id)
return render_template('partials/start_date.html', person_id=person_id, workout_id=workout_id, start_date=workout['start_date'], is_edit=True)
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/start_date", methods=['PUT'])
@ validate_workout
def update_workout_start_date(person_id, workout_id):
new_start_date = request.form.get('start-date')
db.update_workout_start_date(workout_id, new_start_date)
return render_template('partials/start_date.html', person_id=person_id, workout_id=workout_id, start_date=convert_str_to_date(new_start_date, '%Y-%m-%d'))
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/start_date", methods=['GET'])
@ validate_workout
def get_workout_start_date(person_id, workout_id):
workout = db.get_workout(person_id, workout_id)
return render_template('partials/start_date.html', person_id=person_id, workout_id=workout_id, start_date=workout['start_date'])
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>", methods=['GET'])
@ validate_topset
def get_topset(person_id, workout_id, topset_id):
topset = db.get_topset(topset_id)
return render_template('partials/topset.html', person_id=person_id, workout_id=workout_id, topset_id=topset_id, exercise_id=topset['exercise_id'], exercise_name=topset['exercise_name'], repetitions=topset['repetitions'], weight=topset['weight'])
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>/edit_form", methods=['GET'])
@ validate_topset
def get_topset_edit_form(person_id, workout_id, topset_id):
exercises = db.get_all_exercises()
topset = db.get_topset(topset_id)
return render_template('partials/topset.html', person_id=person_id, workout_id=workout_id, topset_id=topset_id, exercises=exercises, exercise_id=topset['exercise_id'], exercise_name=topset['exercise_name'], repetitions=topset['repetitions'], weight=topset['weight'], is_edit=True)
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/topset", methods=['POST'])
@ validate_workout
def create_topset(person_id, workout_id):
exercise_id = request.form.get("exercise_id")
repetitions = request.form.get("repetitions")
weight = request.form.get("weight")
new_topset_id = db.create_topset(
workout_id, exercise_id, repetitions, weight)
exercise = db.get_exercise(exercise_id)
return render_template('partials/topset.html', person_id=person_id, workout_id=workout_id, topset_id=new_topset_id, exercise_id=exercise_id, exercise_name=exercise['name'], repetitions=repetitions, weight=weight), 200, {"HX-Trigger": "topsetAdded"}
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>", methods=['PUT'])
@ validate_workout
def update_topset(person_id, workout_id, topset_id):
exercise_id = request.form.get("exercise_id")
repetitions = request.form.get("repetitions")
weight = request.form.get("weight")
db.update_topset(exercise_id, repetitions, weight, topset_id)
exercise = db.get_exercise(exercise_id)
return render_template('partials/topset.html', person_id=person_id, workout_id=workout_id, topset_id=topset_id, exercise_name=exercise['name'], repetitions=repetitions, weight=weight)
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/topset/<int:topset_id>/delete", methods=['DELETE'])
@ validate_topset
def delete_topset(person_id, workout_id, topset_id):
db.delete_topset(topset_id)
return ""
@ app.route("/person", methods=['POST'])
def create_person():
name = request.form.get("name")
new_person_id = db.create_person(name)
return render_template('partials/person.html', person_id=new_person_id, name=name), 200, {"HX-Trigger": "updatedPeople"}
@ app.route("/person/<int:person_id>/delete", methods=['DELETE'])
def delete_person(person_id):
db.delete_person(person_id)
return "", 200, {"HX-Trigger": "updatedPeople"}
@ app.route("/person/<int:person_id>/edit_form", methods=['GET'])
def get_person_edit_form(person_id):
name = db.get_person_name(person_id)
return render_template('partials/person.html', person_id=person_id, name=name, is_edit=True)
@ app.route("/person/<int:person_id>/name", methods=['PUT'])
def update_person_name(person_id):
new_name = request.form.get("name")
db.update_person_name(person_id, new_name)
return render_template('partials/person.html', person_id=person_id, name=new_name), 200, {"HX-Trigger": "updatedPeople"}
@ app.route("/person/<int:person_id>/name", methods=['GET'])
def get_person_name(person_id):
name = db.get_person_name(person_id)
return render_template('partials/person.html', person_id=person_id, name=name)
@ app.route("/exercise", methods=['POST'])
def create_exercise():
name = request.form.get("name")
new_exercise_id = db.create_exercise(name)
return render_template('partials/exercise.html', exercise_id=new_exercise_id, name=name)
@ app.route("/exercise/<int:exercise_id>", methods=['GET'])
def get_exercise(exercise_id):
exercise = db.get_exercise(exercise_id)
return render_template('partials/exercise.html', exercise_id=exercise_id, name=exercise.name)
@ app.route("/exercise/<int:exercise_id>/edit_form", methods=['GET'])
def get_exercise_edit_form(exercise_id):
exercise = db.get_exercise(exercise_id)
return render_template('partials/exercise.html', exercise_id=exercise_id, name=exercise['name'], is_edit=True)
@ app.route("/exercise/<int:exercise_id>/update", methods=['PUT'])
def update_exercise(exercise_id):
new_name = request.form.get('name')
db.update_exercise(exercise_id, new_name)
return render_template('partials/exercise.html', exercise_id=exercise_id, name=new_name)
""" @ app.route("/exercise/<int:exercise_id>/delete", methods=['DELETE'])
def delete_exercise(exercise_id):
db.delete_exercise(exercise_id)
return "" """
@ app.route("/settings")
def settings():
people = db.get_people()
exercises = db.get_all_exercises()
if htmx:
return render_block(app.jinja_env, "settings.html", "content", people=people, exercises=exercises), 200, {"HX-Trigger": "updatedPeople"}
return render_template('settings.html', people=people, exercises=exercises)
@ app.route("/tag/redirect", methods=['GET'])
def goto_tag():
person_id = request.args.get("person_id")
tag_filter = request.args.get('filter')
if person_id:
return redirect(url_for('person_overview', person_id=int(person_id)) + tag_filter)
return redirect(url_for('dashboard') + tag_filter)
@ app.route("/tag/add", methods=['GET'])
def add_tag():
person_id = request.args.get("person_id")
tag = request.args.get('tag')
tag_filter = request.args.get('filter')
if person_id:
db.add_or_update_tag_for_person(person_id, tag, tag_filter)
else:
db.add_or_update_tag_for_dashboard(tag, tag_filter)
return ""
@ app.route("/tag/<int:tag_id>/delete", methods=['GET'])
def delete_tag(tag_id):
person_id = request.args.get("person_id")
tag_filter = request.args.get("filter")
if person_id:
db.delete_tag_for_person(person_id=person_id, tag_id=tag_id)
return redirect(url_for('get_person', person_id=person_id) + tag_filter)
db.delete_tag_for_dashboard(tag_id)
return redirect(url_for('dashboard') + tag_filter)
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/note/edit", methods=['GET'])
@ validate_workout
def get_workout_note_edit_form(person_id, workout_id):
workout = db.get_workout(person_id, workout_id)
return render_template('partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=workout['note'], is_edit=True)
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/note", methods=['PUT'])
@ validate_workout
def update_workout_note(person_id, workout_id):
note = request.form.get('note')
db.update_workout_note_for_person(person_id, workout_id, note)
return render_template('partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=note)
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/note", methods=['GET'])
@ validate_workout
def get_workout_note(person_id, workout_id):
workout = db.get_workout(person_id, workout_id)
return render_template('partials/workout_note.html', person_id=person_id, workout_id=workout_id, note=workout['note'])
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/tag/add", methods=['POST'])
def add_tag_to_workout(person_id, workout_id):
tags_id = [int(i) for i in request.form.getlist('tag_id')]
tags = db.add_tag_for_workout(workout_id, tags_id)
return render_template('partials/workout_tags_list.html', tags=tags)
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/tag/new", methods=['POST'])
def create_new_tag_for_workout(person_id, workout_id):
tag_name = request.form.get('tag_name')
workout_tags = db.create_tag_for_workout(person_id, workout_id, tag_name)
return render_template('partials/workout_tags_list.html', workout_tags=workout_tags)
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/exercise/most_recent_topset_for_exercise", methods=['GET'])
def get_most_recent_topset_for_exercise(person_id, workout_id):
exercise_id = request.args.get('exercise_id', type=int)
exercises = db.get_all_exercises()
if not exercise_id:
return render_template('partials/new_set_form.html', person_id=person_id, workout_id=workout_id, exercises=exercises)
topset = db.get_most_recent_topset_for_exercise(person_id, exercise_id)
if not topset:
return render_template('partials/new_set_form.html', person_id=person_id, workout_id=workout_id, exercises=exercises, exercise_id=exercise_id)
(repetitions, weight, exercise_name) = topset
return render_template('partials/new_set_form.html', person_id=person_id, workout_id=workout_id, exercise_id=exercise_id, exercise_name=exercise_name, repetitions=repetitions, weight=weight)
def calculate_relative_positions(start_dates):
min_date = min(start_dates)
max_date = max(start_dates)
total_span = (max_date - min_date).days if max_date != min_date else 1
return [(date - min_date).days / total_span for date in start_dates]
@ app.route("/person/<int:person_id>/exercise/<int:exercise_id>/sparkline", methods=['GET'])
def get_exercise_progress_for_user(person_id, exercise_id):
min_date = convert_str_to_date(request.args.get(
'min_date'), '%Y-%m-%d')
max_date = convert_str_to_date(request.args.get(
'max_date'), '%Y-%m-%d')
epoch = request.args.get('epoch', default='All')
if epoch == 'Custom' and (min_date is None or max_date is None):
(min_date, max_date) = db.get_exercise_earliest_and_latest_dates(person_id, exercise_id)
exercise_progress = db.get_exercise_progress_for_user(person_id, exercise_id, min_date, max_date, epoch)
if not exercise_progress:
abort(404)
return render_template('partials/sparkline.html', **exercise_progress)
@app.route("/stats", methods=['GET'])
def get_stats():
selected_people_ids = request.args.getlist('person_id', type=int)
min_date = request.args.get('min_date', type=convert_str_to_date)
max_date = request.args.get('max_date', type=convert_str_to_date)
selected_exercise_ids = request.args.getlist('exercise_id', type=int)
stats = db.stats.fetch_stats(selected_people_ids, min_date, max_date, selected_exercise_ids)
return render_template('partials/stats.html', stats=stats, refresh_url=request.full_path)
@app.route("/graphs", methods=['GET'])
def get_people_graphs():
selected_people_ids = request.args.getlist('person_id', type=int)
min_date = request.args.get('min_date', type=convert_str_to_date)
max_date = request.args.get('max_date', type=convert_str_to_date)
selected_exercise_ids = request.args.getlist('exercise_id', type=int)
graphs = db.people_graphs.get(selected_people_ids, min_date, max_date, selected_exercise_ids)
return render_template('partials/people_graphs.html', graphs=graphs, refresh_url=request.full_path)
@ app.route("/person/<int:person_id>/workout/<int:workout_id>", methods=['GET'])
def show_workout(person_id, workout_id):
view_model = db.workout.get(person_id, workout_id)
if htmx:
return render_block(app.jinja_env, 'workout.html', 'content', **view_model)
return render_template('workout.html', **view_model)
@app.route("/exercises/get")
def get_exercises():
query = request.args.get('query')
person_id = request.args.get('person_id', type=int)
exercises = db.exercises.get(query)
return render_template('partials/exercise/exercise_dropdown.html', exercises=exercises, person_id=person_id)
@app.route("/exercise/<int:exercise_id>/edit_name", methods=['GET', 'POST'])
def edit_exercise_name(exercise_id):
exercise = db.exercises.get_exercise(exercise_id)
person_id = request.args.get('person_id', type=int)
if request.method == 'GET':
return render_template('partials/exercise/edit_exercise_name.html', exercise=exercise, person_id=person_id)
else:
updated_name = request.form['name']
updated_exercise = db.exercises.update_exercise_name(exercise_id, updated_name)
return render_template('partials/exercise/exercise_list_item.html', exercise=updated_exercise, person_id=person_id)
@app.route("/exercises/add", methods=['POST'])
def add_exercise():
exercise_name = request.form['query']
new_exercise = db.exercises.add_exercise(exercise_name)
person_id = request.args.get('person_id', type=int)
return render_template('partials/exercise/exercise_list_item.html', exercise=new_exercise, person_id=person_id)
@ app.route("/exercise/<int:exercise_id>/delete", methods=['DELETE'])
def delete_exercise(exercise_id):
db.exercises.delete_exercise(exercise_id)
return ""
@app.route("/sql_explorer", methods=['GET'])
def sql_explorer():
saved_queries = db.sql_explorer.list_saved_queries()
if htmx:
return render_block(app.jinja_env, 'sql_explorer.html', 'content', saved_queries=saved_queries)
return render_template('sql_explorer.html', saved_queries=saved_queries)
@app.route("/sql_query", methods=['POST'])
def sql_query():
query = request.form.get('query')
action = request.form.get('action')
title = request.form.get('title')
if action == 'execute':
(results, columns, error) = db.sql_explorer.execute_sql(query)
saved_queries = db.sql_explorer.list_saved_queries()
return render_template('partials/sql_explorer/sql_query.html',
title=title,
query=query,
results=results,
columns=columns,
error=error,
saved_queries=saved_queries)
else:
error = db.sql_explorer.save_query(title, query)
saved_queries = db.sql_explorer.list_saved_queries()
return render_template('partials/sql_explorer/sql_query.html',
title=title,
query=query,
error=error,
saved_queries=saved_queries)
@app.route('/load_sql_query/<int:query_id>', methods=['GET'])
def load_sql_query(query_id):
(title, query) = db.sql_explorer.get_saved_query(query_id)
saved_queries = db.sql_explorer.list_saved_queries()
return render_template('partials/sql_explorer/sql_query.html',
title=title,
query=query,
saved_queries=saved_queries)
@app.route('/delete_sql_query/<int:query_id>', methods=['DELETE'])
def delete_sql_query(query_id):
db.sql_explorer.delete_saved_query(query_id)
saved_queries = db.sql_explorer.list_saved_queries()
return render_template('partials/sql_explorer/sql_query.html',
title="",
query="",
saved_queries=saved_queries)
@ app.route("/sql_schema", methods=['GET'])
def sql_schema():
schema_info = db.sql_explorer.get_schema_info()
mermaid_code = db.sql_explorer.generate_mermaid_er(schema_info)
return render_template('partials/sql_explorer/schema.html', mermaid_code=mermaid_code)
@app.route("/plot/<int:query_id>", methods=['GET'])
def plot_query(query_id):
(title, query) = db.sql_explorer.get_saved_query(query_id)
#(results, columns, error) = db.sql_explorer.execute_sql(query)
results_df = db.read_sql_as_df(query)
plot_div = generate_plot(results_df, title)
return plot_div
def get_routes():
routes = []
for rule in app.url_map.iter_rules():
if rule.endpoint == 'static':
continue
methods = ', '.join(sorted(rule.methods - {'HEAD', 'OPTIONS'}))
route_info = {
'endpoint': rule.endpoint,
'url': rule.rule,
'methods': methods,
'view_func': app.view_functions[rule.endpoint].__name__,
'doc': app.view_functions[rule.endpoint].__doc__
}
routes.append(route_info)
return routes
@app.route('/endpoints')
def list_endpoints():
"""
Lists all API endpoints available in the Flask application.
This endpoint retrieves all registered routes, excluding static routes,
and displays their details such as endpoint name, URL, allowed HTTP methods,
view function name, and a brief description.
"""
routes = get_routes()
if htmx:
return render_block(app.jinja_env, 'endpoints.html', 'content', routes=routes)
return render_template('endpoints.html', routes=routes)
@app.route('/endpoints/search')
def search_endpoints():
routes = get_routes()
search = request.args.get('search', '').lower()
if search:
routes = [
route for route in routes
if search in route['endpoint'].lower()
or search in route['url'].lower()
or search in route['methods'].lower()
or search in route['view_func'].lower()
or (route['doc'] and search in route['doc'].lower())
]
return render_template('partials/endpoints_table.html', routes=routes)
@app.teardown_appcontext
def closeConnection(exception):
db.close_connection()
@app.template_filter('list_to_string')
def list_to_string(list):
return [str(i) for i in list]
@app.template_filter('strftime')
def strftime(date, format="%b %d %Y"):
return date.strftime(format)
@app.template_filter('get_first_element_from_list_with_matching_attribute')
def get_first_element_from_list_with_matching_attribute(list, attribute, value):
if not list:
return None
for element in list:
if element[attribute] == value:
return element
return None
@app.template_filter('replace_double_quote_strings_with_single_quote')
def replace_double_quote_strings_with_single_quote(str):
return str.replace('"', "'")
@ app.context_processor
def my_utility_processor():
def is_selected_page(url):
# if htmx:
# parsed_url = urlparse(htmx.current_url)
# return 'bg-gray-200' if url == parsed_url.path else ''
if url == request.path:
return 'bg-gray-200'
return ''
def get_first_element_from_list_with_matching_attribute(list, attribute, value):
if not list:
return None
for element in list:
if element[attribute] == value:
return element
return None
def in_list(val, checked_vals, attr='checked'):
if not checked_vals:
return attr
return attr if val in checked_vals else ''
def strftime(date, format="%b %d %Y"):
return date.strftime(format)
def list_to_string(list):
return [str(i) for i in list]
return dict(is_selected_page=is_selected_page, get_first_element_from_list_with_matching_attribute=get_first_element_from_list_with_matching_attribute, in_list=in_list, strftime=strftime, datetime=datetime, timedelta=timedelta, relativedelta=relativedelta, list_to_string=list_to_string, quote=quote)
if __name__ == '__main__':
# Bind to PORT if defined, otherwise default to 5000.
port = int(os.environ.get('PORT', 5000))
app.run(host='127.0.0.1', port=port)