option to use power zones
This commit is contained in:
@@ -58,6 +58,24 @@ class FlexibleDecimalField(forms.DecimalField):
|
|||||||
value = value.replace('.', '').replace(',', '.')
|
value = value.replace('.', '').replace(',', '.')
|
||||||
return super(FlexibleDecimalField, self).to_python(value)
|
return super(FlexibleDecimalField, self).to_python(value)
|
||||||
|
|
||||||
|
class TrainingZonesForm(forms.Form):
|
||||||
|
zoneschoices = (
|
||||||
|
('power','Power Zones'),
|
||||||
|
('hr','Heart Rate Zones')
|
||||||
|
)
|
||||||
|
|
||||||
|
zones = forms.ChoiceField(initial='hr',label='Training Zones',choices=zoneschoices)
|
||||||
|
startdate = forms.DateField(
|
||||||
|
initial=timezone.now()-datetime.timedelta(days=365),
|
||||||
|
widget=AdminDateWidget(), #format='%Y-%m-%d'),
|
||||||
|
label='Start Date')
|
||||||
|
enddate = forms.DateField(
|
||||||
|
initial=timezone.now(),
|
||||||
|
# widget=SelectDateWidget(years=range(1990,2050)),
|
||||||
|
widget=AdminDateWidget(), #format='%Y-%m-%d'),
|
||||||
|
label='End Date')
|
||||||
|
|
||||||
|
|
||||||
class InstantPlanSelectForm(forms.Form):
|
class InstantPlanSelectForm(forms.Form):
|
||||||
datechoices = (
|
datechoices = (
|
||||||
('startdate','start date'),
|
('startdate','start date'),
|
||||||
|
|||||||
@@ -715,13 +715,14 @@ def interactive_activitychart2(workouts,startdate,enddate,stack='type',toolbar_l
|
|||||||
dd = w.date.strftime('%m/%d')
|
dd = w.date.strftime('%m/%d')
|
||||||
dd2 = w.date.strftime('%Y/%m/%d')
|
dd2 = w.date.strftime('%Y/%m/%d')
|
||||||
dd3 = w.date.strftime('%Y/%m')
|
dd3 = w.date.strftime('%Y/%m')
|
||||||
|
|
||||||
du = w.duration.hour*60+w.duration.minute
|
du = w.duration.hour*60+w.duration.minute
|
||||||
trimp = w.trimp
|
trimp = w.trimp
|
||||||
rscore = w.rscore
|
rscore = w.rscore
|
||||||
if rscore == 0: # pragma: no cover
|
if rscore == 0: # pragma: no cover
|
||||||
rscore = w.hrtss
|
rscore = w.hrtss
|
||||||
|
|
||||||
if totaldays<30: # pragma: no cover
|
if totaldays<=30: # pragma: no cover
|
||||||
dates.append(dd)
|
dates.append(dd)
|
||||||
dates_sorting.append(dd2)
|
dates_sorting.append(dd2)
|
||||||
else:
|
else:
|
||||||
@@ -759,7 +760,7 @@ def interactive_activitychart2(workouts,startdate,enddate,stack='type',toolbar_l
|
|||||||
dd = d.strftime('%d')
|
dd = d.strftime('%d')
|
||||||
|
|
||||||
|
|
||||||
if totaldays<30:
|
if totaldays<=30:
|
||||||
dates.append(d.strftime('%m/%d'))
|
dates.append(d.strftime('%m/%d'))
|
||||||
dates_sorting.append(d.strftime('%Y/%m/%d'))
|
dates_sorting.append(d.strftime('%Y/%m/%d'))
|
||||||
else:
|
else:
|
||||||
@@ -6558,7 +6559,7 @@ def interactive_otw_advanced_pace_chart(id=0,promember=0):
|
|||||||
|
|
||||||
return [script,div]
|
return [script,div]
|
||||||
|
|
||||||
def get_zones_report(rower,startdate,enddate):
|
def get_zones_report(rower,startdate,enddate,trainingzones='hr'):
|
||||||
duration = enddate-startdate
|
duration = enddate-startdate
|
||||||
|
|
||||||
totaldays = duration.total_seconds()/(24*3600)
|
totaldays = duration.total_seconds()/(24*3600)
|
||||||
@@ -6592,90 +6593,141 @@ def get_zones_report(rower,startdate,enddate):
|
|||||||
#totalmeters,totalhours, totalminutes, totalseconds = get_totals(workouts)
|
#totalmeters,totalhours, totalminutes, totalseconds = get_totals(workouts)
|
||||||
|
|
||||||
hrzones = rower.hrzones
|
hrzones = rower.hrzones
|
||||||
|
powerzones = rower.powerzones
|
||||||
|
|
||||||
for w in workouts:
|
for w in workouts:
|
||||||
dd = w.date.strftime('%m/%d')
|
dd = w.date.strftime('%m/%d')
|
||||||
dd2 = w.date.strftime('%Y/%m/%d')
|
dd2 = w.date.strftime('%Y/%m/%d')
|
||||||
dd3 = w.date.strftime('%Y/%m')
|
dd3 = w.date.strftime('%Y/%m')
|
||||||
|
dd4 = arrow.get(w.date).isocalendar()[1]
|
||||||
|
#print(w.date,arrow.get(w.date),arrow.get(w.date).isocalendar())
|
||||||
|
|
||||||
qryw = 'workoutid == {workoutid}'.format(workoutid=w.id)
|
qryw = 'workoutid == {workoutid}'.format(workoutid=w.id)
|
||||||
|
|
||||||
qry = 'hr < {ut2}'.format(ut2=rower.ut2)
|
qry = 'hr < {ut2}'.format(ut2=rower.ut2)
|
||||||
|
if trainingzones == 'power':
|
||||||
|
qry = 'power < {ut2}'.format(ut2=rower.pw_ut2)
|
||||||
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||||
if totaldays<30:
|
if totaldays<=30:
|
||||||
dates.append(dd)
|
dates.append(dd)
|
||||||
dates_sorting.append(dd2)
|
dates_sorting.append(dd2)
|
||||||
|
elif totaldays<=121:
|
||||||
|
dates.append(dd4)
|
||||||
|
dates_sorting.append(dd4)
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
dates.append(dd3)
|
dates.append(dd3)
|
||||||
dates_sorting.append(dd3)
|
dates_sorting.append(dd3)
|
||||||
minutes.append(timeinzone)
|
minutes.append(timeinzone)
|
||||||
hours.append(timeinzone/60.)
|
hours.append(timeinzone/60.)
|
||||||
|
if trainingzones == 'hr':
|
||||||
zones.append('<{ut2}'.format(ut2=hrzones[1]))
|
zones.append('<{ut2}'.format(ut2=hrzones[1]))
|
||||||
|
else:
|
||||||
|
zones.append('<{ut2}'.format(ut2=powerzones[1]))
|
||||||
#print(w,dd,timeinzone,'<UT2')
|
#print(w,dd,timeinzone,'<UT2')
|
||||||
|
|
||||||
qry = '{ut2} <= hr < {ut1}'.format(ut1=rower.ut1,ut2=rower.ut2)
|
qry = '{ut2} <= hr < {ut1}'.format(ut1=rower.ut1,ut2=rower.ut2)
|
||||||
|
if trainingzones == 'power':
|
||||||
|
qry = '{ut2} <= power < {ut2}'.format(ut1=rower.pw_ut1,ut2=rower.pw_ut2)
|
||||||
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||||
if totaldays<30:
|
if totaldays<=30:
|
||||||
dates.append(dd)
|
dates.append(dd)
|
||||||
dates_sorting.append(dd2)
|
dates_sorting.append(dd2)
|
||||||
|
elif totaldays<=121:
|
||||||
|
dates.append(dd4)
|
||||||
|
dates_sorting.append(dd4)
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
dates.append(dd3)
|
dates.append(dd3)
|
||||||
dates_sorting.append(dd3)
|
dates_sorting.append(dd3)
|
||||||
minutes.append(timeinzone)
|
minutes.append(timeinzone)
|
||||||
hours.append(timeinzone/60.)
|
hours.append(timeinzone/60.)
|
||||||
|
if trainingzones == 'hr':
|
||||||
zones.append(hrzones[1])
|
zones.append(hrzones[1])
|
||||||
|
else:
|
||||||
|
zones.append(powerzones[1])
|
||||||
#print(w,dd,timeinzone,'UT2')
|
#print(w,dd,timeinzone,'UT2')
|
||||||
|
|
||||||
qry = '{ut1} <= hr < {at}'.format(ut1=rower.ut1,at=rower.at)
|
qry = '{ut1} <= hr < {at}'.format(ut1=rower.ut1,at=rower.at)
|
||||||
|
if trainingzones == 'power':
|
||||||
|
qry = '{ut1} <= power < {at}'.format(ut1=rower.pw_ut1,at=rower.pw_at)
|
||||||
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||||
if totaldays<30:
|
if totaldays<=30:
|
||||||
dates.append(dd)
|
dates.append(dd)
|
||||||
dates_sorting.append(dd2)
|
dates_sorting.append(dd2)
|
||||||
|
elif totaldays<=121:
|
||||||
|
dates.append(dd4)
|
||||||
|
dates_sorting.append(dd4)
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
dates.append(dd3)
|
dates.append(dd3)
|
||||||
dates_sorting.append(dd3)
|
dates_sorting.append(dd3)
|
||||||
minutes.append(timeinzone)
|
minutes.append(timeinzone)
|
||||||
hours.append(timeinzone/60.)
|
hours.append(timeinzone/60.)
|
||||||
|
if trainingzones == 'hr':
|
||||||
zones.append(hrzones[2])
|
zones.append(hrzones[2])
|
||||||
|
else:
|
||||||
|
zones.append(powerzones[2])
|
||||||
#print(w,dd,timeinzone,'UT1')
|
#print(w,dd,timeinzone,'UT1')
|
||||||
|
|
||||||
qry = '{at} <= hr < {tr}'.format(at=rower.at,tr=rower.tr)
|
qry = '{at} <= hr < {tr}'.format(at=rower.at,tr=rower.tr)
|
||||||
|
if trainingzones == 'power':
|
||||||
|
qry = '{at} <= power < {tr}'.format(at=rower.pw_at,tr=rower.pw_tr)
|
||||||
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||||
if totaldays<30:
|
if totaldays<=30:
|
||||||
dates.append(dd)
|
dates.append(dd)
|
||||||
dates_sorting.append(dd2)
|
dates_sorting.append(dd2)
|
||||||
|
elif totaldays<=121:
|
||||||
|
dates.append(dd4)
|
||||||
|
dates_sorting.append(dd4)
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
dates.append(dd3)
|
dates.append(dd3)
|
||||||
dates_sorting.append(dd3)
|
dates_sorting.append(dd3)
|
||||||
minutes.append(timeinzone)
|
minutes.append(timeinzone)
|
||||||
hours.append(timeinzone/60.)
|
hours.append(timeinzone/60.)
|
||||||
|
if trainingzones == 'hr':
|
||||||
zones.append(hrzones[3])
|
zones.append(hrzones[3])
|
||||||
|
else:
|
||||||
|
zones.append(powerzones[3])
|
||||||
#print(w,dd,timeinzone,'AT')
|
#print(w,dd,timeinzone,'AT')
|
||||||
|
|
||||||
qry = '{tr} <= hr < {an}'.format(tr=rower.tr,an=rower.an)
|
qry = '{tr} <= hr < {an}'.format(tr=rower.tr,an=rower.an)
|
||||||
|
if trainingzones == 'power':
|
||||||
|
qry = '{tr} <= power < {an}'.format(tr=rower.pw_tr,an=rower.pw_an)
|
||||||
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||||
if totaldays<30:
|
if totaldays<=30:
|
||||||
dates.append(dd)
|
dates.append(dd)
|
||||||
dates_sorting.append(dd2)
|
dates_sorting.append(dd2)
|
||||||
|
elif totaldays<=121:
|
||||||
|
dates.append(dd4)
|
||||||
|
dates_sorting.append(dd4)
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
dates.append(dd3)
|
dates.append(dd3)
|
||||||
dates_sorting.append(dd3)
|
dates_sorting.append(dd3)
|
||||||
minutes.append(timeinzone)
|
minutes.append(timeinzone)
|
||||||
hours.append(timeinzone/60.)
|
hours.append(timeinzone/60.)
|
||||||
|
if trainingzones == 'hr':
|
||||||
zones.append(hrzones[4])
|
zones.append(hrzones[4])
|
||||||
|
else:
|
||||||
|
zones.append(powerzones[4])
|
||||||
#print(w,dd,timeinzone,'TR')
|
#print(w,dd,timeinzone,'TR')
|
||||||
|
|
||||||
qry = 'hr >= {an}'.format(an=rower.an)
|
qry = 'hr >= {an}'.format(an=rower.an)
|
||||||
|
if trainingzones == 'power':
|
||||||
|
qry = 'power >= {an}'.format(an=rower.pw_an)
|
||||||
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||||
if totaldays<30:
|
if totaldays<=30:
|
||||||
dates.append(dd)
|
dates.append(dd)
|
||||||
dates_sorting.append(dd2)
|
dates_sorting.append(dd2)
|
||||||
|
elif totaldays<=121:
|
||||||
|
dates.append(dd4)
|
||||||
|
dates_sorting.append(dd4)
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
dates.append(dd3)
|
dates.append(dd3)
|
||||||
dates_sorting.append(dd3)
|
dates_sorting.append(dd3)
|
||||||
minutes.append(timeinzone)
|
minutes.append(timeinzone)
|
||||||
hours.append(timeinzone/60.)
|
hours.append(timeinzone/60.)
|
||||||
|
if trainingzones == 'hr':
|
||||||
zones.append(hrzones[5])
|
zones.append(hrzones[5])
|
||||||
|
else:
|
||||||
|
zones.append(powerzones[5])
|
||||||
#print(w,dd,timeinzone,'AN')
|
#print(w,dd,timeinzone,'AN')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -6690,19 +6742,28 @@ def get_zones_report(rower,startdate,enddate):
|
|||||||
|
|
||||||
while d<=enddate:
|
while d<=enddate:
|
||||||
dd = d.strftime('%d')
|
dd = d.strftime('%d')
|
||||||
if totaldays < 30:
|
if totaldays <= 30:
|
||||||
dates.append(d.strftime('%m/%d'))
|
dates.append(d.strftime('%m/%d'))
|
||||||
dates_sorting.append(d.strftime('%Y/%m/%d'))
|
dates_sorting.append(d.strftime('%Y/%m/%d'))
|
||||||
|
elif totaldays<=121:
|
||||||
|
dd4 = arrow.get(d).isocalendar()[1]
|
||||||
|
dates.append(dd4)
|
||||||
|
dates_sorting.append(dd4)
|
||||||
else:
|
else:
|
||||||
dates.append(d.strftime('%Y/%m'))
|
dates.append(d.strftime('%Y/%m'))
|
||||||
dates_sorting.append(d.strftime('%Y/%m'))
|
dates_sorting.append(d.strftime('%Y/%m'))
|
||||||
|
|
||||||
minutes.append(0)
|
minutes.append(0)
|
||||||
hours.append(0)
|
hours.append(0)
|
||||||
|
if trainingzones == 'hr':
|
||||||
zones.append(hrzones[1])
|
zones.append(hrzones[1])
|
||||||
|
else:
|
||||||
|
zones.append(powerzones[1])
|
||||||
|
|
||||||
d += datetime.timedelta(days=1)
|
d += datetime.timedelta(days=1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# this should be renamed with rower zones
|
# this should be renamed with rower zones
|
||||||
data = {
|
data = {
|
||||||
'date':dates,
|
'date':dates,
|
||||||
@@ -6716,7 +6777,7 @@ def get_zones_report(rower,startdate,enddate):
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def interactive_zoneschart(rower,data,startdate,enddate):
|
def interactive_zoneschart(rower,data,startdate,enddate,trainingzones='hr'):
|
||||||
duration = enddate-startdate
|
duration = enddate-startdate
|
||||||
|
|
||||||
totaldays = duration.total_seconds()/(24*3600)
|
totaldays = duration.total_seconds()/(24*3600)
|
||||||
@@ -6724,6 +6785,7 @@ def interactive_zoneschart(rower,data,startdate,enddate):
|
|||||||
colors = ['gray','yellow','lime','blue','purple','red']
|
colors = ['gray','yellow','lime','blue','purple','red']
|
||||||
|
|
||||||
hrzones = rower.hrzones
|
hrzones = rower.hrzones
|
||||||
|
powerzones = rower.powerzones
|
||||||
|
|
||||||
|
|
||||||
color_map = {
|
color_map = {
|
||||||
@@ -6734,6 +6796,15 @@ def interactive_zoneschart(rower,data,startdate,enddate):
|
|||||||
hrzones[4]:'purple',
|
hrzones[4]:'purple',
|
||||||
hrzones[5]:'red',
|
hrzones[5]:'red',
|
||||||
}
|
}
|
||||||
|
if trainingzones == 'power':
|
||||||
|
color_map = {
|
||||||
|
'<{ut2}'.format(ut2=powerzones[1]):'gray',
|
||||||
|
powerzones[1]:'lime',
|
||||||
|
powerzones[2]:'yellow',
|
||||||
|
powerzones[3]:'blue',
|
||||||
|
powerzones[4]:'purple',
|
||||||
|
powerzones[5]:'red',
|
||||||
|
}
|
||||||
|
|
||||||
zones_order = [
|
zones_order = [
|
||||||
'<{ut2}'.format(ut2=hrzones[1]),
|
'<{ut2}'.format(ut2=hrzones[1]),
|
||||||
@@ -6744,8 +6815,19 @@ def interactive_zoneschart(rower,data,startdate,enddate):
|
|||||||
hrzones[5]
|
hrzones[5]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if trainingzones == 'power':
|
||||||
|
zones_order = [
|
||||||
|
'<{ut2}'.format(ut2=powerzones[1]),
|
||||||
|
powerzones[1],
|
||||||
|
powerzones[2],
|
||||||
|
powerzones[3],
|
||||||
|
powerzones[4],
|
||||||
|
powerzones[5]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
df = pd.DataFrame(data)
|
df = pd.DataFrame(data)
|
||||||
if totaldays >= 30:
|
if totaldays > 30:
|
||||||
df.drop('minutes',inplace=True,axis='columns')
|
df.drop('minutes',inplace=True,axis='columns')
|
||||||
else:
|
else:
|
||||||
df.drop('hours',inplace=True,axis='columns')
|
df.drop('hours',inplace=True,axis='columns')
|
||||||
@@ -6754,6 +6836,7 @@ def interactive_zoneschart(rower,data,startdate,enddate):
|
|||||||
source = ColumnDataSource(df)
|
source = ColumnDataSource(df)
|
||||||
|
|
||||||
df.sort_values('date_sorting',inplace=True)
|
df.sort_values('date_sorting',inplace=True)
|
||||||
|
df.drop('date_sorting',inplace=True,axis='columns')
|
||||||
|
|
||||||
hv.extension('bokeh')
|
hv.extension('bokeh')
|
||||||
|
|
||||||
@@ -6763,18 +6846,24 @@ def interactive_zoneschart(rower,data,startdate,enddate):
|
|||||||
#bars = table.to.bars(['date','zones'],['minutes'])
|
#bars = table.to.bars(['date','zones'],['minutes'])
|
||||||
bars.opts(
|
bars.opts(
|
||||||
opts.Bars(cmap=color_map,show_legend=True,stacked=True,
|
opts.Bars(cmap=color_map,show_legend=True,stacked=True,
|
||||||
tools=['tap','hover'],width=550,xrotation=45,padding=(0,(0,.1)),
|
tools=['tap','hover'],width=550,padding=(0,(0,.1)),
|
||||||
legend_position='bottom',
|
legend_position='bottom',
|
||||||
show_frame=False)
|
show_frame=False)
|
||||||
)
|
)
|
||||||
|
|
||||||
p = hv.render(bars)
|
p = hv.render(bars)
|
||||||
|
|
||||||
p.title.text = 'Activity {d1} to {d2}'.format(
|
p.title.text = 'Activity {d1} to {d2} for {r}'.format(
|
||||||
d1 = startdate.strftime("%Y-%m-%d"),
|
d1 = startdate.strftime("%Y-%m-%d"),
|
||||||
d2 = enddate.strftime("%Y-%m-%d"),
|
d2 = enddate.strftime("%Y-%m-%d"),
|
||||||
|
r = str(rower),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if totaldays >= 30:
|
||||||
|
p.xaxis.axis_label = 'Week'
|
||||||
|
if totaldays >= 121:
|
||||||
|
p.xaxis.axis_label = 'Month'
|
||||||
|
|
||||||
p.plot_width=550
|
p.plot_width=550
|
||||||
p.plot_height=350
|
p.plot_height=350
|
||||||
p.toolbar_location = 'above'
|
p.toolbar_location = 'above'
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<table>
|
<table>
|
||||||
{{ form.as_table }}
|
{{ form.as_table }}
|
||||||
</table>
|
</table>
|
||||||
<input class="button" type="submit" value="Select Dates">
|
<input class="button" type="submit" value="Submit">
|
||||||
</form>
|
</form>
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1065,18 +1065,19 @@ def trainingzones_view(request,userid=0,mode='rower',
|
|||||||
enddate = timezone.now()
|
enddate = timezone.now()
|
||||||
startdate = enddate-datetime.timedelta(days=365)
|
startdate = enddate-datetime.timedelta(days=365)
|
||||||
|
|
||||||
form = DateRangeForm()
|
form = TrainingZonesForm()
|
||||||
|
zones = 'hr'
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = DateRangeForm(request.POST)
|
form = TrainingZonesForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
startdate = form.cleaned_data['startdate']
|
startdate = form.cleaned_data['startdate']
|
||||||
enddate = form.cleaned_data['enddate']
|
enddate = form.cleaned_data['enddate']
|
||||||
|
zones = form.cleaned_data['zones']
|
||||||
|
|
||||||
|
data = get_zones_report(r,startdate,enddate,trainingzones=zones)
|
||||||
|
|
||||||
|
|
||||||
data = get_zones_report(r,startdate,enddate)
|
script, div = interactive_zoneschart(r,data,startdate,enddate,trainingzones=zones)
|
||||||
|
|
||||||
|
|
||||||
script, div = interactive_zoneschart(r,data,startdate,enddate)
|
|
||||||
|
|
||||||
breadcrumbs = [
|
breadcrumbs = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ from rowers.forms import (
|
|||||||
VideoAnalysisCreateForm,WorkoutSingleSelectForm,
|
VideoAnalysisCreateForm,WorkoutSingleSelectForm,
|
||||||
VideoAnalysisMetricsForm,SurveyForm,HistorySelectForm,
|
VideoAnalysisMetricsForm,SurveyForm,HistorySelectForm,
|
||||||
StravaChartForm,FitnessFitForm,PerformanceManagerForm,
|
StravaChartForm,FitnessFitForm,PerformanceManagerForm,
|
||||||
TrainingPlanBillingForm,InstantPlanSelectForm
|
TrainingPlanBillingForm,InstantPlanSelectForm,
|
||||||
|
TrainingZonesForm,
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
|
|||||||
Reference in New Issue
Block a user