Create endpoint that returns graphs of an overview of users workouts (Needs to be refactored)
This commit is contained in:
144
app.py
144
app.py
@@ -1,3 +1,4 @@
|
||||
from collections import defaultdict
|
||||
from flask_basicauth import BasicAuth
|
||||
import matplotlib.dates as mdates
|
||||
import matplotlib.pyplot as plt
|
||||
@@ -213,6 +214,149 @@ def create_workout(user_id):
|
||||
return f'Added {humanize.naturaldelta(workout.duration)} session.', 201
|
||||
|
||||
|
||||
@app.route('/user/<int:user_id>/workouts/graph', methods=['GET'])
|
||||
def graph_user_workouts(user_id):
|
||||
user = User.query.get(user_id)
|
||||
workouts = user.workouts
|
||||
|
||||
earliest_workout_date = min([workout.created_at.date()
|
||||
for workout in workouts])
|
||||
most_recent_workout_date = max(
|
||||
[workout.created_at.date() for workout in workouts])
|
||||
|
||||
start_date = request.args.get(
|
||||
'start_date', default=earliest_workout_date, type=toDate)
|
||||
end_date = request.args.get(
|
||||
'end_date', default=most_recent_workout_date, type=toDate)
|
||||
|
||||
attributes = request.args.getlist(
|
||||
'attributes', type=str)
|
||||
|
||||
period = request.args.get('period', default='day', type=str)
|
||||
|
||||
return plot_averaged_attributes(workouts, start_date, end_date, period, attributes)
|
||||
|
||||
|
||||
def daterange(start_date, end_date, delta=timedelta(days=1)):
|
||||
"""Helper generator to iterate over date ranges."""
|
||||
curr_date = start_date
|
||||
while curr_date < end_date:
|
||||
yield curr_date
|
||||
curr_date += delta
|
||||
|
||||
|
||||
def average_workout_attributes_per_period(workouts, start_date, end_date, period, attributes):
|
||||
"""
|
||||
Returns a dictionary of averaged attributes for workouts within each given period
|
||||
between start_date and end_date.
|
||||
|
||||
Parameters:
|
||||
- workouts (list): List of Workout objects.
|
||||
- start_date, end_date: Date range to consider.
|
||||
- period (str): 'day', 'week', or 'month'.
|
||||
- attributes (list): List of attribute names to average.
|
||||
|
||||
Returns:
|
||||
- Dictionary: A nested dictionary where keys are dates representing the start of each period,
|
||||
and the values are dictionaries with averaged attributes for that period.
|
||||
"""
|
||||
|
||||
if period == "day":
|
||||
delta = timedelta(days=1)
|
||||
elif period == "week":
|
||||
delta = timedelta(weeks=1)
|
||||
elif period == "month":
|
||||
# approximating month as 4 weeks for simplicity
|
||||
delta = timedelta(weeks=4)
|
||||
else:
|
||||
raise ValueError(f"Invalid period: {period}")
|
||||
|
||||
results = defaultdict(lambda: defaultdict(float))
|
||||
|
||||
for start_period in daterange(start_date, end_date, delta):
|
||||
end_period = start_period + delta
|
||||
filtered_workouts = [
|
||||
w for w in workouts if start_period <= w.created_at.date() < end_period]
|
||||
|
||||
for attribute in attributes:
|
||||
if hasattr(Workout, attribute):
|
||||
valid_values = [getattr(w, attribute) for w in filtered_workouts if getattr(
|
||||
w, attribute) is not None]
|
||||
if valid_values:
|
||||
average = sum(valid_values) / len(valid_values)
|
||||
results[start_period][attribute] = average
|
||||
else:
|
||||
results[start_period][attribute] = 0 # None
|
||||
|
||||
results[start_period]['workout_count'] = len(
|
||||
filtered_workouts)
|
||||
|
||||
return dict(results)
|
||||
|
||||
|
||||
def create_user_graph(x_values, y_data, filename, x_label='Time'):
|
||||
"""Create a graph for given x-values and y-values.
|
||||
|
||||
Parameters:
|
||||
- x_values: A list of x-values (common for all graphs).
|
||||
- y_data: A dictionary where key is y_label and value is list of y-values.
|
||||
- filename: Name for the generated file.
|
||||
- x_label: Label for x-axis.
|
||||
|
||||
Returns:
|
||||
- Flask Response object containing the image of the graph.
|
||||
"""
|
||||
fig, ax = plt.subplots()
|
||||
|
||||
# Plotting multiple lines
|
||||
for y_label, y_values in y_data.items():
|
||||
ax.plot(x_values, y_values, label=y_label)
|
||||
|
||||
ax.set_xlabel(x_label)
|
||||
ax.legend() # Show legend to differentiate between multiple attributes
|
||||
ax.xaxis.set_major_formatter(mdates.DateFormatter("%d/%m"))
|
||||
ax.set_ylim(bottom=0)
|
||||
|
||||
# Save the graph to a bytes buffer
|
||||
buffer = io.BytesIO()
|
||||
plt.savefig(buffer, format='png', transparent=True, bbox_inches='tight')
|
||||
buffer.seek(0)
|
||||
|
||||
# Create a response object with the graph image
|
||||
response = make_response(buffer.getvalue())
|
||||
response.headers['Content-Type'] = 'image/png'
|
||||
response.headers['Content-Disposition'] = f'attachment; filename={filename}.png'
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def plot_averaged_attributes(workouts_list, start_date, end_date, period, attributes):
|
||||
"""Creates a graph for averaged attributes over a period.
|
||||
|
||||
Parameters:
|
||||
- start_date, end_date: Date range to consider.
|
||||
- period (str): 'day', 'week', or 'month'.
|
||||
- attributes (list): A list of attribute names to plot.
|
||||
|
||||
Returns:
|
||||
- Flask Response object containing the image of the graph.
|
||||
"""
|
||||
# Fetching the data
|
||||
averaged_attributes = average_workout_attributes_per_period(
|
||||
workouts_list, start_date, end_date, period, attributes)
|
||||
|
||||
# Extracting x_values and y_values
|
||||
x_values = list(averaged_attributes.keys())
|
||||
|
||||
y_data = {}
|
||||
for attribute in attributes:
|
||||
y_data[attribute] = [averaged_attributes[date][attribute]
|
||||
for date in x_values]
|
||||
|
||||
# Creating the graph
|
||||
return create_user_graph(x_values, y_data, filename=f"average_attributes_over_{period}")
|
||||
|
||||
|
||||
@app.route('/user/<int:user_id>/workout/<int:workout_id>/<string:graph_type>', methods=['GET'])
|
||||
def workout(user_id, workout_id, graph_type):
|
||||
workout = Workout.query.filter_by(user_id=user_id, id=workout_id) \
|
||||
|
||||
@@ -71,9 +71,17 @@
|
||||
</span>
|
||||
</button>
|
||||
</h2>
|
||||
|
||||
<div class="!visible collapse p-4 hidden" id="workouts-list-accordion-{{ user.id }}">
|
||||
{{ render_partial('partials/calendar.html', calendar_month=user.calendar_month, user_id = user.id) }}
|
||||
|
||||
<img src="{{ url_for('graph_user_workouts', user_id=user.id, period='week', attributes=['workout_count']) }}"
|
||||
loading="lazy" alt="No image" class="mx-auto">
|
||||
<img src="{{ url_for('graph_user_workouts', user_id=user.id, period='month', attributes=['duration']) }}"
|
||||
loading="lazy" alt="No image" class="mx-auto">
|
||||
|
||||
|
||||
|
||||
<div id="workouts-list-wrapper-for-user-{{ user.id }}" class="mt-5 pl-2">{{
|
||||
render_partial('partials/workouts_list_fragment.html',
|
||||
workouts=workouts[:7], user_id = user.id) }}</div>
|
||||
|
||||
Reference in New Issue
Block a user