Refactor calendar (month and year view), switching to vertical slice arch. Also in progress of refactoring of stats so they are retreived after inital page load for performance sake

This commit is contained in:
Peter Stockings
2024-04-11 13:45:38 +10:00
parent 76789a4934
commit 63d997a3f1
7 changed files with 302 additions and 133 deletions

16
app.py
View File

@@ -7,7 +7,7 @@ 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 count_prs_over_time, get_date_info, get_people_and_exercise_rep_maxes, convert_str_to_date, get_earliest_and_latest_workout_date, filter_workout_topsets, first_and_last_visible_days_in_month, get_weekly_pr_graph_model, get_workout_counts
from utils import count_prs_over_time, get_people_and_exercise_rep_maxes, convert_str_to_date, get_earliest_and_latest_workout_date, filter_workout_topsets, first_and_last_visible_days_in_month, get_weekly_pr_graph_model, get_workout_counts
from flask_htmx import HTMX
import minify_html
from urllib.parse import quote
@@ -123,8 +123,6 @@ def get_person(person_id):
@ app.route("/person/<int:person_id>/calendar")
@ validate_person
def get_calendar(person_id):
person = db.get_person(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'
@@ -134,12 +132,11 @@ def get_calendar(person_id):
elif selected_view == 'notes':
return redirect(url_for('get_person_notes', person_id=person_id))
# selected_view = month | year | all
date_info = get_date_info(selected_date, selected_view)
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', person=person, selected_date=selected_date, selected_view=selected_view, **date_info, datetime=datetime, timedelta=timedelta, relativedelta=relativedelta, first_and_last_visible_days_in_month=first_and_last_visible_days_in_month), 200, {"HX-Push-Url": url_for('get_calendar', person_id=person_id, view=selected_view, date=selected_date)}
return render_template('calendar.html', person=person, selected_date=selected_date, selected_view=selected_view, **date_info, datetime=datetime, timedelta=timedelta, relativedelta=relativedelta, first_and_last_visible_days_in_month=first_and_last_visible_days_in_month), 200, {"HX-Push-Url": url_for('get_calendar', person_id=person_id, view=selected_view, date=selected_date)}
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)}
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)}
@ app.route("/person/<int:person_id>/notes", methods=['GET'])
@ validate_person
@@ -428,6 +425,11 @@ def get_exercise_progress_for_user(person_id, exercise_id):
return render_template('partials/sparkline.html', **exercise_progress)
@app.route("/stats/person/<int:person_id>", methods=['GET'])
def get_stats_for_person(person_id):
stats = db.stats.fetch_stats_for_person(person_id)
return render_template('partials/stats.html', stats=stats)
@app.teardown_appcontext
def closeConnection(exception):

6
db.py
View File

@@ -6,13 +6,15 @@ from datetime import datetime
from dateutil.relativedelta import relativedelta
from urllib.parse import urlparse
from flask import g
from features.calendar import Calendar
from features.stats import Stats
from utils import count_prs_over_time, get_all_exercises_from_topsets, get_exercise_graph_model, get_stats_from_topsets, get_topsets_for_person, get_weekly_pr_graph_model, get_workout_counts, get_workouts
class DataBase():
def __init__(self, app):
self.calendar = Calendar(self.execute)
self.stats = Stats(self.execute)
db_url = urlparse(os.environ['DATABASE_URL'])
# if db_url is null then throw error
if not db_url:

141
features/calendar.py Normal file
View File

@@ -0,0 +1,141 @@
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': []
}
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

85
features/stats.py Normal file
View File

