Here is a conventional commit message summarizing the refactoring work:
``` feat: Refactor calendar feature into blueprint - Moved calendar logic from `features/calendar.py` and `app.py` into a new blueprint at `routes/calendar.py`. - Removed the `Calendar` class and refactored logic into helper functions within the blueprint module for better organization and readability. - Eliminated the `pandas` dependency for date range generation, using standard `datetime` operations instead. - Resolved circular import issues between `db.py`, `extensions.py`, and `routes/calendar.py` by adjusting import locations. - Corrected `url_for` calls in templates (`calendar.html`, `partials/people_link.html`) to reference the new blueprint endpoint (`calendar.get_calendar`). - Fixed an `AttributeError` related to HTMX request checking in the calendar route. - Corrected `AttributeError` related to `.date()` calls on `datetime.date` objects in view processing functions. - Updated `templates/changelog/changelog.html` to document the refactoring and associated fixes. ```
This commit is contained in:
22
app.py
22
app.py
@@ -7,6 +7,7 @@ from jinja2_fragments import render_block
|
|||||||
from decorators import validate_person, validate_topset, validate_workout
|
from decorators import validate_person, validate_topset, validate_workout
|
||||||
from routes.auth import auth, get_person_by_id
|
from routes.auth import auth, get_person_by_id
|
||||||
from routes.changelog import changelog_bp
|
from routes.changelog import changelog_bp
|
||||||
|
from routes.calendar import calendar_bp # Import the new calendar blueprint
|
||||||
from extensions import db
|
from extensions import db
|
||||||
from utils import convert_str_to_date, generate_plot
|
from utils import convert_str_to_date, generate_plot
|
||||||
from flask_htmx import HTMX
|
from flask_htmx import HTMX
|
||||||
@@ -34,6 +35,7 @@ def load_user(person_id):
|
|||||||
|
|
||||||
app.register_blueprint(auth, url_prefix='/auth')
|
app.register_blueprint(auth, url_prefix='/auth')
|
||||||
app.register_blueprint(changelog_bp, url_prefix='/changelog')
|
app.register_blueprint(changelog_bp, url_prefix='/changelog')
|
||||||
|
app.register_blueprint(calendar_bp) # Register the calendar blueprint
|
||||||
|
|
||||||
@app.after_request
|
@app.after_request
|
||||||
def response_minify(response):
|
def response_minify(response):
|
||||||
@@ -127,22 +129,6 @@ def person_overview(person_id):
|
|||||||
|
|
||||||
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"}
|
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")
|
|
||||||
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'])
|
@ app.route("/person/<int:person_id>/notes", methods=['GET'])
|
||||||
@ validate_person
|
@ validate_person
|
||||||
@@ -166,8 +152,8 @@ def create_workout(person_id):
|
|||||||
@ validate_workout
|
@ validate_workout
|
||||||
def delete_workout(person_id, workout_id):
|
def delete_workout(person_id, workout_id):
|
||||||
db.delete_workout(workout_id)
|
db.delete_workout(workout_id)
|
||||||
return redirect(url_for('get_calendar', person_id=person_id))
|
return redirect(url_for('calendar.get_calendar', person_id=person_id))
|
||||||
#return "", 200, {"HX-Trigger": "updatedPeople", "HX-Push-Url": url_for('get_calendar', person_id=person_id)}
|
#return "", 200, {"HX-Trigger": "updatedPeople", "HX-Push-Url": url_for('calendar.get_calendar', person_id=person_id)}
|
||||||
|
|
||||||
|
|
||||||
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/start_date_edit_form", methods=['GET'])
|
@ app.route("/person/<int:person_id>/workout/<int:workout_id>/start_date_edit_form", methods=['GET'])
|
||||||
|
|||||||
2
db.py
2
db.py
@@ -6,7 +6,6 @@ from dateutil.relativedelta import relativedelta
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from flask import g
|
from flask import g
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from features.calendar import Calendar
|
|
||||||
from features.exercises import Exercises
|
from features.exercises import Exercises
|
||||||
from features.people_graphs import PeopleGraphs
|
from features.people_graphs import PeopleGraphs
|
||||||
from features.person_overview import PersonOverview
|
from features.person_overview import PersonOverview
|
||||||
@@ -19,7 +18,6 @@ from utils import get_exercise_graph_model
|
|||||||
|
|
||||||
class DataBase():
|
class DataBase():
|
||||||
def __init__(self, app=None):
|
def __init__(self, app=None):
|
||||||
self.calendar = Calendar(self.execute)
|
|
||||||
self.stats = Stats(self.execute)
|
self.stats = Stats(self.execute)
|
||||||
self.workout = Workout(self.execute)
|
self.workout = Workout(self.execute)
|
||||||
self.exercises = Exercises(self.execute)
|
self.exercises = Exercises(self.execute)
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
from datetime import datetime, timedelta
|
|
||||||
from dateutil.relativedelta import relativedelta
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
class Calendar:
|
|
||||||
def __init__(self, db_connection_method):
|
|
||||||
self.execute = db_connection_method
|
|
||||||
|
|
||||||
def fetch_workouts_for_person(self, person_id, date, view):
|
|
||||||
prev_date, next_date = None, None
|
|
||||||
if view == 'month':
|
|
||||||
first_day_of_month = date.replace(day=1)
|
|
||||||
days_to_subtract = (first_day_of_month.weekday() + 1) % 7
|
|
||||||
start_date = first_day_of_month - timedelta(days=days_to_subtract)
|
|
||||||
end_date = start_date + timedelta(days=6 * 7 - 1)
|
|
||||||
prev_date = first_day_of_month - relativedelta(months=1)
|
|
||||||
next_date = first_day_of_month + relativedelta(months=1)
|
|
||||||
elif view == 'year':
|
|
||||||
start_date = date.replace(month=1, day=1)
|
|
||||||
end_date = date.replace(year=date.year + 1, month=1, day=1) - timedelta(days=1)
|
|
||||||
prev_date = date - relativedelta(years=1)
|
|
||||||
next_date = date + relativedelta(years=1)
|
|
||||||
else:
|
|
||||||
raise ValueError('Invalid view')
|
|
||||||
|
|
||||||
query = """
|
|
||||||
SELECT
|
|
||||||
w.workout_id,
|
|
||||||
w.start_date,
|
|
||||||
t.topset_id,
|
|
||||||
t.repetitions,
|
|
||||||
t.weight,
|
|
||||||
e.name AS exercise_name,
|
|
||||||
p.name AS person_name
|
|
||||||
FROM
|
|
||||||
person p
|
|
||||||
LEFT JOIN workout w ON p.person_id = w.person_id AND w.start_date BETWEEN %s AND %s
|
|
||||||
LEFT JOIN topset t ON w.workout_id = t.workout_id
|
|
||||||
LEFT JOIN exercise e ON t.exercise_id = e.exercise_id
|
|
||||||
WHERE
|
|
||||||
p.person_id = %s
|
|
||||||
ORDER BY
|
|
||||||
w.start_date,
|
|
||||||
t.topset_id;
|
|
||||||
"""
|
|
||||||
workouts_data = self.execute(query, [start_date, end_date, person_id])
|
|
||||||
|
|
||||||
# Assuming person_name is the same for all rows as we filter by person_id
|
|
||||||
person_name = workouts_data[0]['person_name'] if workouts_data else 'Unknown'
|
|
||||||
|
|
||||||
calendar_view = {'prev_date': prev_date, 'next_date': next_date, 'person_id': person_id, 'person_name': person_name, 'view': view, 'date': date}
|
|
||||||
|
|
||||||
if view == 'month':
|
|
||||||
calendar_view['days'] = []
|
|
||||||
workouts_by_date = {}
|
|
||||||
|
|
||||||
for row in workouts_data:
|
|
||||||
if row['workout_id'] is None:
|
|
||||||
continue # Skip rows that don't have workout data
|
|
||||||
|
|
||||||
workout_date_str = row['start_date'].strftime("%Y-%m-%d")
|
|
||||||
workout_id = row['workout_id']
|
|
||||||
|
|
||||||
if workout_date_str not in workouts_by_date:
|
|
||||||
workouts_by_date[workout_date_str] = {}
|
|
||||||
|
|
||||||
if workout_id not in workouts_by_date[workout_date_str]:
|
|
||||||
workouts_by_date[workout_date_str][workout_id] = {
|
|
||||||
'workout_id': workout_id,
|
|
||||||
'start_date': row['start_date'],
|
|
||||||
'sets': []
|
|
||||||
}
|
|
||||||
|
|
||||||
if row['topset_id']:
|
|
||||||
workouts_by_date[workout_date_str][workout_id]['sets'].append({
|
|
||||||
'repetitions': row['repetitions'],
|
|
||||||
'weight': row['weight'],
|
|
||||||
'exercise_name': row['exercise_name']
|
|
||||||
})
|
|
||||||
|
|
||||||
for current_date in pd.date_range(start_date, end_date, freq='D'):
|
|
||||||
date_str = current_date.strftime("%Y-%m-%d")
|
|
||||||
day_workouts = workouts_by_date.get(date_str, {})
|
|
||||||
today = datetime.today().date()
|
|
||||||
|
|
||||||
calendar_view['days'].append({
|
|
||||||
'day': current_date.day,
|
|
||||||
'is_today': current_date == today,
|
|
||||||
'is_in_current_month': current_date.month == date.month, # Ensure it compares with the selected month
|
|
||||||
'has_workouts': len(day_workouts) > 0,
|
|
||||||
'workouts': list(day_workouts.values())
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
elif view == 'year':
|
|
||||||
calendar_view['months'] = []
|
|
||||||
workouts_by_date = {}
|
|
||||||
for row in workouts_data:
|
|
||||||
if row['start_date'] is None:
|
|
||||||
continue # Skip rows that don't have workout data
|
|
||||||
workout_date_str = row['start_date'].strftime("%Y-%m-%d")
|
|
||||||
if workout_date_str not in workouts_by_date:
|
|
||||||
workouts_by_date[workout_date_str] = []
|
|
||||||
|
|
||||||
workouts_by_date[workout_date_str].append({
|
|
||||||
'workout_id': row['workout_id'],
|
|
||||||
'start_date': row['start_date'],
|
|
||||||
'topset_id': row['topset_id'],
|
|
||||||
'repetitions': row['repetitions'],
|
|
||||||
'weight': row['weight'],
|
|
||||||
'exercise_name': row['exercise_name']
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
for month in range(1, 13):
|
|
||||||
first_day_of_month = date.replace(month=month, day=1)
|
|
||||||
days_to_subtract = (first_day_of_month.weekday() + 1) % 7
|
|
||||||
start_date = first_day_of_month - timedelta(days=days_to_subtract)
|
|
||||||
end_date = start_date + timedelta(days=6 * 7 - 1)
|
|
||||||
|
|
||||||
month_data = {'name': first_day_of_month.strftime('%B'), 'first_day_of_month': first_day_of_month, 'days': []}
|
|
||||||
|
|
||||||
current_day = start_date
|
|
||||||
while current_day <= end_date:
|
|
||||||
day_workouts = workouts_by_date.get(current_day.strftime('%Y-%m-%d'), [])
|
|
||||||
has_workouts = len(day_workouts) > 0
|
|
||||||
first_workout_id = day_workouts[0]['workout_id'] if has_workouts else None
|
|
||||||
|
|
||||||
day_data = {
|
|
||||||
'day': current_day.day,
|
|
||||||
'is_today': current_day == datetime.today().date(),
|
|
||||||
'is_in_current_month': current_day.month == month,
|
|
||||||
'workouts': day_workouts,
|
|
||||||
'has_workouts': has_workouts,
|
|
||||||
'first_workout_id': first_workout_id
|
|
||||||
}
|
|
||||||
month_data['days'].append(day_data)
|
|
||||||
current_day += timedelta(days=1)
|
|
||||||
|
|
||||||
calendar_view['months'].append(month_data)
|
|
||||||
|
|
||||||
return calendar_view
|
|
||||||
@@ -110,7 +110,7 @@ def login():
|
|||||||
if person and check_password_hash(person.password_hash, form.password.data):
|
if person and check_password_hash(person.password_hash, form.password.data):
|
||||||
login_user(person)
|
login_user(person)
|
||||||
flash("Logged in successfully.", "success")
|
flash("Logged in successfully.", "success")
|
||||||
return redirect(url_for('get_calendar', person_id=person.id))
|
return redirect(url_for('calendar.get_calendar', person_id=person.id))
|
||||||
else:
|
else:
|
||||||
flash("Invalid email or password.", "danger")
|
flash("Invalid email or password.", "danger")
|
||||||
return render_template('auth/login.html', form=form)
|
return render_template('auth/login.html', form=form)
|
||||||
|
|||||||
214
routes/calendar.py
Normal file
214
routes/calendar.py
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
from datetime import date, datetime, timedelta
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
from collections import defaultdict
|
||||||
|
from flask import Blueprint, render_template, redirect, url_for, request, current_app
|
||||||
|
from flask_htmx import HTMX
|
||||||
|
from jinja2_fragments import render_block
|
||||||
|
from utils import convert_str_to_date
|
||||||
|
|
||||||
|
calendar_bp = Blueprint('calendar', __name__)
|
||||||
|
htmx = HTMX()
|
||||||
|
|
||||||
|
# --- Helper Functions ---
|
||||||
|
|
||||||
|
def _get_date_range_and_links(date_obj, view):
|
||||||
|
"""Calculates start/end dates and prev/next links based on view."""
|
||||||
|
start_date, end_date, prev_date, next_date = None, None, None, None
|
||||||
|
if view == 'month':
|
||||||
|
first_day_of_month = date_obj.replace(day=1)
|
||||||
|
# Day of week: Monday is 0 and Sunday is 6
|
||||||
|
# Calculate the first day to display on the calendar grid (previous Sunday)
|
||||||
|
start_date = first_day_of_month - timedelta(days=first_day_of_month.weekday() + 1)
|
||||||
|
# Calculate the last day (start + 6 weeks - 1 day)
|
||||||
|
end_date = start_date + timedelta(days=41) # 6*7 - 1 = 41 days total in 6 weeks
|
||||||
|
prev_date = first_day_of_month - relativedelta(months=1)
|
||||||
|
next_date = first_day_of_month + relativedelta(months=1)
|
||||||
|
elif view == 'year':
|
||||||
|
start_date = date_obj.replace(month=1, day=1)
|
||||||
|
end_date = date_obj.replace(year=date_obj.year + 1, month=1, day=1) - timedelta(days=1)
|
||||||
|
prev_date = date_obj - relativedelta(years=1)
|
||||||
|
next_date = date_obj + relativedelta(years=1)
|
||||||
|
else:
|
||||||
|
raise ValueError('Invalid view type specified.')
|
||||||
|
return start_date, end_date, prev_date, next_date
|
||||||
|
|
||||||
|
def _fetch_raw_workout_data(db_executor, person_id, start_date, end_date):
|
||||||
|
"""Fetches workout data for a person within a date range."""
|
||||||
|
query = """
|
||||||
|
SELECT
|
||||||
|
w.workout_id,
|
||||||
|
w.start_date,
|
||||||
|
t.topset_id,
|
||||||
|
t.repetitions,
|
||||||
|
t.weight,
|
||||||
|
e.name AS exercise_name,
|
||||||
|
p.name AS person_name
|
||||||
|
FROM
|
||||||
|
person p
|
||||||
|
LEFT JOIN workout w ON p.person_id = w.person_id AND w.start_date BETWEEN %s AND %s
|
||||||
|
LEFT JOIN topset t ON w.workout_id = t.workout_id
|
||||||
|
LEFT JOIN exercise e ON t.exercise_id = e.exercise_id
|
||||||
|
WHERE
|
||||||
|
p.person_id = %s
|
||||||
|
ORDER BY
|
||||||
|
w.start_date,
|
||||||
|
t.topset_id;
|
||||||
|
"""
|
||||||
|
# Ensure dates are passed in a format the DB understands (e.g., YYYY-MM-DD strings)
|
||||||
|
return db_executor(query, [start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d'), person_id])
|
||||||
|
|
||||||
|
def _group_workouts_by_date(workouts_data):
|
||||||
|
"""Groups workout data by date and workout ID."""
|
||||||
|
# Structure: { 'YYYY-MM-DD': { workout_id: { workout_details..., 'sets': [...] } } }
|
||||||
|
workouts_by_date = defaultdict(lambda: defaultdict(lambda: {'sets': []}))
|
||||||
|
person_name = 'Unknown'
|
||||||
|
if workouts_data:
|
||||||
|
# Use .get() for safer access in case the key doesn't exist
|
||||||
|
person_name = workouts_data[0].get('person_name', 'Unknown')
|
||||||
|
|
||||||
|
for row in workouts_data:
|
||||||
|
# Basic validation for row data
|
||||||
|
if not row or row.get('workout_id') is None or row.get('start_date') is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
workout_date = row['start_date']
|
||||||
|
workout_date_str = workout_date.strftime("%Y-%m-%d")
|
||||||
|
workout_id = row['workout_id']
|
||||||
|
|
||||||
|
# Initialize workout details if this workout_id hasn't been seen for this date
|
||||||
|
if workout_id not in workouts_by_date[workout_date_str]:
|
||||||
|
workouts_by_date[workout_date_str][workout_id].update({
|
||||||
|
'workout_id': workout_id,
|
||||||
|
'start_date': workout_date,
|
||||||
|
# 'sets' is already initialized by defaultdict
|
||||||
|
})
|
||||||
|
|
||||||
|
# Add set details if topset_id exists
|
||||||
|
if row.get('topset_id'):
|
||||||
|
workouts_by_date[workout_date_str][workout_id]['sets'].append({
|
||||||
|
'repetitions': row.get('repetitions'),
|
||||||
|
'weight': row.get('weight'),
|
||||||
|
'exercise_name': row.get('exercise_name')
|
||||||
|
})
|
||||||
|
|
||||||
|
# Convert nested defaultdict to regular dict for easier handling/JSON serialization
|
||||||
|
processed_workouts = {
|
||||||
|
date_str: dict(workouts)
|
||||||
|
for date_str, workouts in workouts_by_date.items()
|
||||||
|
}
|
||||||
|
return processed_workouts, person_name
|
||||||
|
|
||||||
|
def _process_workouts_for_month_view(grouped_workouts, start_date, end_date, selected_date):
|
||||||
|
"""Formats grouped workout data for the monthly calendar view."""
|
||||||
|
days_data = []
|
||||||
|
today = datetime.today().date()
|
||||||
|
current_date = start_date
|
||||||
|
while current_date <= end_date:
|
||||||
|
date_str = current_date.strftime("%Y-%m-%d")
|
||||||
|
day_workouts_dict = grouped_workouts.get(date_str, {})
|
||||||
|
day_workouts_list = list(day_workouts_dict.values()) # Convert workout dicts to list
|
||||||
|
|
||||||
|
days_data.append({
|
||||||
|
'date_obj': current_date, # Pass the date object for easier template logic
|
||||||
|
'day': current_date.day,
|
||||||
|
'is_today': current_date == today, # Correct comparison: date object == date object
|
||||||
|
'is_in_current_month': current_date.month == selected_date.month,
|
||||||
|
'has_workouts': len(day_workouts_list) > 0,
|
||||||
|
'workouts': day_workouts_list
|
||||||
|
})
|
||||||
|
current_date += timedelta(days=1)
|
||||||
|
return days_data
|
||||||
|
|
||||||
|
def _process_workouts_for_year_view(grouped_workouts, year_date):
|
||||||
|
"""Formats grouped workout data for the yearly calendar view."""
|
||||||
|
months_data = []
|
||||||
|
today = datetime.today().date()
|
||||||
|
|
||||||
|
for month_num in range(1, 13):
|
||||||
|
first_day_of_month = year_date.replace(month=month_num, day=1)
|
||||||
|
# Calculate the first day to display on the calendar month grid (previous Sunday)
|
||||||
|
start_date_month = first_day_of_month - timedelta(days=first_day_of_month.weekday() + 1)
|
||||||
|
# Calculate the last day of the grid (start + 6 weeks - 1 day)
|
||||||
|
end_date_month = start_date_month + timedelta(days=41)
|
||||||
|
|
||||||
|
month_days_data = []
|
||||||
|
current_day = start_date_month
|
||||||
|
while current_day <= end_date_month:
|
||||||
|
date_str = current_day.strftime('%Y-%m-%d')
|
||||||
|
day_workouts_dict = grouped_workouts.get(date_str, {})
|
||||||
|
day_workouts_list = list(day_workouts_dict.values())
|
||||||
|
has_workouts = len(day_workouts_list) > 0
|
||||||
|
# Get first workout ID if workouts exist
|
||||||
|
first_workout_id = day_workouts_list[0]['workout_id'] if has_workouts else None
|
||||||
|
|
||||||
|
month_days_data.append({
|
||||||
|
'date_obj': current_day,
|
||||||
|
'day': current_day.day,
|
||||||
|
'is_today': current_day == today, # Correct comparison: date object == date object
|
||||||
|
'is_in_current_month': current_day.month == month_num,
|
||||||
|
'has_workouts': has_workouts,
|
||||||
|
'workouts': day_workouts_list, # Pass full workout details
|
||||||
|
'first_workout_id': first_workout_id # Keep if template uses this
|
||||||
|
})
|
||||||
|
current_day += timedelta(days=1)
|
||||||
|
|
||||||
|
months_data.append({
|
||||||
|
'name': first_day_of_month.strftime('%B'),
|
||||||
|
'first_day_of_month': first_day_of_month.strftime('%Y-%m-%d'), # Pass date string for URL
|
||||||
|
'days': month_days_data
|
||||||
|
})
|
||||||
|
return months_data
|
||||||
|
|
||||||
|
# --- Route ---
|
||||||
|
|
||||||
|
from extensions import db # Import db locally within the route's scope
|
||||||
|
|
||||||
|
@calendar_bp.route("/person/<int:person_id>/calendar")
|
||||||
|
def get_calendar(person_id):
|
||||||
|
"""Displays the workout calendar for a given person."""
|
||||||
|
selected_date_str = request.args.get('date') # Get as string first
|
||||||
|
# Use today's date if 'date' param is missing or invalid
|
||||||
|
selected_date = convert_str_to_date(selected_date_str, '%Y-%m-%d') or date.today()
|
||||||
|
selected_view = request.args.get('view', 'month') # Default to month view
|
||||||
|
|
||||||
|
# Redirect for non-calendar views
|
||||||
|
if selected_view == 'overview':
|
||||||
|
return redirect(url_for('person_overview', person_id=person_id))
|
||||||
|
if selected_view == 'notes':
|
||||||
|
return redirect(url_for('get_person_notes', person_id=person_id))
|
||||||
|
|
||||||
|
try:
|
||||||
|
start_date, end_date, prev_date, next_date = _get_date_range_and_links(selected_date, selected_view)
|
||||||
|
except ValueError as e:
|
||||||
|
# Handle invalid view type gracefully (e.g., flash message and redirect, or error page)
|
||||||
|
# For now, returning a simple error response
|
||||||
|
return f"Error: Invalid view type '{selected_view}'.", 400
|
||||||
|
|
||||||
|
# Fetch and process data
|
||||||
|
raw_workouts = _fetch_raw_workout_data(db.execute, person_id, start_date, end_date)
|
||||||
|
grouped_workouts, person_name = _group_workouts_by_date(raw_workouts)
|
||||||
|
|
||||||
|
# Prepare base context for the template
|
||||||
|
calendar_view_data = {
|
||||||
|
'person_id': person_id,
|
||||||
|
'person_name': person_name,
|
||||||
|
'view': selected_view,
|
||||||
|
'date': selected_date, # Pass the actual date object for display formatting
|
||||||
|
'prev_date': prev_date.strftime('%Y-%m-%d') if prev_date else None, # Format dates for URL generation
|
||||||
|
'next_date': next_date.strftime('%Y-%m-%d') if next_date else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add view-specific data
|
||||||
|
if selected_view == 'month':
|
||||||
|
calendar_view_data['days'] = _process_workouts_for_month_view(grouped_workouts, start_date, end_date, selected_date)
|
||||||
|
elif selected_view == 'year':
|
||||||
|
calendar_view_data['months'] = _process_workouts_for_year_view(grouped_workouts, selected_date)
|
||||||
|
|
||||||
|
# Format selected_date for URL generation consistently
|
||||||
|
selected_date_url_str = selected_date.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
# Render response (HTMX or full page)
|
||||||
|
if htmx:
|
||||||
|
# Use current_app imported from Flask
|
||||||
|
return render_block(current_app.jinja_env, 'calendar.html', 'content', **calendar_view_data), 200, {"HX-Push-Url": url_for('.get_calendar', person_id=person_id, view=selected_view, date=selected_date_url_str), "HX-Trigger": "refreshStats"}
|
||||||
|
return render_template('calendar.html', **calendar_view_data), 200, {"HX-Push-Url": url_for('.get_calendar', person_id=person_id, view=selected_view, date=selected_date_url_str), "HX-Trigger": "refreshStats"}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="flex items-center justify-between pt-2 pb-2">
|
<div class="flex items-center justify-between pt-2 pb-2">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex ml-1 md:ml-6">
|
<div class="flex ml-1 md:ml-6">
|
||||||
<button hx-get="{{ url_for('get_calendar', person_id=person_id) }}" hx-target="#container"
|
<button hx-get="{{ url_for('calendar.get_calendar', person_id=person_id) }}" hx-target="#container"
|
||||||
hx-vals='{"date": "{{ prev_date }}"}' hx-include="[name='view']" hx-push-url="true"
|
hx-vals='{"date": "{{ prev_date }}"}' hx-include="[name='view']" hx-push-url="true"
|
||||||
hx-swap="innerHTML swap:0.5s">
|
hx-swap="innerHTML swap:0.5s">
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</path>
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button hx-get="{{ url_for('get_calendar', person_id=person_id) }}" hx-target="#container"
|
<button hx-get="{{ url_for('calendar.get_calendar', person_id=person_id) }}" hx-target="#container"
|
||||||
hx-vals='{"date": "{{ next_date }}"}' hx-include="[name='view']" hx-push-url="true"
|
hx-vals='{"date": "{{ next_date }}"}' hx-include="[name='view']" hx-push-url="true"
|
||||||
hx-swap="innerHTML swap:0.5s">
|
hx-swap="innerHTML swap:0.5s">
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
@@ -40,9 +40,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mr-4">
|
<div class="mr-4">
|
||||||
<select name="view" hx-get="{{ url_for('get_calendar', person_id=person_id) }}" hx-target="#container"
|
<select name="view" hx-get="{{ url_for('calendar.get_calendar', person_id=person_id) }}"
|
||||||
hx-vals='{"date": "{{ date }}"}' hx-push-url="true" _="init js(me) tail.select(me, {}) end"
|
hx-target="#container" hx-vals='{"date": "{{ date }}"}' hx-push-url="true"
|
||||||
class="h-10 invisible">
|
_="init js(me) tail.select(me, {}) end" class="h-10 invisible">
|
||||||
<option value="month" {% if view=='month' %}selected{% endif %}>Month</option>
|
<option value="month" {% if view=='month' %}selected{% endif %}>Month</option>
|
||||||
<option value="year" {% if view=='year' %}selected{% endif %}>Year</option>
|
<option value="year" {% if view=='year' %}selected{% endif %}>Year</option>
|
||||||
<option value="notes">Notes</option>
|
<option value="notes">Notes</option>
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
{% for month in months %}
|
{% for month in months %}
|
||||||
<div>
|
<div>
|
||||||
<div class="bg-grey-lighter font-semibold text-center cursor-pointer"
|
<div class="bg-grey-lighter font-semibold text-center cursor-pointer"
|
||||||
hx-get="{{ url_for('get_calendar', person_id=person_id) }}" hx-target="#container"
|
hx-get="{{ url_for('calendar.get_calendar', person_id=person_id) }}" hx-target="#container"
|
||||||
hx-vals='{"date": "{{ month.first_day_of_month }}", "view": "month"}' hx-push-url="true"
|
hx-vals='{"date": "{{ month.first_day_of_month }}", "view": "month"}' hx-push-url="true"
|
||||||
_="on click go to the top of the body">
|
_="on click go to the top of the body">
|
||||||
{{ month.first_day_of_month | strftime('%B') }}
|
{{ month.first_day_of_month | strftime('%B') }}
|
||||||
|
|||||||
@@ -10,6 +10,16 @@
|
|||||||
<div class="prose max-w-none">
|
<div class="prose max-w-none">
|
||||||
<p>Updates and changes to the site will be documented here, with the most recent changes listed first.</p>
|
<p>Updates and changes to the site will be documented here, with the most recent changes listed first.</p>
|
||||||
|
|
||||||
|
<!-- New Entry for Refactoring -->
|
||||||
|
<hr class="my-6">
|
||||||
|
<h2 class="text-xl font-semibold mb-2">March 30, 2025</h2>
|
||||||
|
<ul class="list-disc pl-5 space-y-1">
|
||||||
|
<li>Refactored the calendar view and logic into its own blueprint (`routes/calendar.py`).</li>
|
||||||
|
<li>Cleaned up calendar code, removing the `Calendar` class and `pandas` dependency.</li>
|
||||||
|
<li>Fixed various bugs related to the calendar refactoring (circular imports, HTMX checks, `url_for`
|
||||||
|
build errors, date comparisons).</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<!-- Example Entry Structure -->
|
<!-- Example Entry Structure -->
|
||||||
<hr class="my-6"> {# Increased margin for HR #}
|
<hr class="my-6"> {# Increased margin for HR #}
|
||||||
<h2 class="text-xl font-semibold mb-2">March 30, 2025</h2> {# Reduced margin-bottom for H2 #}
|
<h2 class="text-xl font-semibold mb-2">March 30, 2025</h2> {# Reduced margin-bottom for H2 #}
|
||||||
@@ -17,6 +27,7 @@
|
|||||||
<li>Added the initial changelog page.</li>
|
<li>Added the initial changelog page.</li>
|
||||||
<li>Fixed a minor styling issue on the dashboard.</li>
|
<li>Fixed a minor styling issue on the dashboard.</li>
|
||||||
<li>Improved visual styling of the changelog page itself.</li> {# Added an entry for this change #}
|
<li>Improved visual styling of the changelog page itself.</li> {# Added an entry for this change #}
|
||||||
|
<li>Refactored the calendar view and logic into its own blueprint (`routes/calendar.py`).</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{# Add more entries below, following the pattern above #}
|
{# Add more entries below, following the pattern above #}
|
||||||
|
|||||||
@@ -117,7 +117,8 @@
|
|||||||
<span class="text-base font-normal text-gray-500">Current rep maxes</span>
|
<span class="text-base font-normal text-gray-500">Current rep maxes</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<a hx-get="{{ url_for('get_calendar', person_id=person.id) }}" hx-push-url="true" hx-target="#container"
|
<a hx-get="{{ url_for('calendar.get_calendar', person_id=person.id) }}" hx-push-url="true"
|
||||||
|
hx-target="#container"
|
||||||
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg p-2 cursor-pointer">View
|
class="text-sm font-medium text-cyan-600 hover:bg-gray-100 rounded-lg p-2 cursor-pointer">View
|
||||||
workouts</a>
|
workouts</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mr-4">
|
<div class="mr-4">
|
||||||
<select name="view" hx-get="{{ url_for('get_calendar', person_id=person_id) }}" hx-target="#container"
|
<select name="view" hx-get="{{ url_for('calendar.get_calendar', person_id=person_id) }}"
|
||||||
x-push-url="true" _="init js(me) tail.select(me, {}) end" class="h-10 invisible">
|
hx-target="#container" x-push-url="true" _="init js(me) tail.select(me, {}) end" class="h-10 invisible">
|
||||||
<option value="month">Month</option>
|
<option value="month">Month</option>
|
||||||
<option value="year">Year</option>
|
<option value="year">Year</option>
|
||||||
<option value="notes" selected>Notes</option>
|
<option value="notes" selected>Notes</option>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{% for p in people %}
|
{% for p in people %}
|
||||||
<li>
|
<li>
|
||||||
<a hx-get="{{ url_for('get_calendar' ,person_id=p['PersonId']) }}" hx-push-url="true" hx-target="#container"
|
<a hx-get="{{ url_for('calendar.get_calendar' ,person_id=p['PersonId']) }}" hx-push-url="true"
|
||||||
|
hx-target="#container"
|
||||||
class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 flex items-center p-2 group cursor-pointer page-link"
|
class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 flex items-center p-2 group cursor-pointer page-link"
|
||||||
_="on click add .hidden to #sidebar then remove .ml-64 from #main
|
_="on click add .hidden to #sidebar then remove .ml-64 from #main
|
||||||
on htmx:afterRequest go to the top of the body">
|
on htmx:afterRequest go to the top of the body">
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<select name="view" hx-get="{{ url_for('get_calendar', person_id=person_id) }}"
|
<select name="view" hx-get="{{ url_for('calendar.get_calendar', person_id=person_id) }}"
|
||||||
hx-target="#container" hx-push-url="true" _="init js(me) tail.select(me, {}) end"
|
hx-target="#container" hx-push-url="true" _="init js(me) tail.select(me, {}) end"
|
||||||
class="h-10 invisible">
|
class="h-10 invisible">
|
||||||
<option value="month">Month</option>
|
<option value="month">Month</option>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
d="m1 9 4-4-4-4" />
|
d="m1 9 4-4-4-4" />
|
||||||
</svg>
|
</svg>
|
||||||
{% set hx_current_url = request.headers.get('HX-Current-URL') %}
|
{% set hx_current_url = request.headers.get('HX-Current-URL') %}
|
||||||
<a hx-get="{{ hx_current_url if hx_current_url else url_for('get_calendar', person_id=person_id) }}"
|
<a hx-get="{{ hx_current_url if hx_current_url else url_for('calendar.get_calendar', person_id=person_id) }}"
|
||||||
hx-push-url="true" hx-target="#container"
|
hx-push-url="true" hx-target="#container"
|
||||||
class="ms-1 text-sm font-medium text-gray-700 hover:text-blue-600 md:ms-2 dark:text-gray-400 dark:hover:text-white cursor-pointer">{{person_name}}</a>
|
class="ms-1 text-sm font-medium text-gray-700 hover:text-blue-600 md:ms-2 dark:text-gray-400 dark:hover:text-white cursor-pointer">{{person_name}}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user