from datetime import date import os from flask import Flask, abort, render_template, redirect, request, url_for from flask_login import LoginManager import jinja_partials from jinja2_fragments import render_block from decorators import validate_person, validate_topset, validate_workout from routes.auth import auth, get_person_by_id from routes.changelog import changelog_bp from routes.calendar import calendar_bp # Import the new calendar blueprint from routes.notes import notes_bp # Import the new notes blueprint from routes.workout import workout_bp # Import the new workout blueprint from routes.sql_explorer import sql_explorer_bp # Import the new SQL explorer blueprint from routes.endpoints import endpoints_bp # Import the new endpoints blueprint from routes.export import export_bp # Import the new export blueprint from routes.tags import tags_bp # Import the new tags blueprint from routes.programs import programs_bp # Import the new programs blueprint from extensions import db from utils import convert_str_to_date, generate_plot from flask_htmx import HTMX import minify_html 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') app.secret_key = os.environ.get('SECRET_KEY', '2a661781919643cb8a5a8bc57642d99f') jinja_partials.register_extensions(app) htmx = HTMX(app) login_manager = LoginManager(app) login_manager.login_view = 'auth.login' login_manager.login_message_category = 'info' @login_manager.user_loader def load_user(person_id): return get_person_by_id(person_id) app.register_blueprint(auth, url_prefix='/auth') app.register_blueprint(changelog_bp, url_prefix='/changelog') app.register_blueprint(calendar_bp) # Register the calendar blueprint app.register_blueprint(notes_bp) # Register the notes blueprint app.register_blueprint(workout_bp) # Register the workout blueprint app.register_blueprint(sql_explorer_bp) # Register the SQL explorer blueprint (prefix defined in blueprint file) app.register_blueprint(endpoints_bp) # Register the endpoints blueprint (prefix defined in blueprint file) app.register_blueprint(export_bp) # Register the export blueprint (prefix defined in blueprint file) app.register_blueprint(tags_bp) # Register the tags blueprint (prefix defined in blueprint file) app.register_blueprint(programs_bp) # Register the programs blueprint (prefix defined in blueprint file) @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//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", 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//delete", methods=['DELETE']) def delete_person(person_id): db.delete_person(person_id) return "", 200, {"HX-Trigger": "updatedPeople"} @ app.route("/person//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//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//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/", 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//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//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//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) # Routes moved to routes/tags.py blueprint @ app.route("/person//exercise//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') degree = request.args.get('degree', type=int, default=1) 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, degree=degree) 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("/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//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//delete", methods=['DELETE']) def delete_exercise(exercise_id): db.exercises.delete_exercise(exercise_id) return "" @app.teardown_appcontext def closeConnection(exception): db.close_connection() @app.template_filter('strftime') def strftime(date, format="%b %d %Y"): return date.strftime(format) @ 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 strftime(date, format="%b %d %Y"): return date.strftime(format) return dict(is_selected_page=is_selected_page, strftime=strftime) 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)