@@ -0,0 +1,85 @@
from collections import Counter
from datetime import date
class Stats:
def __init__(self, db_connection_method):
self.execute = db_connection_method
def get_stats_from_topsets(self, topsets):
if not topsets:
return []
# Extracting necessary fields
workout_ids = [t['WorkoutId'] for t in topsets if t['WorkoutId']]
person_ids = [t['PersonId'] for t in topsets if t['PersonId']]
start_dates = [t['StartDate'] for t in topsets if t['StartDate']]
workout_count = len(set(workout_ids))
people_count = len(set(person_ids))
total_sets = len(topsets)
stats = [
{"Text": "Total Workouts", "Value": workout_count},
{"Text": "Total Sets", "Value": total_sets}
]
if people_count > 1:
stats.append({"Text": "People tracked", "Value": people_count})
if workout_count > 0:
first_workout_date = min(start_dates)
last_workout_date = max(start_dates)
current_date = date.today()
stats.append({"Text": "Days Since First Workout",
"Value": (current_date - first_workout_date).days})
if workout_count >= 2:
stats.append({"Text": "Days Since Last Workout",
"Value": (current_date - last_workout_date).days})
average_sets_per_workout = round(total_sets / workout_count, 1)
stats.append({"Text": "Average sets per workout",
"Value": average_sets_per_workout})
training_duration = last_workout_date - first_workout_date
if training_duration.days > 0:
average_workouts_per_week = round(
workout_count / (training_duration.days / 7), 1)
stats.append({"Text": "Average Workouts Per Week",
"Value": average_workouts_per_week})
return stats
def fetch_stats_for_person(self, person_id):
query = """
SELECT
t.workout_id AS "WorkoutId",
w.person_id AS "PersonId",
w.start_date AS "StartDate"
FROM
topset t
JOIN workout w ON t.workout_id = w.workout_id
JOIN person p ON w.person_id = p.person_id
WHERE p.person_id = %s
"""
workouts_data = self.execute(query, [person_id])
person_stats = self.get_stats_from_topsets(workouts_data)
return person_stats
def fetch_all_stats(self):
query = """
SELECT
t.workout_id AS "WorkoutId",
w.person_id AS "PersonId",
w.start_date AS "StartDate"
FROM
topset t
JOIN workout w ON t.workout_id = w.workout_id
JOIN person p ON w.person_id = p.person_id;
"""
workouts_data = self.execute(query, [])
person_stats = self.get_stats_from_topsets(workouts_data)
return person_stats

View File

@@ -108,7 +108,8 @@
{% block content %}
{% endblock %}
</div>
{% block stats %}
{% endblock %}
</main>
</div>
@@ -150,6 +151,9 @@
</div>
</template>
{% block add_workout_button %}
{% endblock %}
</body>
</html>

View File

@@ -2,16 +2,17 @@
{% block content %}
<a hx-get="{{ url_for('get_calendar', person_id=person['PersonId']) }}" hx-target="#container"
hx-vals='{"date": "{{ selected_date }}", "view": "{{ selected_view }}"}' hx-trigger="refreshView"
id="refreshViewElement"></a>
<a hx-get="{{ url_for('get_calendar', person_id=person_id) }}" hx-target="#container"
hx-vals='{"date": "{{ date }}", "view": "{{ view }}"}' hx-trigger="refreshView" id="refreshViewElement"
hx-swap="innerHTML swap:0.5s"></a>
<div class="flex flex-grow flex-col bg-white sm:rounded shadow overflow-hidden">
<div class="flex items-center justify-between pt-2 pb-2">
<div class="flex">
<div class="flex ml-1 md:ml-6">
<button hx-get="{{ url_for('get_calendar', person_id=person['PersonId']) }}" hx-target="#container"
hx-vals='{"date": "{{ previous_date }}"}' hx-include="[name='view']" hx-push-url="true">
<button hx-get="{{ url_for('get_calendar', person_id=person_id) }}" hx-target="#container"
hx-vals='{"date": "{{ prev_date }}"}' hx-include="[name='view']" hx-push-url="true"
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"
stroke="currentColor" data-darkreader-inline-stroke=""
style="--darkreader-inline-stroke:currentColor;">
@@ -19,8 +20,9 @@
</path>
</svg>
</button>
<button hx-get="{{ url_for('get_calendar', person_id=person['PersonId']) }}" hx-target="#container"
hx-vals='{"date": "{{ next_date }}"}' hx-include="[name='view']" hx-push-url="true">
<button hx-get="{{ url_for('get_calendar', person_id=person_id) }}" hx-target="#container"
hx-vals='{"date": "{{ next_date }}"}' hx-include="[name='view']" hx-push-url="true"
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"
stroke="currentColor" data-darkreader-inline-stroke=""
style="--darkreader-inline-stroke:currentColor;">
@@ -30,29 +32,29 @@
</button>
</div>
{% if selected_view == 'month' %}
<h2 class="ml-2 text-xl font-bold leading-none">{{ selected_date | strftime('%B, %Y') }}</h2>
{% if view == 'month' %}
<h2 class="ml-2 text-xl font-bold leading-none">{{ date | strftime('%B, %Y') }}</h2>
{% else %}
<h2 class="ml-2 text-xl font-bold leading-none">{{ selected_date | strftime('%Y') }}</h2>
<h2 class="ml-2 text-xl font-bold leading-none">{{ date | strftime('%Y') }}</h2>
{% endif %}
<span
class="bg-blue-100 text-blue-800 text-sm font-medium mr-2 px-2.5 py-0.5 rounded dark:bg-blue-200 dark:text-blue-800 ml-1 sm:ml-5 flex justify-center items-center ">{{
person['PersonName']}}</span>
class="bg-blue-100 text-blue-800 text-sm font-medium mr-2 px-2.5 py-0.5 rounded dark:bg-blue-200 dark:text-blue-800 ml-1 sm:ml-5 flex justify-center items-center ">
{{ person_name }}
</span>
</div>
<div class="mr-4">
<select name="view" hx-get="{{ url_for('get_calendar', person_id=person['PersonId']) }}"
hx-target="#container" hx-vals='{"date": "{{ selected_date }}"}' hx-push-url="true"
_="init js(me) tail.select(me, {}) end">
<option value="month" {% if selected_view=='month' %}selected{% endif %}>Month</option>
<option value="year" {% if selected_view=='year' %}selected{% endif %}>Year</option>
<select name="view" hx-get="{{ url_for('get_calendar', person_id=person_id) }}" hx-target="#container"
hx-vals='{"date": "{{ date }}"}' hx-push-url="true" _="init js(me) tail.select(me, {}) end">
<option value="month" {% if view=='month' %}selected{% endif %}>Month</option>
<option value="year" {% if view=='year' %}selected{% endif %}>Year</option>
<option value="notes">Notes</option>
<option value="all">All</option>
</select>
</div>
</div>
{% if selected_view == 'month' %}
{% if view == 'month' %}
<div class="flex flex-col px-2 py-2 -mb-px">
<div class="grid grid-cols-7 pl-2 pr-2">
@@ -88,57 +90,44 @@
<div class="grid grid-cols-7 overflow-hidden flex-1 pl-2 pr-2 w-full">
{% for i in range((end_date-start_date).days + 1) %}
{% set date = start_date + timedelta(days=i) %}
{% set workout = person['Workouts'] |
get_first_element_from_list_with_matching_attribute(
'StartDate',
date) %}
<div class="{% if date == datetime.today().date() %}rounded-md border-4 border-green-50{% endif %} border flex flex-col h-36 sm:h-40 md:h-30 lg:h-30 mx-auto mx-auto overflow-hidden w-full pt-2 pl-1 cursor-pointer {% if selected_date.month != date.month %}bg-gray-100{% endif %}"
{% if workout %}
hx-get="{{ url_for('get_workout_modal', person_id=person['PersonId'], workout_id=workout['WorkoutId']) }}"
hx-target='body' hx-swap='beforeend' {% endif %}>
{% for day in days %}
<div
class="{% if day.is_today %}rounded-md border-4 border-green-50{% endif %} border flex flex-col h-36 sm:h-40 md:h-30 lg:h-30 mx-auto mx-auto overflow-hidden w-full pt-2 pl-1 cursor-pointer {% if day.is_in_current_month %}bg-gray-100{% endif %}">
<div class="top h-5 w-full">
<span class="text-gray-500 font-semibold">{{ date.day }}</span>
<span class="text-gray-500 font-semibold">{{ day.day }}</span>
</div>
<div class="bottom flex-grow py-1 w-full">
{% if workout['TopSets']|length > 0 %}
{% for topset in workout['TopSets'] %}
{% for workout in day.workouts %}
<div class="bottom flex-grow py-1 w-full"
hx-get="{{ url_for('get_workout_modal', person_id=person_id, workout_id=workout.workout_id) }}"
hx-target='body' hx-swap='beforeend'>
{% for set in workout.sets %}
<button
class="flex flex-col xl:flex-row items-start lg:items-center flex-shrink-0 px-0 sm:px-0.5 md:px-0.5 lg:px-0.5 text-xs">
<span class="ml-0 sm:ml-0.5 md:ml-2 lg:ml-2 font-medium leading-none truncate">{{
topset['ExerciseName'] }}</span>
<span class="ml-0 sm:ml-0.5 md:ml-2 lg:ml-2 font-light leading-none">{{ topset['Repetitions'] }}
x
{{
topset['Weight']
}}kg</span>
set.exercise_name }}</span>
<span class="ml-0 sm:ml-0.5 md:ml-2 lg:ml-2 font-light leading-none">{{ set.repetitions }} x {{
set.weight }}kg</span>
</button>
{% endfor %}
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
</div>
{% elif selected_view == 'year'%}
{% elif view == 'year'%}
<div class="flex justify-center px-2 py-2 -mb-px">
<div
class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-4 lg:gap-10 bg-white overflow-hidden">
{% for i in range(12) %}
{% set date = start_date + relativedelta(months=i) %}
{% set first_day_of_month = date.replace(day=1) %}
{% set last_day_of_month = date + relativedelta(day=31) %}
{% set (first_day, last_day) = first_and_last_visible_days_in_month(first_day_of_month, last_day_of_month)
%}
{% for month in months %}
<div>
<div class="bg-grey-lighter font-semibold text-center cursor-pointer"
hx-get="{{ url_for('get_calendar', person_id=person['PersonId']) }}" hx-target="#container"
hx-vals='{"date": "{{ first_day_of_month }}", "view": "month"}' hx-push-url="true"
_="on click go to the top of the body">{{
first_day_of_month | strftime('%B %Y') }}
hx-get="{{ url_for('get_calendar', person_id=person_id) }}" hx-target="#container"
hx-vals='{"date": "{{ month.first_day_of_month }}", "view": "month"}' hx-push-url="true"
_="on click go to the top of the body">
{{ month.first_day_of_month | strftime('%B') }}
</div>
<div>
<div class="grid grid-cols-7 border-b font-semibold">
@@ -152,18 +141,15 @@
</div>
<div class="grid grid-cols-7 overflow-hidden flex-1 pl-2 pr-2 w-full">
{% for i in range((last_day-first_day).days + 1) %}
{% set day_date = first_day + timedelta(days=i) %}
{% set workout = person['Workouts'] | get_first_element_from_list_with_matching_attribute(
'StartDate',
day_date) %}
{% set is_in_month = day_date.month == first_day_of_month.month%}
<div class="{% if day_date == datetime.today().date() and is_in_month %}rounded-md border-4 border-green-50 border{% endif %} py-3 px-4 hover:bg-blue hover:text-white text-center cursor-pointer rounded-md {% if workout and is_in_month %}bg-green-100{% endif %}"
{% if workout %}
hx-get="{{ url_for('get_workout_modal', person_id=person['PersonId'], workout_id=workout['WorkoutId']) }}"
{% for day in month.days %}
<div class="{% if day.is_today and day.is_in_current_month %}rounded-md border-4 border-green-50 border{% endif %} py-3 px-4 hover:bg-blue hover:text-white text-center cursor-pointer rounded-md {% if day.has_workouts and day.is_in_current_month %}bg-green-100{% endif %}"
{% if day.has_workouts %}
hx-get="{{ url_for('get_workout_modal', person_id=person_id, workout_id=day.first_workout_id) }}"
hx-target='body' hx-swap='beforeend' {% endif %}>
{% if is_in_month %}
{{ day_date.day }} {% endif %}</div>
{% if day.is_in_current_month %}
{{ day.day }}
{% endif %}
</div>
{% endfor %}
</div>
</div>
@@ -175,11 +161,20 @@
{% endif %}
</div>
</div>
{{ render_partial('partials/stats.html', stats=person['Stats']) }}
{% endblock %}
{% block stats %}
<div class="hidden" hx-get="{{ url_for('get_stats_for_person', person_id=person_id) }}" hx-trigger="load"
hx-target="this" hx-swap="outerHTML">
</div>
{% endblock %}
{% block add_workout_button %}
<button
class="fixed z-90 bottom-10 right-8 bg-blue-600 w-20 h-20 rounded-full drop-shadow-lg flex justify-center items-center text-white text-4xl hover:bg-blue-700 hover:drop-shadow-2xl hover:animate-bounce duration-300"
hx-post="{{ url_for('create_workout', person_id=person['PersonId']) }}" hx-target='body' hx-swap='beforeend'>
hx-post="{{ url_for('create_workout', person_id=person_id) }}" hx-target='body' hx-swap='beforeend'>
<svg viewBox="0 0 20 20" enable-background="new 0 0 20 20" class="w-6 h-6 inline-block">
<path fill="#FFFFFF" d="M16,10c0,0.553-0.048,1-0.601,1H11v4.399C11,15.951,10.553,16,10,16c-0.553,0-1-0.049-1-0.601V11H4.601
C4.049,11,4,10.553,4,10c0-0.553,0.049-1,0.601-1H9V4.601C9,4.048,9.447,4,10,4c0.553,0,1,0.048,1,0.601V9h4.399

View File

@@ -1,6 +1,5 @@
import colorsys
from datetime import datetime, date, timedelta
import random
import numpy as np
import pandas as pd
@@ -214,65 +213,6 @@ def flatten(lst):
result.append(item)
return result
def get_date_info(input_date, selected_view):
if selected_view not in ['month', 'year']:
raise ValueError(
'selected_view must be either "month" or "year"')
# First day of the month
first_day_of_month = input_date.replace(day=1)
# Last day of the month
if input_date.month == 12:
last_day_of_month = input_date.replace(
year=input_date.year+1, month=1, day=1) - timedelta(days=1)
else:
last_day_of_month = input_date.replace(
month=input_date.month+1, day=1) - timedelta(days=1)
# First and last day of the year
first_day_of_year = input_date.replace(month=1, day=1)
last_day_of_year = input_date.replace(
year=input_date.year+1, month=1, day=1) - timedelta(days=1)
# Next/previous month
year, month = divmod(input_date.year * 12 + input_date.month, 12)
next_month = date(year, month + 1, 1)
prev_month_last_day = first_day_of_month - timedelta(days=1)
prev_month = prev_month_last_day.replace(day=1)
# Next/previous year
next_year = input_date.replace(year=input_date.year+1)
prev_year = input_date.replace(year=input_date.year-1)
# Business logic, should move above to a separate function
if selected_view == 'month':
# Step 1: Find the first Sunday before or on the first day of the month
days_to_subtract = (first_day_of_month.weekday() + 1) % 7
start_date = first_day_of_month - timedelta(days=days_to_subtract)
# Step 2: Calculate the last day to display, based on the number of weeks
end_date = start_date + timedelta(days=6 * 7 - 1)
return {
'next_date': next_month,
'previous_date': prev_month,
'first_date_of_view': first_day_of_month,
'last_date_of_view': last_day_of_month,
'start_date': start_date,
'end_date': end_date,
}
elif selected_view == 'year':
return {
'next_date': next_year,
'previous_date': prev_year,
'first_date_of_view': first_day_of_year,
'last_date_of_view': last_day_of_year,
'start_date': first_day_of_year,
'end_date': last_day_of_year,
}
def get_exercise_graph_model(title, estimated_1rm, repetitions, weight, start_dates, messages, epoch, person_id, exercise_id):
min_date, max_date = min(start_dates), max(start_dates)
min_e1rm, max_e1rm = min(estimated_1rm), max(estimated_1rm)