Merge branch 'develop' of https://bitbucket.org/sanderroosendaal/rowsandall into develop
This commit is contained in:
+132
-3
@@ -10,6 +10,7 @@ from __future__ import unicode_literals, absolute_import
|
|||||||
from rowers.models import Workout, Team
|
from rowers.models import Workout, Team
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
import collections
|
||||||
|
|
||||||
from rowingdata import rowingdata as rrdata
|
from rowingdata import rowingdata as rrdata
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ from rowingdata import (
|
|||||||
from rowingdata.csvparsers import HumonParser
|
from rowingdata.csvparsers import HumonParser
|
||||||
|
|
||||||
|
|
||||||
from rowers.metrics import axes,calc_trimp,rowingmetrics,dtypes
|
from rowers.metrics import axes,calc_trimp,rowingmetrics,dtypes,metricsgroups
|
||||||
from rowers.models import strokedatafields
|
from rowers.models import strokedatafields
|
||||||
|
|
||||||
#allowedcolumns = [item[0] for item in rowingmetrics]
|
#allowedcolumns = [item[0] for item in rowingmetrics]
|
||||||
@@ -122,6 +123,100 @@ from scipy.signal import savgol_filter
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def get_video_data(w,groups=['basic'],mode='water'):
|
||||||
|
modes = [mode,'both','basic']
|
||||||
|
columns = ['time','velo','spm']
|
||||||
|
columns += [name for name,d in rowingmetrics if d['group'] in groups and d['mode'] in modes]
|
||||||
|
columns = list(set(columns))
|
||||||
|
df = getsmallrowdata_db(columns,ids=[w.id],
|
||||||
|
workstrokesonly=False,doclean=False,compute=False)
|
||||||
|
df['time'] = (df['time']-df['time'].min())/1000.
|
||||||
|
df.sort_values(by='time',inplace=True)
|
||||||
|
|
||||||
|
|
||||||
|
df.set_index(pd.to_timedelta(df['time'],unit='s'),inplace=True)
|
||||||
|
df2 = df.resample('1s').first().fillna(method='ffill')
|
||||||
|
if 'pace' in columns:
|
||||||
|
df2['pace'] = df2['pace']/1000.
|
||||||
|
p = df2['pace']
|
||||||
|
p = p.apply(lambda x:timedeltaconv(x))
|
||||||
|
p = nicepaceformat(p)
|
||||||
|
df2['pace'] = p
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#mask = df2['time'] < delay
|
||||||
|
#df2 = df2.mask(mask).dropna()
|
||||||
|
df2['time'] = (df2['time']-df2['time'].min())
|
||||||
|
|
||||||
|
df2 = df2.round(decimals=2)
|
||||||
|
|
||||||
|
boatspeed = (100*df2['velo']).astype(int)/100.
|
||||||
|
|
||||||
|
try:
|
||||||
|
coordinates = get_latlon_time(w.id)
|
||||||
|
except KeyError:
|
||||||
|
nulseries = df['time']*0
|
||||||
|
coordinates = pd.DataFrame({
|
||||||
|
'time': df['time'],
|
||||||
|
'latitude': nulseries,
|
||||||
|
'longitude': nulseries,
|
||||||
|
})
|
||||||
|
|
||||||
|
coordinates.set_index(pd.to_timedelta(coordinates['time'],unit='s'),inplace=True)
|
||||||
|
coordinates = coordinates.resample('1s').mean().interpolate()
|
||||||
|
#mask = coordinates['time'] < delay
|
||||||
|
#coordinates = coordinates.mask(mask).dropna()
|
||||||
|
coordinates['time'] = coordinates['time']-coordinates['time'].min()
|
||||||
|
latitude = coordinates['latitude']
|
||||||
|
longitude = coordinates['longitude']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# bundle data
|
||||||
|
data = {
|
||||||
|
'boatspeed':boatspeed.values.tolist(),
|
||||||
|
'latitude':latitude.values.tolist(),
|
||||||
|
'longitude':longitude.values.tolist(),
|
||||||
|
}
|
||||||
|
|
||||||
|
# metrics = {
|
||||||
|
# 'boatspeed': {
|
||||||
|
# 'unit': 'm/s',
|
||||||
|
# 'metric': 'boatspeed',
|
||||||
|
# 'name': 'Boat Speed'
|
||||||
|
# },
|
||||||
|
# }
|
||||||
|
|
||||||
|
metrics = {}
|
||||||
|
|
||||||
|
for c in columns:
|
||||||
|
if c != 'time':
|
||||||
|
try:
|
||||||
|
if dict(rowingmetrics)[c]['numtype'] == 'integer':
|
||||||
|
data[c] = df2[c].astype(int).tolist()
|
||||||
|
else:
|
||||||
|
data[c] = df2[c].values.tolist()
|
||||||
|
metrics[c] = {
|
||||||
|
'name': dict(rowingmetrics)[c]['verbose_name'],
|
||||||
|
'metric': c,
|
||||||
|
'unit': ''
|
||||||
|
}
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
metrics['boatspeed'] = metrics.pop('velo')
|
||||||
|
# metrics['workperstroke'] = metrics.pop('driveenergy')
|
||||||
|
metrics = collections.OrderedDict(sorted(metrics.items()))
|
||||||
|
|
||||||
|
maxtime = coordinates['time'].max()
|
||||||
|
|
||||||
|
|
||||||
|
return data, metrics, maxtime
|
||||||
|
|
||||||
|
|
||||||
def polarization_index(df,rower):
|
def polarization_index(df,rower):
|
||||||
df['dt'] = df['time'].diff()/6.e4
|
df['dt'] = df['time'].diff()/6.e4
|
||||||
# remove rest (spm<15)
|
# remove rest (spm<15)
|
||||||
@@ -172,6 +267,36 @@ def get_latlon(id):
|
|||||||
|
|
||||||
return [pd.Series([]), pd.Series([])]
|
return [pd.Series([]), pd.Series([])]
|
||||||
|
|
||||||
|
def get_latlon_time(id):
|
||||||
|
try:
|
||||||
|
w = Workout.objects.get(id=id)
|
||||||
|
except Workout.DoesNotExist:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
rowdata = rdata(w.csvfilename)
|
||||||
|
|
||||||
|
if rowdata.df.empty:
|
||||||
|
return [pd.Series([]), pd.Series([])]
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
latitude = rowdata.df.loc[:, ' latitude']
|
||||||
|
longitude = rowdata.df.loc[:, ' longitude']
|
||||||
|
except KeyError:
|
||||||
|
latitude = 0 * rowdata.df.loc[:, 'TimeStamp (sec)']
|
||||||
|
longitude = 0 * rowdata.df.loc[:, 'TimeStamp (sec)']
|
||||||
|
except AttributeError:
|
||||||
|
return pd.DataFrame()
|
||||||
|
|
||||||
|
df = pd.DataFrame({
|
||||||
|
'time': rowdata.df[' ElapsedTime (sec)'],
|
||||||
|
'latitude': rowdata.df[' latitude'],
|
||||||
|
'longitude': rowdata.df[' longitude']
|
||||||
|
})
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
def workout_summary_to_df(
|
def workout_summary_to_df(
|
||||||
rower,
|
rower,
|
||||||
startdate=datetime.datetime(1970,1,1),
|
startdate=datetime.datetime(1970,1,1),
|
||||||
@@ -1693,8 +1818,12 @@ def getrowdata_db(id=0, doclean=False, convertnewtons=True,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
if not data.empty and data['efficiency'].mean() == 0 and data['power'].mean() != 0 and checkefficiency == True:
|
if checkefficiency==True and not data.empty:
|
||||||
data = add_efficiency(id=id)
|
try:
|
||||||
|
if data['efficiency'].mean() == 0 and data['power'].mean() != 0:
|
||||||
|
data = add_efficiency(id=id)
|
||||||
|
except KeyError:
|
||||||
|
data = add_efficiency(id=id)
|
||||||
|
|
||||||
if doclean:
|
if doclean:
|
||||||
data = clean_df_stats(data, ignorehr=True)
|
data = clean_df_stats(data, ignorehr=True)
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ from rowingdata import empower_bug_correction,get_empower_rigging
|
|||||||
from time import strftime
|
from time import strftime
|
||||||
from pandas import DataFrame,Series
|
from pandas import DataFrame,Series
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
from shutil import copyfile
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import itertools
|
import itertools
|
||||||
|
|||||||
+123
-82
@@ -24,7 +24,7 @@ import rowers.mytypes as mytypes
|
|||||||
import datetime
|
import datetime
|
||||||
from django.forms import formset_factory
|
from django.forms import formset_factory
|
||||||
from rowers.utils import landingpages
|
from rowers.utils import landingpages
|
||||||
from rowers.metrics import axes
|
from rowers.metrics import axes, metricsgroups,rowingmetrics
|
||||||
from rowers.metrics import axlabels
|
from rowers.metrics import axlabels
|
||||||
|
|
||||||
formaxlabels = axlabels.copy()
|
formaxlabels = axlabels.copy()
|
||||||
@@ -51,6 +51,40 @@ 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)
|
||||||
|
|
||||||
|
# Video Analysis creation form
|
||||||
|
class VideoAnalysisCreateForm(forms.Form):
|
||||||
|
name = forms.CharField(max_length=255,label='Analysis Name',required=False)
|
||||||
|
url = forms.CharField(max_length=255,required=True,label='YouTube Video URL')
|
||||||
|
delay = forms.IntegerField(initial=0,label='Delay (seconds)')
|
||||||
|
|
||||||
|
def get_metricschoices(mode='water'):
|
||||||
|
modes = [mode,'both','basic']
|
||||||
|
metricsdescriptions = {}
|
||||||
|
for m in metricsgroups:
|
||||||
|
metricsdescriptions[m] = m+' ('
|
||||||
|
for name,d in rowingmetrics:
|
||||||
|
if d['group']==m and d['mode'] in modes:
|
||||||
|
metricsdescriptions[m]+=d['verbose_name']+', '
|
||||||
|
metricsdescriptions[m]=metricsdescriptions[m][0:-2]+')'
|
||||||
|
|
||||||
|
metricsgroupschoices = ((m,metricsdescriptions[m]) for m in metricsgroups)
|
||||||
|
|
||||||
|
return metricsgroupschoices
|
||||||
|
|
||||||
|
class VideoAnalysisMetricsForm(forms.Form):
|
||||||
|
groups = forms.MultipleChoiceField(label='Metrics Groups',
|
||||||
|
choices=get_metricschoices(mode='water'),
|
||||||
|
widget=forms.CheckboxSelectMultiple,)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
mode = 'water'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
mode = kwargs.pop('mode','water')
|
||||||
|
super(VideoAnalysisMetricsForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields['groups'].choices = get_metricschoices(mode=mode)
|
||||||
|
|
||||||
|
|
||||||
# BillingForm form
|
# BillingForm form
|
||||||
class BillingForm(forms.Form):
|
class BillingForm(forms.Form):
|
||||||
amount = FlexibleDecimalField(required=True,decimal_places=2,
|
amount = FlexibleDecimalField(required=True,decimal_places=2,
|
||||||
@@ -59,7 +93,7 @@ class BillingForm(forms.Form):
|
|||||||
payment_method_nonce = forms.CharField(max_length=255,required=True)
|
payment_method_nonce = forms.CharField(max_length=255,required=True)
|
||||||
paymenttype = forms.CharField(max_length=255,required=True)
|
paymenttype = forms.CharField(max_length=255,required=True)
|
||||||
tac= forms.BooleanField(required=True,initial=False)
|
tac= forms.BooleanField(required=True,initial=False)
|
||||||
|
|
||||||
|
|
||||||
# login form
|
# login form
|
||||||
class LoginForm(forms.Form):
|
class LoginForm(forms.Form):
|
||||||
@@ -73,8 +107,8 @@ class SearchForm(forms.Form):
|
|||||||
attrs={'placeholder': 'keyword or leave empty'}),
|
attrs={'placeholder': 'keyword or leave empty'}),
|
||||||
label='Filter by Keyword')
|
label='Filter by Keyword')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# simple form for Contact page. Sends email to info@rowsandall.com
|
# simple form for Contact page. Sends email to info@rowsandall.com
|
||||||
class EmailForm(forms.Form):
|
class EmailForm(forms.Form):
|
||||||
firstname = forms.CharField(max_length=255)
|
firstname = forms.CharField(max_length=255)
|
||||||
@@ -107,7 +141,7 @@ class MetricsForm(forms.Form):
|
|||||||
avghr = forms.IntegerField(required=False,label='Average Heart Rate')
|
avghr = forms.IntegerField(required=False,label='Average Heart Rate')
|
||||||
avgpwr = forms.IntegerField(required=False,label='Average Power')
|
avgpwr = forms.IntegerField(required=False,label='Average Power')
|
||||||
avgspm = forms.FloatField(required=False,label='Average SPM')
|
avgspm = forms.FloatField(required=False,label='Average SPM')
|
||||||
|
|
||||||
# Upload the CrewNerd Summary CSV
|
# Upload the CrewNerd Summary CSV
|
||||||
class CNsummaryForm(forms.Form):
|
class CNsummaryForm(forms.Form):
|
||||||
file = forms.FileField(required=True,validators=[must_be_csv])
|
file = forms.FileField(required=True,validators=[must_be_csv])
|
||||||
@@ -120,7 +154,7 @@ class SummaryStringForm(forms.Form):
|
|||||||
class TeamInviteCodeForm(forms.Form):
|
class TeamInviteCodeForm(forms.Form):
|
||||||
code = forms.CharField(max_length=10,label='Team Code',
|
code = forms.CharField(max_length=10,label='Team Code',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Used for testing the POST API for StrokeData
|
# Used for testing the POST API for StrokeData
|
||||||
class StrokeDataForm(forms.Form):
|
class StrokeDataForm(forms.Form):
|
||||||
strokedata = forms.CharField(label='payload',widget=forms.Textarea)
|
strokedata = forms.CharField(label='payload',widget=forms.Textarea)
|
||||||
@@ -133,7 +167,7 @@ class ImageForm(forms.Form):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
from django.forms.widgets import HiddenInput
|
from django.forms.widgets import HiddenInput
|
||||||
super(ImageForm, self).__init__(*args, **kwargs)
|
super(ImageForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# The form used for uploading images
|
# The form used for uploading images
|
||||||
class CourseForm(forms.Form):
|
class CourseForm(forms.Form):
|
||||||
@@ -147,9 +181,9 @@ class CourseForm(forms.Form):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
from django.forms.widgets import HiddenInput
|
from django.forms.widgets import HiddenInput
|
||||||
super(CourseForm, self).__init__(*args, **kwargs)
|
super(CourseForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# The form used for uploading files
|
# The form used for uploading files
|
||||||
class DocumentsForm(forms.Form):
|
class DocumentsForm(forms.Form):
|
||||||
title = forms.CharField(required=False)
|
title = forms.CharField(required=False)
|
||||||
file = forms.FileField(required=False,
|
file = forms.FileField(required=False,
|
||||||
@@ -157,7 +191,7 @@ class DocumentsForm(forms.Form):
|
|||||||
|
|
||||||
workouttype = forms.ChoiceField(required=True,
|
workouttype = forms.ChoiceField(required=True,
|
||||||
choices=Workout.workouttypes)
|
choices=Workout.workouttypes)
|
||||||
|
|
||||||
boattype = forms.ChoiceField(required=True,
|
boattype = forms.ChoiceField(required=True,
|
||||||
choices=mytypes.boattypes,
|
choices=mytypes.boattypes,
|
||||||
label = "Boat Type")
|
label = "Boat Type")
|
||||||
@@ -181,7 +215,7 @@ from rowers.utils import (
|
|||||||
defaultleft,defaultmiddle
|
defaultleft,defaultmiddle
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Form to change Workflow page layout
|
# Form to change Workflow page layout
|
||||||
class WorkFlowLeftPanelForm(forms.Form):
|
class WorkFlowLeftPanelForm(forms.Form):
|
||||||
@@ -212,14 +246,14 @@ class WorkFlowLeftPanelForm(forms.Form):
|
|||||||
self.base_fields['leftpanel'].initial=panels
|
self.base_fields['leftpanel'].initial=panels
|
||||||
else:
|
else:
|
||||||
panels = defaultleft
|
panels = defaultleft
|
||||||
|
|
||||||
|
|
||||||
super(WorkFlowLeftPanelForm,self).__init__(*args, **kwargs)
|
super(WorkFlowLeftPanelForm,self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class WorkFlowMiddlePanelForm(forms.Form):
|
class WorkFlowMiddlePanelForm(forms.Form):
|
||||||
panels = defaultmiddle
|
panels = defaultmiddle
|
||||||
|
|
||||||
|
|
||||||
middlepanel = forms.MultipleChoiceField(
|
middlepanel = forms.MultipleChoiceField(
|
||||||
label='',
|
label='',
|
||||||
@@ -237,7 +271,7 @@ class WorkFlowMiddlePanelForm(forms.Form):
|
|||||||
# 'css/uid-manage-form.css'],
|
# 'css/uid-manage-form.css'],
|
||||||
}
|
}
|
||||||
js = ['/admin/jsi18n/']
|
js = ['/admin/jsi18n/']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
if 'instance' in kwargs:
|
if 'instance' in kwargs:
|
||||||
r = kwargs.pop('instance')
|
r = kwargs.pop('instance')
|
||||||
@@ -245,7 +279,7 @@ class WorkFlowMiddlePanelForm(forms.Form):
|
|||||||
self.base_fields['middlepanel'].initial=panels
|
self.base_fields['middlepanel'].initial=panels
|
||||||
else:
|
else:
|
||||||
panels = defaultmiddle
|
panels = defaultmiddle
|
||||||
|
|
||||||
super(WorkFlowMiddlePanelForm,self).__init__(*args, **kwargs)
|
super(WorkFlowMiddlePanelForm,self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
class WorkFlowLeftPanelElement(forms.Form):
|
class WorkFlowLeftPanelElement(forms.Form):
|
||||||
@@ -265,8 +299,8 @@ class WorkFlowMiddlePanelElement(forms.Form):
|
|||||||
choices=panelchoices,
|
choices=panelchoices,
|
||||||
initial='None',
|
initial='None',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# The form to indicate additional actions to be performed immediately
|
# The form to indicate additional actions to be performed immediately
|
||||||
# after a successful upload
|
# after a successful upload
|
||||||
|
|
||||||
@@ -359,7 +393,7 @@ class TeamUploadOptionsForm(forms.Form):
|
|||||||
class Meta:
|
class Meta:
|
||||||
fields = ['make_plot','plottype']
|
fields = ['make_plot','plottype']
|
||||||
|
|
||||||
|
|
||||||
# This form is used on the Workout Split page
|
# This form is used on the Workout Split page
|
||||||
class WorkoutSplitForm(forms.Form):
|
class WorkoutSplitForm(forms.Form):
|
||||||
splitchoices = (
|
splitchoices = (
|
||||||
@@ -386,7 +420,7 @@ class WorkoutSplitForm(forms.Form):
|
|||||||
label='Split Mode',
|
label='Split Mode',
|
||||||
choices=splitchoices,
|
choices=splitchoices,
|
||||||
widget = forms.CheckboxSelectMultiple())
|
widget = forms.CheckboxSelectMultiple())
|
||||||
|
|
||||||
# This form is used on the Analysis page to add a custom distance/time
|
# This form is used on the Analysis page to add a custom distance/time
|
||||||
# trial and predict the pace
|
# trial and predict the pace
|
||||||
from rowers.utils import rankingdistances,rankingdurations
|
from rowers.utils import rankingdistances,rankingdurations
|
||||||
@@ -427,14 +461,14 @@ class PredictedPieceForm(forms.Form):
|
|||||||
choices=rankingdistancechoices,initial=rankingdistances,
|
choices=rankingdistancechoices,initial=rankingdistances,
|
||||||
label='Ranking Distances'
|
label='Ranking Distances'
|
||||||
)
|
)
|
||||||
|
|
||||||
trankingdurations = forms.MultipleChoiceField(
|
trankingdurations = forms.MultipleChoiceField(
|
||||||
required=True,
|
required=True,
|
||||||
choices=rankingdurationchoices,
|
choices=rankingdurationchoices,
|
||||||
initial=[a for a,b in rankingdurationchoices],
|
initial=[a for a,b in rankingdurationchoices],
|
||||||
label='Ranking Durations'
|
label='Ranking Durations'
|
||||||
)
|
)
|
||||||
|
|
||||||
value = forms.FloatField(initial=10,label='Free ranking piece (minutes)')
|
value = forms.FloatField(initial=10,label='Free ranking piece (minutes)')
|
||||||
pieceunit = forms.ChoiceField(required=True,choices=unitchoices,
|
pieceunit = forms.ChoiceField(required=True,choices=unitchoices,
|
||||||
initial='t',label='Unit')
|
initial='t',label='Unit')
|
||||||
@@ -451,17 +485,17 @@ class PredictedPieceFormNoDistance(forms.Form):
|
|||||||
thetuple = (timestr,timestr)
|
thetuple = (timestr,timestr)
|
||||||
rankingdurationchoices.append(thetuple)
|
rankingdurationchoices.append(thetuple)
|
||||||
|
|
||||||
|
|
||||||
trankingdurations = forms.MultipleChoiceField(
|
trankingdurations = forms.MultipleChoiceField(
|
||||||
required=True,
|
required=True,
|
||||||
choices=rankingdurationchoices,
|
choices=rankingdurationchoices,
|
||||||
initial=[a for a,b in rankingdurationchoices],
|
initial=[a for a,b in rankingdurationchoices],
|
||||||
label='Ranking Durations'
|
label='Ranking Durations'
|
||||||
)
|
)
|
||||||
|
|
||||||
value = forms.FloatField(initial=10,label='Free ranking piece')
|
value = forms.FloatField(initial=10,label='Free ranking piece')
|
||||||
|
|
||||||
|
|
||||||
# On the Geeky side, to update stream information for river dwellers
|
# On the Geeky side, to update stream information for river dwellers
|
||||||
class UpdateStreamForm(forms.Form):
|
class UpdateStreamForm(forms.Form):
|
||||||
unitchoices = (
|
unitchoices = (
|
||||||
@@ -545,16 +579,16 @@ class FitnessMetricForm(forms.Form):
|
|||||||
('rower','indoor rower'),
|
('rower','indoor rower'),
|
||||||
('water','on the water')
|
('water','on the water')
|
||||||
)
|
)
|
||||||
|
|
||||||
mode = forms.ChoiceField(required=True,
|
mode = forms.ChoiceField(required=True,
|
||||||
choices=modechoices,
|
choices=modechoices,
|
||||||
initial='rower',
|
initial='rower',
|
||||||
label='Workout Mode'
|
label='Workout Mode'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ['startdate','enddate','mode']
|
fields = ['startdate','enddate','mode']
|
||||||
|
|
||||||
class SessionDateShiftForm(forms.Form):
|
class SessionDateShiftForm(forms.Form):
|
||||||
shiftstartdate = forms.DateField(
|
shiftstartdate = forms.DateField(
|
||||||
initial=timezone.now(),
|
initial=timezone.now(),
|
||||||
@@ -563,7 +597,7 @@ class SessionDateShiftForm(forms.Form):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ['shiftstartdate']
|
fields = ['shiftstartdate']
|
||||||
|
|
||||||
# Form used to select workouts for the past N days
|
# Form used to select workouts for the past N days
|
||||||
class DeltaDaysForm(forms.Form):
|
class DeltaDaysForm(forms.Form):
|
||||||
deltadays = forms.IntegerField(initial=7,required=False,label='')
|
deltadays = forms.IntegerField(initial=7,required=False,label='')
|
||||||
@@ -599,7 +633,7 @@ class RegistrationFormTermsOfService(RegistrationForm):
|
|||||||
tos = forms.BooleanField(widget=forms.CheckboxInput,
|
tos = forms.BooleanField(widget=forms.CheckboxInput,
|
||||||
label='I have read and agree to the Terms of Service',
|
label='I have read and agree to the Terms of Service',
|
||||||
error_messages={'required': "You must agree to the terms to register"})
|
error_messages={'required': "You must agree to the terms to register"})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RegistrationFormUniqueEmail(RegistrationFormTermsOfService):
|
class RegistrationFormUniqueEmail(RegistrationFormTermsOfService):
|
||||||
@@ -631,20 +665,20 @@ class RegistrationFormSex(RegistrationFormUniqueEmail):
|
|||||||
adaptivecategories = mytypes.adaptivetypes
|
adaptivecategories = mytypes.adaptivetypes
|
||||||
|
|
||||||
thisyear = timezone.now().year
|
thisyear = timezone.now().year
|
||||||
|
|
||||||
birthdate = forms.DateTimeField(
|
birthdate = forms.DateTimeField(
|
||||||
widget=SelectDateWidget(years=range(1900,thisyear)),
|
widget=SelectDateWidget(years=range(1900,thisyear)),
|
||||||
initial = datetime.date(year=1970,
|
initial = datetime.date(year=1970,
|
||||||
month=4,
|
month=4,
|
||||||
day=15))
|
day=15))
|
||||||
|
|
||||||
def clean_birthdate(self):
|
def clean_birthdate(self):
|
||||||
dob = self.cleaned_data['birthdate']
|
dob = self.cleaned_data['birthdate']
|
||||||
age = (timezone.now() - dob).days/365
|
age = (timezone.now() - dob).days/365
|
||||||
if age < 16:
|
if age < 16:
|
||||||
raise forms.ValidationError('Must be at least 16 years old to register')
|
raise forms.ValidationError('Must be at least 16 years old to register')
|
||||||
return self.cleaned_data['birthdate']
|
return self.cleaned_data['birthdate']
|
||||||
|
|
||||||
sex = forms.ChoiceField(required=False,
|
sex = forms.ChoiceField(required=False,
|
||||||
choices=sexcategories,
|
choices=sexcategories,
|
||||||
initial='not specified',
|
initial='not specified',
|
||||||
@@ -655,10 +689,10 @@ class RegistrationFormSex(RegistrationFormUniqueEmail):
|
|||||||
|
|
||||||
adaptiveclass = forms.ChoiceField(label='Adaptive Classification',
|
adaptiveclass = forms.ChoiceField(label='Adaptive Classification',
|
||||||
choices=adaptivecategories,initial='None',required=False)
|
choices=adaptivecategories,initial='None',required=False)
|
||||||
|
|
||||||
# def __init__(self, *args, **kwargs):
|
# def __init__(self, *args, **kwargs):
|
||||||
# self.fields['sex'].initial = 'not specified'
|
# self.fields['sex'].initial = 'not specified'
|
||||||
|
|
||||||
# Time field supporting microseconds. Not used, I believe.
|
# Time field supporting microseconds. Not used, I believe.
|
||||||
class MyTimeField(forms.TimeField):
|
class MyTimeField(forms.TimeField):
|
||||||
|
|
||||||
@@ -673,7 +707,7 @@ class PowerIntervalUpdateForm(forms.Form):
|
|||||||
('pace','Pace'),
|
('pace','Pace'),
|
||||||
('work','Work per Stroke'),
|
('work','Work per Stroke'),
|
||||||
)
|
)
|
||||||
|
|
||||||
pace = forms.DurationField(required=False,label='Pace (/500m)')
|
pace = forms.DurationField(required=False,label='Pace (/500m)')
|
||||||
power = forms.IntegerField(required=False,label='Power (W)')
|
power = forms.IntegerField(required=False,label='Power (W)')
|
||||||
work = forms.IntegerField(required=False,label='Work per Stroke (J)')
|
work = forms.IntegerField(required=False,label='Work per Stroke (J)')
|
||||||
@@ -681,10 +715,10 @@ class PowerIntervalUpdateForm(forms.Form):
|
|||||||
required=True,
|
required=True,
|
||||||
initial='power',
|
initial='power',
|
||||||
label='Use')
|
label='Use')
|
||||||
|
|
||||||
# Form used to update interval stats
|
# Form used to update interval stats
|
||||||
class IntervalUpdateForm(forms.Form):
|
class IntervalUpdateForm(forms.Form):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
typechoices = (
|
typechoices = (
|
||||||
(1,'single time'),
|
(1,'single time'),
|
||||||
@@ -733,7 +767,7 @@ class HistoForm(forms.Form):
|
|||||||
includereststrokes = forms.BooleanField(initial=False,label='Include Rest Strokes',required=False)
|
includereststrokes = forms.BooleanField(initial=False,label='Include Rest Strokes',required=False)
|
||||||
histoparam = forms.ChoiceField(choices=parchoices,initial='power',
|
histoparam = forms.ChoiceField(choices=parchoices,initial='power',
|
||||||
label='Metric')
|
label='Metric')
|
||||||
|
|
||||||
class AnalysisOptionsForm(forms.Form):
|
class AnalysisOptionsForm(forms.Form):
|
||||||
modality = forms.ChoiceField(choices=workouttypes,
|
modality = forms.ChoiceField(choices=workouttypes,
|
||||||
label='Workout Type',
|
label='Workout Type',
|
||||||
@@ -744,8 +778,8 @@ class AnalysisOptionsForm(forms.Form):
|
|||||||
rankingonly = forms.BooleanField(initial=False,
|
rankingonly = forms.BooleanField(initial=False,
|
||||||
label='Only Ranking Pieces',
|
label='Only Ranking Pieces',
|
||||||
required=False)
|
required=False)
|
||||||
|
|
||||||
|
|
||||||
# form to select modality and boat type for trend flex
|
# form to select modality and boat type for trend flex
|
||||||
class TrendFlexModalForm(forms.Form):
|
class TrendFlexModalForm(forms.Form):
|
||||||
modality = forms.ChoiceField(choices=workouttypes,
|
modality = forms.ChoiceField(choices=workouttypes,
|
||||||
@@ -759,7 +793,7 @@ class TrendFlexModalForm(forms.Form):
|
|||||||
required=False)
|
required=False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# This form sets options for the summary stats page
|
# This form sets options for the summary stats page
|
||||||
class StatsOptionsForm(forms.Form):
|
class StatsOptionsForm(forms.Form):
|
||||||
includereststrokes = forms.BooleanField(initial=False,label='Include Rest Strokes',required=False)
|
includereststrokes = forms.BooleanField(initial=False,label='Include Rest Strokes',required=False)
|
||||||
@@ -772,13 +806,13 @@ class StatsOptionsForm(forms.Form):
|
|||||||
initial = mytypes.waterboattype)
|
initial = mytypes.waterboattype)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(StatsOptionsForm, self).__init__(*args,**kwargs)
|
super(StatsOptionsForm, self).__init__(*args,**kwargs)
|
||||||
|
|
||||||
for type in mytypes.checktypes:
|
for type in mytypes.checktypes:
|
||||||
self.fields[type] = forms.BooleanField(initial=True,required=False)
|
self.fields[type] = forms.BooleanField(initial=True,required=False)
|
||||||
|
|
||||||
class PlanSelectForm(forms.Form):
|
class PlanSelectForm(forms.Form):
|
||||||
plan = forms.ModelChoiceField(queryset=PaidPlan.objects.all(),
|
plan = forms.ModelChoiceField(queryset=PaidPlan.objects.all(),
|
||||||
widget=forms.RadioSelect,required=True)
|
widget=forms.RadioSelect,required=True)
|
||||||
@@ -812,10 +846,21 @@ class PlanSelectForm(forms.Form):
|
|||||||
"price","shortname"
|
"price","shortname"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CourseSelectForm(forms.Form):
|
class CourseSelectForm(forms.Form):
|
||||||
course = forms.ModelChoiceField(queryset=GeoCourse.objects.all())
|
course = forms.ModelChoiceField(queryset=GeoCourse.objects.all())
|
||||||
|
|
||||||
|
class WorkoutSingleSelectForm(forms.Form):
|
||||||
|
workout = forms.ModelChoiceField(
|
||||||
|
queryset=Workout.objects.filter(),
|
||||||
|
widget=forms.RadioSelect
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
workouts = kwargs.pop('workouts',Workout.objects.filter().order_by('-date'))
|
||||||
|
super(WorkoutSingleSelectForm,self).__init__(*args,**kwargs)
|
||||||
|
self.fields['workout'].queryset = workouts
|
||||||
|
|
||||||
class WorkoutMultipleCompareForm(forms.Form):
|
class WorkoutMultipleCompareForm(forms.Form):
|
||||||
workouts = forms.ModelMultipleChoiceField(
|
workouts = forms.ModelMultipleChoiceField(
|
||||||
queryset=Workout.objects.filter(),
|
queryset=Workout.objects.filter(),
|
||||||
@@ -824,8 +869,8 @@ class WorkoutMultipleCompareForm(forms.Form):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(WorkoutMultipleCompareForm,self).__init__(*args,**kwargs)
|
super(WorkoutMultipleCompareForm,self).__init__(*args,**kwargs)
|
||||||
self.fields['workouts'].queryset = Workout.objects.filter()
|
self.fields['workouts'].queryset = Workout.objects.filter()
|
||||||
|
|
||||||
|
|
||||||
class PlannedSessionMultipleCloneForm(forms.Form):
|
class PlannedSessionMultipleCloneForm(forms.Form):
|
||||||
plannedsessions = forms.ModelMultipleChoiceField(
|
plannedsessions = forms.ModelMultipleChoiceField(
|
||||||
queryset=PlannedSession.objects.all(),
|
queryset=PlannedSession.objects.all(),
|
||||||
@@ -867,7 +912,7 @@ class AnalysisChoiceForm(forms.Form):
|
|||||||
)
|
)
|
||||||
axchoices = dict((x,y) for x,y in axchoices)
|
axchoices = dict((x,y) for x,y in axchoices)
|
||||||
axchoices = list(sorted(axchoices.items(), key = lambda x:x[1]))
|
axchoices = list(sorted(axchoices.items(), key = lambda x:x[1]))
|
||||||
|
|
||||||
|
|
||||||
yaxchoices = list((ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time'])
|
yaxchoices = list((ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time'])
|
||||||
yaxchoices = dict((x,y) for x,y in yaxchoices)
|
yaxchoices = dict((x,y) for x,y in yaxchoices)
|
||||||
@@ -877,7 +922,7 @@ class AnalysisChoiceForm(forms.Form):
|
|||||||
('line','Line Plot'),
|
('line','Line Plot'),
|
||||||
('scatter','Scatter Plot'),
|
('scatter','Scatter Plot'),
|
||||||
)
|
)
|
||||||
|
|
||||||
yaxchoices2 = list(
|
yaxchoices2 = list(
|
||||||
(ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time']
|
(ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time']
|
||||||
)
|
)
|
||||||
@@ -914,7 +959,7 @@ class AnalysisChoiceForm(forms.Form):
|
|||||||
palette = forms.ChoiceField(choices=palettechoices,
|
palette = forms.ChoiceField(choices=palettechoices,
|
||||||
label = 'Color Scheme',
|
label = 'Color Scheme',
|
||||||
initial='monochrome_blue')
|
initial='monochrome_blue')
|
||||||
|
|
||||||
spmmin = forms.FloatField(initial=15,
|
spmmin = forms.FloatField(initial=15,
|
||||||
required=False,label = 'Min SPM')
|
required=False,label = 'Min SPM')
|
||||||
spmmax = forms.FloatField(initial=55,
|
spmmax = forms.FloatField(initial=55,
|
||||||
@@ -927,7 +972,7 @@ class AnalysisChoiceForm(forms.Form):
|
|||||||
includereststrokes = forms.BooleanField(initial=False,
|
includereststrokes = forms.BooleanField(initial=False,
|
||||||
required=False,
|
required=False,
|
||||||
label='Include Rest Strokes')
|
label='Include Rest Strokes')
|
||||||
|
|
||||||
class BoxPlotChoiceForm(forms.Form):
|
class BoxPlotChoiceForm(forms.Form):
|
||||||
yparam = forms.ChoiceField(choices=parchoices,initial='spm',
|
yparam = forms.ChoiceField(choices=parchoices,initial='spm',
|
||||||
label='Metric')
|
label='Metric')
|
||||||
@@ -971,7 +1016,7 @@ class MultiFlexChoiceForm(forms.Form):
|
|||||||
palette = forms.ChoiceField(choices=palettechoices,
|
palette = forms.ChoiceField(choices=palettechoices,
|
||||||
label = 'Color Scheme',
|
label = 'Color Scheme',
|
||||||
initial='monochrome_blue')
|
initial='monochrome_blue')
|
||||||
|
|
||||||
class ChartParamChoiceForm(forms.Form):
|
class ChartParamChoiceForm(forms.Form):
|
||||||
plotchoices = (
|
plotchoices = (
|
||||||
('line','Line Plot'),
|
('line','Line Plot'),
|
||||||
@@ -993,7 +1038,7 @@ class FusionMetricChoiceForm(ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Workout
|
model = Workout
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
posneg = (
|
posneg = (
|
||||||
('pos','Workout 2 starts after Workout 1'),
|
('pos','Workout 2 starts after Workout 1'),
|
||||||
('neg','Workout 2 starts before Workout 1'),
|
('neg','Workout 2 starts before Workout 1'),
|
||||||
@@ -1021,14 +1066,14 @@ class FusionMetricChoiceForm(ModelForm):
|
|||||||
formaxlabels2.pop(label)
|
formaxlabels2.pop(label)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
metricchoices = list(sorted(formaxlabels2.items(), key = lambda x:x[1]))
|
metricchoices = list(sorted(formaxlabels2.items(), key = lambda x:x[1]))
|
||||||
self.fields['columns'].choices = metricchoices
|
self.fields['columns'].choices = metricchoices
|
||||||
|
|
||||||
class PlannedSessionSelectForm(forms.Form):
|
class PlannedSessionSelectForm(forms.Form):
|
||||||
|
|
||||||
def __init__(self, sessionchoices, *args, **kwargs):
|
def __init__(self, sessionchoices, *args, **kwargs):
|
||||||
initialsession = kwargs.pop('initialsession',None)
|
initialsession = kwargs.pop('initialsession',None)
|
||||||
super(PlannedSessionSelectForm, self).__init__(*args,**kwargs)
|
super(PlannedSessionSelectForm, self).__init__(*args,**kwargs)
|
||||||
|
|
||||||
self.fields['plannedsession'] = forms.ChoiceField(
|
self.fields['plannedsession'] = forms.ChoiceField(
|
||||||
@@ -1040,7 +1085,7 @@ class PlannedSessionSelectForm(forms.Form):
|
|||||||
|
|
||||||
|
|
||||||
class WorkoutSessionSelectForm(forms.Form):
|
class WorkoutSessionSelectForm(forms.Form):
|
||||||
|
|
||||||
def __init__(self, workoutdata, *args, **kwargs):
|
def __init__(self, workoutdata, *args, **kwargs):
|
||||||
|
|
||||||
super(WorkoutSessionSelectForm, self).__init__(*args, **kwargs)
|
super(WorkoutSessionSelectForm, self).__init__(*args, **kwargs)
|
||||||
@@ -1053,7 +1098,7 @@ class WorkoutSessionSelectForm(forms.Form):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RaceResultFilterForm(forms.Form):
|
class RaceResultFilterForm(forms.Form):
|
||||||
boatclasses = (type for type in mytypes.workouttypes if type[0] in mytypes.rowtypes)
|
boatclasses = (type for type in mytypes.workouttypes if type[0] in mytypes.rowtypes)
|
||||||
boatclassinitial = [t for t in mytypes.rowtypes]
|
boatclassinitial = [t for t in mytypes.rowtypes]
|
||||||
@@ -1069,7 +1114,7 @@ class RaceResultFilterForm(forms.Form):
|
|||||||
)
|
)
|
||||||
|
|
||||||
adaptivecategories = mytypes.adaptivetypes
|
adaptivecategories = mytypes.adaptivetypes
|
||||||
|
|
||||||
sex = forms.MultipleChoiceField(
|
sex = forms.MultipleChoiceField(
|
||||||
choices=sexchoices,
|
choices=sexchoices,
|
||||||
initial=['male','female','mixed'],
|
initial=['male','female','mixed'],
|
||||||
@@ -1081,7 +1126,7 @@ class RaceResultFilterForm(forms.Form):
|
|||||||
initial=boatclassinitial,
|
initial=boatclassinitial,
|
||||||
label='Boat/Erg Class',
|
label='Boat/Erg Class',
|
||||||
widget=forms.CheckboxSelectMultiple())
|
widget=forms.CheckboxSelectMultiple())
|
||||||
|
|
||||||
boattype = forms.MultipleChoiceField(
|
boattype = forms.MultipleChoiceField(
|
||||||
choices=boattypes,
|
choices=boattypes,
|
||||||
label='Boat Type',
|
label='Boat Type',
|
||||||
@@ -1106,7 +1151,7 @@ class RaceResultFilterForm(forms.Form):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
if 'records' in kwargs:
|
if 'records' in kwargs:
|
||||||
records = kwargs.pop('records',None)
|
records = kwargs.pop('records',None)
|
||||||
|
|
||||||
super(RaceResultFilterForm,self).__init__(*args,**kwargs)
|
super(RaceResultFilterForm,self).__init__(*args,**kwargs)
|
||||||
|
|
||||||
if records:
|
if records:
|
||||||
@@ -1135,7 +1180,7 @@ class RaceResultFilterForm(forms.Form):
|
|||||||
if choice[0] in theboatclasses:
|
if choice[0] in theboatclasses:
|
||||||
boatclasschoices.append(choice)
|
boatclasschoices.append(choice)
|
||||||
self.fields['boatclass'].choices = boatclasschoices
|
self.fields['boatclass'].choices = boatclasschoices
|
||||||
|
|
||||||
# boattype
|
# boattype
|
||||||
try:
|
try:
|
||||||
theboattypees = [record.boattype for record in records]
|
theboattypees = [record.boattype for record in records]
|
||||||
@@ -1151,7 +1196,7 @@ class RaceResultFilterForm(forms.Form):
|
|||||||
if choice[0] in theboattypees:
|
if choice[0] in theboattypees:
|
||||||
boattypechoices.append(choice)
|
boattypechoices.append(choice)
|
||||||
self.fields['boattype'].choices = boattypechoices
|
self.fields['boattype'].choices = boattypechoices
|
||||||
|
|
||||||
# weightcategory
|
# weightcategory
|
||||||
theweightcategoryes = [record.weightcategory for record in records]
|
theweightcategoryes = [record.weightcategory for record in records]
|
||||||
theweightcategoryes = list(set(theweightcategoryes))
|
theweightcategoryes = list(set(theweightcategoryes))
|
||||||
@@ -1177,7 +1222,7 @@ class RaceResultFilterForm(forms.Form):
|
|||||||
if choice[0] in theadaptivecategoryes:
|
if choice[0] in theadaptivecategoryes:
|
||||||
adaptivecategorychoices.append(choice)
|
adaptivecategorychoices.append(choice)
|
||||||
self.fields['adaptivecategory'].choices = adaptivecategorychoices
|
self.fields['adaptivecategory'].choices = adaptivecategorychoices
|
||||||
|
|
||||||
class WorkoutRaceSelectForm(forms.Form):
|
class WorkoutRaceSelectForm(forms.Form):
|
||||||
# evaluate_after = forms.TimeField(
|
# evaluate_after = forms.TimeField(
|
||||||
# input_formats=['%H:%M:%S.%f',
|
# input_formats=['%H:%M:%S.%f',
|
||||||
@@ -1188,7 +1233,7 @@ class WorkoutRaceSelectForm(forms.Form):
|
|||||||
# '%M'],
|
# '%M'],
|
||||||
# label = 'Only Evaluate After:',
|
# label = 'Only Evaluate After:',
|
||||||
# required=False)
|
# required=False)
|
||||||
|
|
||||||
def __init__(self, workoutdata,entries, *args, **kwargs):
|
def __init__(self, workoutdata,entries, *args, **kwargs):
|
||||||
|
|
||||||
super(WorkoutRaceSelectForm, self).__init__(*args, **kwargs)
|
super(WorkoutRaceSelectForm, self).__init__(*args, **kwargs)
|
||||||
@@ -1206,7 +1251,7 @@ class WorkoutRaceSelectForm(forms.Form):
|
|||||||
initial = entries['initial'],
|
initial = entries['initial'],
|
||||||
)
|
)
|
||||||
|
|
||||||
# self.fields['evaluate_after'] =
|
# self.fields['evaluate_after'] =
|
||||||
|
|
||||||
# form to send messages to team members
|
# form to send messages to team members
|
||||||
class TeamMessageForm(forms.Form):
|
class TeamMessageForm(forms.Form):
|
||||||
@@ -1256,7 +1301,7 @@ class PlannedSessionTeamMemberForm(forms.Form):
|
|||||||
members = forms.ModelMultipleChoiceField(
|
members = forms.ModelMultipleChoiceField(
|
||||||
queryset=Rower.objects.all(),
|
queryset=Rower.objects.all(),
|
||||||
widget=forms.CheckboxSelectMultiple())
|
widget=forms.CheckboxSelectMultiple())
|
||||||
|
|
||||||
def __init__(self, thesession, *args, **kwargs):
|
def __init__(self, thesession, *args, **kwargs):
|
||||||
super(PlannedSessionTeamMemberForm,self).__init__(*args,**kwargs)
|
super(PlannedSessionTeamMemberForm,self).__init__(*args,**kwargs)
|
||||||
|
|
||||||
@@ -1283,13 +1328,13 @@ class VirtualRaceSelectForm(forms.Form):
|
|||||||
('my','My Races'),
|
('my','My Races'),
|
||||||
('all','All Races'),
|
('all','All Races'),
|
||||||
)
|
)
|
||||||
|
|
||||||
regattatype = forms.ChoiceField(
|
regattatype = forms.ChoiceField(
|
||||||
label='Type',
|
label='Type',
|
||||||
choices = regattatypechoices,
|
choices = regattatypechoices,
|
||||||
initial = 'upcoming',
|
initial = 'upcoming',
|
||||||
)
|
)
|
||||||
|
|
||||||
country = forms.ChoiceField(
|
country = forms.ChoiceField(
|
||||||
label='Country',
|
label='Country',
|
||||||
choices = get_countries()
|
choices = get_countries()
|
||||||
@@ -1311,7 +1356,7 @@ class FlexOptionsForm(forms.Form):
|
|||||||
)
|
)
|
||||||
plottype = forms.ChoiceField(choices=plotchoices,initial='line',
|
plottype = forms.ChoiceField(choices=plotchoices,initial='line',
|
||||||
label='Chart Type')
|
label='Chart Type')
|
||||||
|
|
||||||
class ForceCurveOptionsForm(forms.Form):
|
class ForceCurveOptionsForm(forms.Form):
|
||||||
includereststrokes = forms.BooleanField(initial=False, required = False,
|
includereststrokes = forms.BooleanField(initial=False, required = False,
|
||||||
label='Include Rest Strokes')
|
label='Include Rest Strokes')
|
||||||
@@ -1322,15 +1367,15 @@ class ForceCurveOptionsForm(forms.Form):
|
|||||||
)
|
)
|
||||||
plottype = forms.ChoiceField(choices=plotchoices,initial='line',
|
plottype = forms.ChoiceField(choices=plotchoices,initial='line',
|
||||||
label='Individual Stroke Chart Type')
|
label='Individual Stroke Chart Type')
|
||||||
|
|
||||||
|
|
||||||
class FlexAxesForm(forms.Form):
|
class FlexAxesForm(forms.Form):
|
||||||
axchoices = list(
|
axchoices = list(
|
||||||
(ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','None']
|
(ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','None']
|
||||||
)
|
)
|
||||||
axchoices = dict((x,y) for x,y in axchoices)
|
axchoices = dict((x,y) for x,y in axchoices)
|
||||||
axchoices = list(sorted(axchoices.items(), key = lambda x:x[1]))
|
axchoices = list(sorted(axchoices.items(), key = lambda x:x[1]))
|
||||||
|
|
||||||
|
|
||||||
yaxchoices = list((ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time'])
|
yaxchoices = list((ax[0],ax[1]) for ax in axes if ax[0] not in ['cumdist','distance','time'])
|
||||||
yaxchoices = dict((x,y) for x,y in yaxchoices)
|
yaxchoices = dict((x,y) for x,y in yaxchoices)
|
||||||
@@ -1355,9 +1400,9 @@ class FlexAxesForm(forms.Form):
|
|||||||
super(FlexAxesForm, self).__init__(*args, **kwargs)
|
super(FlexAxesForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
rower = Rower.objects.get(user=request.user)
|
rower = Rower.objects.get(user=request.user)
|
||||||
|
|
||||||
axchoicespro = (
|
axchoicespro = (
|
||||||
('',ax[1]) if ax[4] == 'pro' and ax[0] else (ax[0],ax[1]) for ax in axes
|
('',ax[1]) if ax[4] == 'pro' and ax[0] else (ax[0],ax[1]) for ax in axes
|
||||||
)
|
)
|
||||||
|
|
||||||
axchoicesbasicx = []
|
axchoicesbasicx = []
|
||||||
@@ -1375,12 +1420,8 @@ class FlexAxesForm(forms.Form):
|
|||||||
if ax[0] not in ['cumdist','distance','time']:
|
if ax[0] not in ['cumdist','distance','time']:
|
||||||
axchoicesbasicy.insert(0,('None',ax[1]+' (PRO)'))
|
axchoicesbasicy.insert(0,('None',ax[1]+' (PRO)'))
|
||||||
|
|
||||||
|
|
||||||
if rower.rowerplan == 'basic':
|
if rower.rowerplan == 'basic':
|
||||||
self.fields['xaxis'].choices = axchoicesbasicx
|
self.fields['xaxis'].choices = axchoicesbasicx
|
||||||
self.fields['yaxis1'].choices = axchoicesbasicy
|
self.fields['yaxis1'].choices = axchoicesbasicy
|
||||||
self.fields['yaxis2'].choices = axchoicesbasicy
|
self.fields['yaxis2'].choices = axchoicesbasicy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1809,6 +1809,127 @@ def leaflet_chart2(lat,lon,name=""):
|
|||||||
|
|
||||||
return script,div
|
return script,div
|
||||||
|
|
||||||
|
def leaflet_chart_video(lat,lon,name=""):
|
||||||
|
if not len(lat) or not len(lon):
|
||||||
|
return [0,"invalid coordinate data"]
|
||||||
|
|
||||||
|
|
||||||
|
# Throw out 0,0
|
||||||
|
df = pd.DataFrame({
|
||||||
|
'lat':lat,
|
||||||
|
'lon':lon
|
||||||
|
})
|
||||||
|
|
||||||
|
df = df.replace(0,np.nan)
|
||||||
|
df = df.loc[(df!=0).any(axis=1)]
|
||||||
|
df.fillna(method='bfill',axis=0,inplace=True)
|
||||||
|
df.fillna(method='ffill',axis=0,inplace=True)
|
||||||
|
lat = df['lat']
|
||||||
|
lon = df['lon']
|
||||||
|
if lat.empty or lon.empty:
|
||||||
|
return [0,"invalid coordinate data"]
|
||||||
|
|
||||||
|
latmean = lat.mean()
|
||||||
|
lonmean = lon.mean()
|
||||||
|
latbegin = lat[lat.index[0]]
|
||||||
|
longbegin = lon[lon.index[0]]
|
||||||
|
latend = lat[lat.index[-1]]
|
||||||
|
longend = lon[lon.index[-1]]
|
||||||
|
|
||||||
|
coordinates = zip(lat,lon)
|
||||||
|
|
||||||
|
scoordinates = "["
|
||||||
|
|
||||||
|
for x,y in coordinates:
|
||||||
|
scoordinates += """[{x},{y}],
|
||||||
|
""".format(
|
||||||
|
x=x,
|
||||||
|
y=y
|
||||||
|
)
|
||||||
|
|
||||||
|
scoordinates += "]"
|
||||||
|
|
||||||
|
script = """
|
||||||
|
|
||||||
|
|
||||||
|
var streets = L.tileLayer('https://api.tiles.mapbox.com/v4/{{id}}/{{z}}/{{x}}/{{y}}.png?access_token=pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA', {{
|
||||||
|
maxZoom: 18,
|
||||||
|
id: 'mapbox.streets'
|
||||||
|
}}),
|
||||||
|
|
||||||
|
satellite = L.tileLayer('https://api.tiles.mapbox.com/v4/{{id}}/{{z}}/{{x}}/{{y}}.png?access_token=pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA', {{
|
||||||
|
maxZoom: 18,
|
||||||
|
id: 'mapbox.satellite'
|
||||||
|
}}),
|
||||||
|
|
||||||
|
outdoors = L.tileLayer('https://api.tiles.mapbox.com/v4/{{id}}/{{z}}/{{x}}/{{y}}.png?access_token=pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA', {{
|
||||||
|
maxZoom: 18,
|
||||||
|
id: 'mapbox.outdoors'
|
||||||
|
}});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var mymap = L.map('map_canvas', {{
|
||||||
|
center: [{latmean}, {lonmean}],
|
||||||
|
zoom: 13,
|
||||||
|
layers: [streets, satellite]
|
||||||
|
}}).setView([{latmean},{lonmean}], 13);
|
||||||
|
|
||||||
|
var navionics = new JNC.Leaflet.NavionicsOverlay({{
|
||||||
|
navKey: 'Navionics_webapi_03205',
|
||||||
|
chartType: JNC.NAVIONICS_CHARTS.NAUTICAL,
|
||||||
|
isTransparent: true,
|
||||||
|
zIndex: 1
|
||||||
|
}});
|
||||||
|
|
||||||
|
|
||||||
|
var osmUrl2='http://tiles.openseamap.org/seamark/{{z}}/{{x}}/{{y}}.png';
|
||||||
|
var osmUrl='http://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png';
|
||||||
|
|
||||||
|
|
||||||
|
//create two TileLayer
|
||||||
|
var nautical=new L.TileLayer(osmUrl,{{
|
||||||
|
maxZoom:18}});
|
||||||
|
|
||||||
|
|
||||||
|
L.control.layers({{
|
||||||
|
"Streets": streets,
|
||||||
|
"Satellite": satellite,
|
||||||
|
"Outdoors": outdoors,
|
||||||
|
"Nautical": nautical,
|
||||||
|
}},{{
|
||||||
|
"Navionics":navionics,
|
||||||
|
}},
|
||||||
|
{{
|
||||||
|
position:'topleft'
|
||||||
|
}}).addTo(mymap);
|
||||||
|
|
||||||
|
var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap);
|
||||||
|
marker.bindPopup("<b>Start</b>");
|
||||||
|
|
||||||
|
var latlongs = {scoordinates}
|
||||||
|
var polyline = L.polyline(latlongs, {{color:'red'}}).addTo(mymap)
|
||||||
|
mymap.fitBounds(polyline.getBounds())
|
||||||
|
|
||||||
|
""".format(
|
||||||
|
latmean=latmean,
|
||||||
|
lonmean=lonmean,
|
||||||
|
latbegin = latbegin,
|
||||||
|
latend=latend,
|
||||||
|
longbegin=longbegin,
|
||||||
|
longend=longend,
|
||||||
|
scoordinates=scoordinates,
|
||||||
|
)
|
||||||
|
|
||||||
|
div = """
|
||||||
|
<div id="map_canvas" style="width: 640px; height: 390px;"><p> </p></div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return script,div
|
||||||
|
|
||||||
|
|
||||||
def interactive_agegroupcpchart(age,normalized=False):
|
def interactive_agegroupcpchart(age,normalized=False):
|
||||||
durations = [1,4,30,60]
|
durations = [1,4,30,60]
|
||||||
distances = [100,500,1000,2000,5000,6000,10000,21097,42195]
|
distances = [100,500,1000,2000,5000,6000,10000,21097,42195]
|
||||||
@@ -2828,6 +2949,114 @@ def interactive_chart(id=0,promember=0,intervaldata = {}):
|
|||||||
|
|
||||||
return [script,div]
|
return [script,div]
|
||||||
|
|
||||||
|
def interactive_chart_video(videodata):
|
||||||
|
|
||||||
|
spm = videodata['spm']
|
||||||
|
time = range(len(spm))
|
||||||
|
|
||||||
|
data = zip(time,spm)
|
||||||
|
|
||||||
|
data2 = "["
|
||||||
|
|
||||||
|
for t,s in data:
|
||||||
|
data2 += "{x: %s, y: %s}, " % (t,s)
|
||||||
|
|
||||||
|
data2 = data2[:-2] + "]"
|
||||||
|
|
||||||
|
markerpoint = {
|
||||||
|
'x': time[0],
|
||||||
|
'y': spm[0],
|
||||||
|
'r': 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
div = """
|
||||||
|
<canvas id="myChart">
|
||||||
|
</canvas>
|
||||||
|
"""
|
||||||
|
|
||||||
|
script = """
|
||||||
|
var ctx = document.getElementById("myChart").getContext('2d');
|
||||||
|
var data = %s
|
||||||
|
|
||||||
|
var myChart = new Chart(ctx, {
|
||||||
|
type: 'scatter',
|
||||||
|
label: 'SPM',
|
||||||
|
animationSteps: 10,
|
||||||
|
options: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
duration: 100,
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
yAxes: [{
|
||||||
|
scaleLabel: {
|
||||||
|
display: true,
|
||||||
|
labelString: 'Stroke Rate'
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
xAxes: [{
|
||||||
|
scaleLabel: {
|
||||||
|
type: 'linear',
|
||||||
|
display: true,
|
||||||
|
labelString: 'Time (seconds)'
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data:
|
||||||
|
{
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
type: 'bubble',
|
||||||
|
label: 'now',
|
||||||
|
data: [ %s ],
|
||||||
|
backgroundColor: '#36a2eb',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'spm',
|
||||||
|
data: data,
|
||||||
|
backgroundColor: "#ff0000",
|
||||||
|
borderColor: "#ff0000",
|
||||||
|
fill: false,
|
||||||
|
borderDash: [0, 0],
|
||||||
|
pointRadius: 1,
|
||||||
|
pointHoverRadius: 1,
|
||||||
|
showLine: true,
|
||||||
|
tension: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var marker = {
|
||||||
|
datapoint: %s ,
|
||||||
|
setLatLng: function (LatLng) {
|
||||||
|
var lat = LatLng.lat;
|
||||||
|
var lng = LatLng.lng;
|
||||||
|
this.datapoint = {
|
||||||
|
'x': lat,
|
||||||
|
'y': lng,
|
||||||
|
'r': 10,
|
||||||
|
}
|
||||||
|
myChart.data.datasets[0].data[0] = this.datapoint;
|
||||||
|
myChart.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
marker.setLatLng({
|
||||||
|
'lat': data[0]['x'],
|
||||||
|
'lng': data[0]['y']
|
||||||
|
})
|
||||||
|
|
||||||
|
""" % (data2, markerpoint, markerpoint)
|
||||||
|
|
||||||
|
return [script,div]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='',
|
def interactive_multiflex(datadf,xparam,yparam,groupby,extratitle='',
|
||||||
ploterrorbars=False,
|
ploterrorbars=False,
|
||||||
title=None,binsize=1,colorlegend=[],
|
title=None,binsize=1,colorlegend=[],
|
||||||
|
|||||||
+69
-45
@@ -34,7 +34,6 @@ nometrics = [
|
|||||||
'workoutid',
|
'workoutid',
|
||||||
'totalangle',
|
'totalangle',
|
||||||
'hr_bottom',
|
'hr_bottom',
|
||||||
'x_right',
|
|
||||||
'Position',
|
'Position',
|
||||||
'Extensions',
|
'Extensions',
|
||||||
'GPS Speed',
|
'GPS Speed',
|
||||||
@@ -56,8 +55,9 @@ rowingmetrics = (
|
|||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 1e5,
|
'ax_max': 1e5,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group':'basic'}),
|
||||||
|
|
||||||
('hr',{
|
('hr',{
|
||||||
'numtype':'integer',
|
'numtype':'integer',
|
||||||
'null':True,
|
'null':True,
|
||||||
@@ -65,7 +65,8 @@ rowingmetrics = (
|
|||||||
'ax_min': 100,
|
'ax_min': 100,
|
||||||
'ax_max': 200,
|
'ax_max': 200,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group':'athlete'}),
|
||||||
|
|
||||||
('pace',{
|
('pace',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -74,7 +75,8 @@ rowingmetrics = (
|
|||||||
'ax_min': 1.0e3*210,
|
'ax_min': 1.0e3*210,
|
||||||
'ax_max': 1.0e3*75,
|
'ax_max': 1.0e3*75,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group': 'basic'}),
|
||||||
|
|
||||||
('velo',{
|
('velo',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -84,7 +86,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 8,
|
'ax_max': 8,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type':'pro'}),
|
'type':'pro',
|
||||||
|
'group': 'basic'}),
|
||||||
|
|
||||||
# ('powerhr',{
|
# ('powerhr',{
|
||||||
# 'numtype':'float',
|
# 'numtype':'float',
|
||||||
@@ -93,8 +96,9 @@ rowingmetrics = (
|
|||||||
# 'ax_min':0,
|
# 'ax_min':0,
|
||||||
# 'ax_max':300,
|
# 'ax_max':300,
|
||||||
# 'mode':'both',
|
# 'mode':'both',
|
||||||
# 'type':'pro'}),
|
# 'type':'pro',
|
||||||
|
# 'group':'athlete'}),
|
||||||
|
|
||||||
('spm',{
|
('spm',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
'null':True,
|
'null':True,
|
||||||
@@ -102,8 +106,9 @@ rowingmetrics = (
|
|||||||
'ax_min': 15,
|
'ax_min': 15,
|
||||||
'ax_max': 45,
|
'ax_max': 45,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group':'basic'}),
|
||||||
|
|
||||||
('driveenergy',{
|
('driveenergy',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
'null':True,
|
'null':True,
|
||||||
@@ -111,8 +116,9 @@ rowingmetrics = (
|
|||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 1000,
|
'ax_max': 1000,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'forcepower'}),
|
||||||
|
|
||||||
('power',{
|
('power',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
'null':True,
|
'null':True,
|
||||||
@@ -120,8 +126,9 @@ rowingmetrics = (
|
|||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 600,
|
'ax_max': 600,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group':'forcepower'}),
|
||||||
|
|
||||||
('averageforce',{
|
('averageforce',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
'null':True,
|
'null':True,
|
||||||
@@ -129,8 +136,9 @@ rowingmetrics = (
|
|||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 1200,
|
'ax_max': 1200,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'forcepower'}),
|
||||||
|
|
||||||
('peakforce',{
|
('peakforce',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
'null':True,
|
'null':True,
|
||||||
@@ -138,16 +146,18 @@ rowingmetrics = (
|
|||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 1500,
|
'ax_max': 1500,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'forcepower'}),
|
||||||
|
|
||||||
('drivelength',{
|
('drivelength',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
'null':True,
|
'null':True,
|
||||||
'verbose_name': 'Drive Length (m)',
|
'verbose_name': 'Drive Length (m)',
|
||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 2.5,
|
'ax_max': 2.5,
|
||||||
'mode':'both',
|
'mode':'rower',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'stroke'}),
|
||||||
|
|
||||||
('forceratio',{
|
('forceratio',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -156,16 +166,18 @@ rowingmetrics = (
|
|||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 1,
|
'ax_max': 1,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group': 'forcepower'}),
|
||||||
|
|
||||||
('distance',{
|
('distance',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
'null':True,
|
'null':True,
|
||||||
'verbose_name': 'Distance (m)',
|
'verbose_name': 'Interval Distance (m)',
|
||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 1e5,
|
'ax_max': 1e5,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group':'basic'}),
|
||||||
|
|
||||||
('cumdist',{
|
('cumdist',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -174,7 +186,8 @@ rowingmetrics = (
|
|||||||
'ax_min': 0,
|
'ax_min': 0,
|
||||||
'ax_max': 1e5,
|
'ax_max': 1e5,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group':'basic'}),
|
||||||
|
|
||||||
('drivespeed',{
|
('drivespeed',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -184,7 +197,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 4,
|
'ax_max': 4,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group': 'stroke'}),
|
||||||
|
|
||||||
('catch',{
|
('catch',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -194,7 +208,8 @@ rowingmetrics = (
|
|||||||
'ax_max': -75,
|
'ax_max': -75,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group': 'stroke'}),
|
||||||
|
|
||||||
('slip',{
|
('slip',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -204,7 +219,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 20,
|
'ax_max': 20,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group': 'stroke'}),
|
||||||
|
|
||||||
('finish',{
|
('finish',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -214,7 +230,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 55,
|
'ax_max': 55,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group': 'stroke'}),
|
||||||
|
|
||||||
('wash',{
|
('wash',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -224,7 +241,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 30,
|
'ax_max': 30,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group': 'stroke'}),
|
||||||
|
|
||||||
('peakforceangle',{
|
('peakforceangle',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -234,9 +252,10 @@ rowingmetrics = (
|
|||||||
'ax_max': 50,
|
'ax_max': 50,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'stroke'}),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
('totalangle',{
|
('totalangle',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
'null':True,
|
'null':True,
|
||||||
@@ -245,9 +264,10 @@ rowingmetrics = (
|
|||||||
'ax_max': 140,
|
'ax_max': 140,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'stroke'}),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
('effectiveangle',{
|
('effectiveangle',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
'null':True,
|
'null':True,
|
||||||
@@ -256,7 +276,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 140,
|
'ax_max': 140,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'stroke'}),
|
||||||
|
|
||||||
('rhythm',{
|
('rhythm',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -266,7 +287,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 55,
|
'ax_max': 55,
|
||||||
'default': 1.0,
|
'default': 1.0,
|
||||||
'mode':'erg',
|
'mode':'erg',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'stroke'}),
|
||||||
|
|
||||||
('efficiency',{
|
('efficiency',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -276,7 +298,8 @@ rowingmetrics = (
|
|||||||
'ax_max': 110,
|
'ax_max': 110,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'water',
|
'mode':'water',
|
||||||
'type': 'pro'}),
|
'type': 'pro',
|
||||||
|
'group':'forcepower'}),
|
||||||
|
|
||||||
('distanceperstroke',{
|
('distanceperstroke',{
|
||||||
'numtype':'float',
|
'numtype':'float',
|
||||||
@@ -286,10 +309,13 @@ rowingmetrics = (
|
|||||||
'ax_max': 15,
|
'ax_max': 15,
|
||||||
'default': 0,
|
'default': 0,
|
||||||
'mode':'both',
|
'mode':'both',
|
||||||
'type': 'basic'}),
|
'type': 'basic',
|
||||||
|
'group':'basic'}),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
metricsgroups = list(set([d['group'] for n,d in rowingmetrics]))
|
||||||
|
|
||||||
dtypes = {}
|
dtypes = {}
|
||||||
|
|
||||||
for name,d in rowingmetrics:
|
for name,d in rowingmetrics:
|
||||||
@@ -297,11 +323,11 @@ for name,d in rowingmetrics:
|
|||||||
dtypes[name] = float
|
dtypes[name] = float
|
||||||
elif d['numtype'] == 'int':
|
elif d['numtype'] == 'int':
|
||||||
dtypes[name] = int
|
dtypes[name] = int
|
||||||
|
|
||||||
axesnew = [
|
axesnew = [
|
||||||
(name,d['verbose_name'],d['ax_min'],d['ax_max'],d['type']) for name,d in rowingmetrics
|
(name,d['verbose_name'],d['ax_min'],d['ax_max'],d['type']) for name,d in rowingmetrics
|
||||||
]
|
]
|
||||||
|
|
||||||
axes = tuple(axesnew+[('None','<None>',0,1,'basic')])
|
axes = tuple(axesnew+[('None','<None>',0,1,'basic')])
|
||||||
|
|
||||||
axlabels = {ax[0]:ax[1] for ax in axes}
|
axlabels = {ax[0]:ax[1] for ax in axes}
|
||||||
@@ -328,7 +354,7 @@ defaultfavoritecharts = (
|
|||||||
'workouttype':'both',
|
'workouttype':'both',
|
||||||
'reststrokes':True,
|
'reststrokes':True,
|
||||||
'notes':"""This chart shows your pace and heart rate versus time.
|
'notes':"""This chart shows your pace and heart rate versus time.
|
||||||
Heart rate values will be shown only when it is in your data, i.e.
|
Heart rate values will be shown only when it is in your data, i.e.
|
||||||
in case you recorded your heart rate during your workout""",
|
in case you recorded your heart rate during your workout""",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -358,9 +384,9 @@ plotted versus time. """,
|
|||||||
'plottype':'scatter',
|
'plottype':'scatter',
|
||||||
'workouttype':'otw',
|
'workouttype':'otw',
|
||||||
'reststrokes':True,
|
'reststrokes':True,
|
||||||
'notes':"""This chart shows the Distance per Stroke versus stroke
|
'notes':"""This chart shows the Distance per Stroke versus stroke
|
||||||
stroke rate. You should see a steady decline of the Distance per Stroke
|
stroke rate. You should see a steady decline of the Distance per Stroke
|
||||||
as you increase stroke rate. Typical values are > 10m for steady state
|
as you increase stroke rate. Typical values are > 10m for steady state
|
||||||
dropping to 8m for race pace in the single.""",
|
dropping to 8m for race pace in the single.""",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -395,5 +421,3 @@ def calc_trimp(df,sex,hrmax,hrmin,hrftp):
|
|||||||
hrtss = 100*trimp/trimp1hr
|
hrtss = 100*trimp/trimp1hr
|
||||||
|
|
||||||
return trimp,hrtss
|
return trimp,hrtss
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3720,3 +3720,18 @@ class BlogPost(models.Model):
|
|||||||
title = models.TextField(max_length=300)
|
title = models.TextField(max_length=300)
|
||||||
link = models.TextField(max_length=300)
|
link = models.TextField(max_length=300)
|
||||||
date = models.DateField()
|
date = models.DateField()
|
||||||
|
|
||||||
|
defaultgroups = ['basic']
|
||||||
|
|
||||||
|
class VideoAnalysis(models.Model):
|
||||||
|
name = models.CharField(default='', max_length=150,blank=True,null=True)
|
||||||
|
video_id = models.CharField(default='',max_length=150)
|
||||||
|
delay = models.IntegerField(default=0)
|
||||||
|
workout = models.ForeignKey(Workout, on_delete=models.CASCADE)
|
||||||
|
metricsgroups = TemplateListField(default=defaultgroups)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('video_id','workout')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|||||||
@@ -0,0 +1,360 @@
|
|||||||
|
{% extends "newbase.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load leaflet_tags %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% block title %}Workout Video{% endblock %}
|
||||||
|
|
||||||
|
{% block meta %}
|
||||||
|
{% leaflet_js %}
|
||||||
|
{% leaflet_css %}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.js"></script>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('form').submit(function(e) {
|
||||||
|
$(':disabled').each(function(e) {
|
||||||
|
$(this).removeAttr('disabled');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.slidecontainer {
|
||||||
|
width: 100%; /* Width of the outside container */
|
||||||
|
}
|
||||||
|
|
||||||
|
.bold { font-weight: bold; }
|
||||||
|
|
||||||
|
/* The slider itself */
|
||||||
|
.slider {
|
||||||
|
-webkit-appearance: none; /* Override default CSS styles */
|
||||||
|
appearance: none;
|
||||||
|
width: 100%; /* Full-width */
|
||||||
|
height: 25px; /* Specified height */
|
||||||
|
background: #d3d3d3; /* Grey background */
|
||||||
|
outline: none; /* Remove outline */
|
||||||
|
opacity: 0.7; /* Set transparency (for mouse-over effects on hover) */
|
||||||
|
-webkit-transition: .2s; /* 0.2 seconds transition on hover */
|
||||||
|
transition: opacity .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mouse-over effects */
|
||||||
|
.slider:hover {
|
||||||
|
opacity: 1; /* Fully shown on mouse-over */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */
|
||||||
|
.slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none; /* Override default look */
|
||||||
|
appearance: none;
|
||||||
|
width: 25px; /* Set a specific slider handle width */
|
||||||
|
height: 25px; /* Slider handle height */
|
||||||
|
background: #4CAF50; /* Green background */
|
||||||
|
cursor: pointer; /* Cursor on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider::-moz-range-thumb {
|
||||||
|
width: 25px; /* Set a specific slider handle width */
|
||||||
|
height: 25px; /* Slider handle height */
|
||||||
|
background: #4CAF50; /* Green background */
|
||||||
|
cursor: pointer; /* Cursor on hover */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{% language 'en' %}
|
||||||
|
<h1>Video Analysis for {{ workout.name }}</h1>
|
||||||
|
<ul class="main-content">
|
||||||
|
{% if user.is_authenticated and user == workout.user.user and not locked %}
|
||||||
|
<li class="grid_2">
|
||||||
|
<p>Paste link to you tube video below</p>
|
||||||
|
<p>Use the slider to locate start point for video on workout map</p>
|
||||||
|
<p>Playing the video will advance the data in synchonization. Use the regular YouTube
|
||||||
|
controls
|
||||||
|
to move around in the video and play it.</p>
|
||||||
|
<p>You can make manual adjustments to the delay to fine tune the alignment.
|
||||||
|
Once you are finished, check "Lock Video and Data" to lock the video and the data.</p>
|
||||||
|
<p>Once you are happy with the alignment, you can save the analysis, and share with other people.</p>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="grid_2">
|
||||||
|
<p>Playing the video will advance the data in synchronization. Use the regular
|
||||||
|
YouTube controls to move around in the video and play it.</p>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
<ul class="main-content">
|
||||||
|
<li class="grid_2">
|
||||||
|
<div id="theplot" class="flexplot mapdiv">
|
||||||
|
{{ mapdiv | safe}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="slidecontainer">
|
||||||
|
<input type="range" min="0" max="{{ maxtime }}" value="{{ analysis.delay }}"
|
||||||
|
id="myRange">
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
|
<div id="player"></div>
|
||||||
|
<script>
|
||||||
|
// 1. Code for the map
|
||||||
|
{{ mapscript | safe }}
|
||||||
|
|
||||||
|
|
||||||
|
// 2. This code loads the IFrame Player API code asynchronously.
|
||||||
|
var tag = document.createElement('script');
|
||||||
|
|
||||||
|
tag.src = "https://www.youtube.com/iframe_api";
|
||||||
|
var firstScriptTag = document.getElementsByTagName('script')[0];
|
||||||
|
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
||||||
|
|
||||||
|
// 3. This function creates an <iframe> (and YouTube player)
|
||||||
|
// after the API code downloads.
|
||||||
|
var player;
|
||||||
|
var playing;
|
||||||
|
var videotime = 0;
|
||||||
|
var data = JSON.parse('{{ data|safe }}');
|
||||||
|
{% for id, metric in metrics.items %}
|
||||||
|
var {{ id }}_values = data["{{ id }}"];
|
||||||
|
// console.log("{{ id }}_values",{{ id }}_values);
|
||||||
|
{% endfor %}
|
||||||
|
// var boatspeed = data["boatspeed"];
|
||||||
|
var latitude = data["latitude"];
|
||||||
|
var longitude = data["longitude"];
|
||||||
|
// var spm = data["spm"];
|
||||||
|
// var ctch = data["catch"];
|
||||||
|
|
||||||
|
function onYouTubeIframeAPIReady() {
|
||||||
|
player = new YT.Player('player', {
|
||||||
|
height: '315',
|
||||||
|
width: '560',
|
||||||
|
modestbranding: '1',
|
||||||
|
videoId: '{{ video_id }}',
|
||||||
|
events: {
|
||||||
|
'onReady': onPlayerReady,
|
||||||
|
'onStateChange': onPlayerStateChange
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. The API will call this function when the video player is ready.
|
||||||
|
function onPlayerReady(event) {
|
||||||
|
// event.target.playVideo();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTime() {
|
||||||
|
var oldTime = videotime;
|
||||||
|
var slider = document.getElementById("myRange");
|
||||||
|
var lock = document.getElementById("lock");
|
||||||
|
if(player && player.getCurrentTime) {
|
||||||
|
videotime = player.getCurrentTime();
|
||||||
|
var delay = document.getElementById("id_delay").value;
|
||||||
|
sliderpos = Math.round(videotime) + Math.round(delay);
|
||||||
|
slider.value = sliderpos;
|
||||||
|
|
||||||
|
var datatime = parseFloat(videotime)+parseFloat(delay);
|
||||||
|
// velo = boatspeed[Math.round(datatime)];
|
||||||
|
lat = latitude[Math.round(datatime)];
|
||||||
|
lon = longitude[Math.round(datatime)];
|
||||||
|
// strokerate = spm[Math.round(datatime)];
|
||||||
|
// catchangle = ctch[Math.round(datatime)];
|
||||||
|
{% for id, metric in metrics.items %}
|
||||||
|
{{ id }}_now = {{ id }}_values[Math.round(datatime)];
|
||||||
|
// console.log(datatime,{{ id }}_now, "{{ metric.name }}")
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
document.getElementById("time").innerHTML = Math.round(videotime);
|
||||||
|
document.getElementById("datatime").innerHTML = Math.round(datatime);
|
||||||
|
// document.getElementById("speed").innerHTML = velo;
|
||||||
|
// document.getElementById("spm").innerHTML = strokerate;
|
||||||
|
// document.getElementById("catch").innerHTML = catchangle;
|
||||||
|
{% for id, metric in metrics.items %}
|
||||||
|
document.getElementById("{{ id }}").innerHTML = {{ id }}_now;
|
||||||
|
document.getElementById("{{ id }}").className = 'bold';
|
||||||
|
{% endfor %}
|
||||||
|
{% for group in metricsgroups %}
|
||||||
|
try {
|
||||||
|
set_{{ group }}();
|
||||||
|
} catch (e) {}
|
||||||
|
{% endfor %}
|
||||||
|
// gauge.set(catch_now);
|
||||||
|
try {
|
||||||
|
var newLatLng = new L.LatLng(lat, lon);
|
||||||
|
// console.log(newLatLng);
|
||||||
|
marker.setLatLng(newLatLng);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
if(videotime !== oldTime) {
|
||||||
|
onProgress(videotime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeupdater = setInterval(updateTime, 1000);
|
||||||
|
|
||||||
|
// when the time changes, this will be called.
|
||||||
|
function onProgress(currentTime) {
|
||||||
|
var slider = document.getElementById("myRange");
|
||||||
|
var lock = document.getElementById("lock");
|
||||||
|
videotime = player.getCurrentTime();
|
||||||
|
var delay = document.getElementById("id_delay").value;
|
||||||
|
sliderpos = Math.round(videotime) + Math.round(delay);
|
||||||
|
slider.value = sliderpos;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopVideo() {
|
||||||
|
player.stopVideo();
|
||||||
|
}
|
||||||
|
|
||||||
|
// call this function when player state changes
|
||||||
|
function onPlayerStateChange(event) {
|
||||||
|
if (event.data == YT.PlayerState.PLAYING) {
|
||||||
|
playing = false;
|
||||||
|
} else {
|
||||||
|
playing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</li>
|
||||||
|
{% if user.is_authenticated and user == workout.user.user %}
|
||||||
|
<li class="grid_4">
|
||||||
|
<input type="checkbox" name="lock" id="lock" value="Lock">Lock Data and Video
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="grid_4">
|
||||||
|
<input type="hidden" name="lock" id="lock" value="Lock">
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li>
|
||||||
|
Data Time
|
||||||
|
<span id="datatime">
|
||||||
|
</span> seconds
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Video Time
|
||||||
|
<span id="time">
|
||||||
|
</span> seconds
|
||||||
|
</li>
|
||||||
|
{% for id, metric in metrics.items %}
|
||||||
|
<li>
|
||||||
|
{{ metric.name }}
|
||||||
|
<span id="{{ id }}">
|
||||||
|
</span> {{ metric.unit }}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% for group in metricsgroups %}
|
||||||
|
<li class="grid">
|
||||||
|
<canvas id="{{ group }}"></canvas>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<p> </p>
|
||||||
|
<form enctype="multipart/form-data" action="" method="post">
|
||||||
|
<ul class="main-content">
|
||||||
|
{% if form and user.is_authenticated and user == workout.user.user %}
|
||||||
|
<li class="grid_2">
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if not analysis.id %}
|
||||||
|
<input type="submit" name="reload_button" value="Reload">
|
||||||
|
{% endif %}
|
||||||
|
<input type="submit" name="save_button" value="Save">
|
||||||
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
|
<table>
|
||||||
|
{{ metricsform.as_table }}
|
||||||
|
</table>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<input type="hidden" id="id_delay" value="{{ analysis.delay }}">
|
||||||
|
{% endif %}
|
||||||
|
<li>
|
||||||
|
{% if analysis and user.is_authenticated and user == analysis.workout.user.user %}
|
||||||
|
<p>
|
||||||
|
<a href="/rowers/video/{{ analysis.id }}/delete/">Delete Analysis</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
$(document).ready( function() {
|
||||||
|
// lock
|
||||||
|
var lock = document.getElementById("lock");
|
||||||
|
var output = document.getElementById("id_delay");
|
||||||
|
{% if locked %}
|
||||||
|
lock.checked = true;
|
||||||
|
output.disabled = true;
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
// slider
|
||||||
|
var slider = document.getElementById("myRange");
|
||||||
|
{% if user.is_authenticated and user == workout.user.user %}
|
||||||
|
document.getElementById("myRange").style.display = "block";
|
||||||
|
{% else %}
|
||||||
|
document.getElementById("myRange").style.display = "none";
|
||||||
|
{% endif %}
|
||||||
|
var output = document.getElementById("id_delay");
|
||||||
|
try {
|
||||||
|
output.value = Math.round(slider.value)-Math.round(player.getCurrentTime()); // Display the default slider value
|
||||||
|
}
|
||||||
|
catch(err) {
|
||||||
|
output.value = Math.round(slider.value);
|
||||||
|
}
|
||||||
|
// Update the current slider value (each time you drag the slider handle)
|
||||||
|
slider.oninput = function() {
|
||||||
|
clearInterval(timeupdater)
|
||||||
|
if (lock.checked) {
|
||||||
|
if (this.value-output.value > 0) {
|
||||||
|
player.seekTo(this.value-output.value);
|
||||||
|
} else {
|
||||||
|
if (playing) {
|
||||||
|
player.seekTo(0);
|
||||||
|
player.playVideo();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
player.seekTo(0);
|
||||||
|
player.pauseVideo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// console.log('changing');
|
||||||
|
output.value = this.value-Math.round(player.getCurrentTime());
|
||||||
|
}
|
||||||
|
timeupdater = setInterval(updateTime, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
output.oninput = function() {
|
||||||
|
slider.value = this.value+Math.round(player.getCurrentTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
// lock delay form field if checkbox checked
|
||||||
|
lock.oninput = function() {
|
||||||
|
if (this.checked) {
|
||||||
|
output.disabled = true;
|
||||||
|
} else {
|
||||||
|
output.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="https://bernii.github.io/gauge.js/dist/gauge.js"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'js/videogauges.js' %}"></script>
|
||||||
|
{% endlanguage %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
{% include 'menu_workout.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -12,14 +12,15 @@
|
|||||||
<h1>Recent Graphs</h1>
|
<h1>Recent Graphs</h1>
|
||||||
|
|
||||||
<ul class="main-content">
|
<ul class="main-content">
|
||||||
{% if graphs %}
|
<li class="grid_2">
|
||||||
<li class="grid_2">
|
|
||||||
<form id="searchform" action="."
|
<form id="searchform" action="."
|
||||||
method="get" accept-charset="utf-8">
|
method="get" accept-charset="utf-8">
|
||||||
{{ searchform }}
|
{{ searchform }}
|
||||||
<input type="submit" value="GO"></input>
|
<input type="submit" value="GO"></input>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
|
{% if graphs %}
|
||||||
|
|
||||||
<li class="grid_2">
|
<li class="grid_2">
|
||||||
<p>
|
<p>
|
||||||
<span>
|
<span>
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
<a href="?page=1&q={{ request.GET.q }}">
|
<a href="?page=1&q={{ request.GET.q }}">
|
||||||
<i class="fas fa-arrow-alt-to-left"></i>
|
<i class="fas fa-arrow-alt-to-left"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="?page={{ workouts.previous_page_number }}&q={{ request.GET.q }}">
|
<a href="?page={{ graphs.previous_page_number }}&q={{ request.GET.q }}">
|
||||||
<i class="fas fa-arrow-alt-left"></i>
|
<i class="fas fa-arrow-alt-left"></i>
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -40,11 +41,11 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
Page {{ graphs.number }} of {{ graphs.paginator.num_pages }}.
|
Page {{ graphs.number }} of {{ graphs.paginator.num_pages }}.
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{% if graphs.has_next %}
|
{% if graphs.has_next %}
|
||||||
{% if request.GET.q %}
|
{% if request.GET.q %}
|
||||||
<a href="?page={{ graphs.next_page_number }}&q={{ request.GET.q }}">
|
<a href="?page={{ graphs.next_page_number }}&q={{ request.GET.q }}">
|
||||||
@@ -69,7 +70,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<p class="caption">
|
<p class="caption">
|
||||||
<a href="/rowers/graph/{{ graph.id }}/">
|
<a href="/rowers/graph/{{ graph.id }}/">
|
||||||
<img src="/{{ graph.filename }}"
|
<img src="/{{ graph.filename }}"
|
||||||
onerror="this.src='/static/img/rowingtimer.gif'"
|
onerror="this.src='/static/img/rowingtimer.gif'"
|
||||||
alt="{{ graph.filename }}" width="120" height="100">
|
alt="{{ graph.filename }}" width="120" height="100">
|
||||||
</a>
|
</a>
|
||||||
@@ -90,4 +91,4 @@
|
|||||||
|
|
||||||
{% block sidebar %}
|
{% block sidebar %}
|
||||||
{% include 'menu_workouts.html' %}
|
{% include 'menu_workouts.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
{% extends "newbase.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
|
||||||
|
{% block title %}Rowsandall Recent Video Analyses{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% include "monitorjobs.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>Recent videos</h1>
|
||||||
|
|
||||||
|
<ul class="main-content">
|
||||||
|
<li class="grid_2">
|
||||||
|
<form id="searchform" action="."
|
||||||
|
method="get" accept-charset="utf-8">
|
||||||
|
{{ searchform }}
|
||||||
|
<input type="submit" value="GO"></input>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
{% if analyses %}
|
||||||
|
|
||||||
|
<li class="grid_2">
|
||||||
|
<p>
|
||||||
|
<span>
|
||||||
|
{% if analyses.has_previous %}
|
||||||
|
{% if request.GET.q %}
|
||||||
|
<a href="?page=1&q={{ request.GET.q }}">
|
||||||
|
<i class="fas fa-arrow-alt-to-left"></i>
|
||||||
|
</a>
|
||||||
|
<a href="?page={{ analyses.previous_page_number }}&q={{ request.GET.q }}">
|
||||||
|
<i class="fas fa-arrow-alt-left"></i>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="?page=1">
|
||||||
|
<i class="fas fa-arrow-alt-to-left"></i>
|
||||||
|
</a>
|
||||||
|
<a href="?page={{ analyses.previous_page_number }}">
|
||||||
|
<i class="fas fa-arrow-alt-left"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span>
|
||||||
|
Page {{ analyses.number }} of {{ analyses.paginator.num_pages }}.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{% if analyses.has_next %}
|
||||||
|
{% if request.GET.q %}
|
||||||
|
<a href="?page={{ analyses.next_page_number }}&q={{ request.GET.q }}">
|
||||||
|
<i class="fas fa-arrow-alt-right"></i>
|
||||||
|
</a>
|
||||||
|
<a href="?page={{ analyses.paginator.num_pages }}&q={{ request.GET.q }}">
|
||||||
|
<i class="fas fa-arrow-alt-to-right">
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="?page={{ analyses.next_page_number }}">
|
||||||
|
<i class="fas fa-arrow-alt-right"></i>
|
||||||
|
</a>
|
||||||
|
<a href="?page={{ analyses.paginator.num_pages }}">
|
||||||
|
<i class="fas fa-arrow-alt-to-right"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
{% for video in analyses %}
|
||||||
|
<li>
|
||||||
|
<div>{{ video.name }}</div>
|
||||||
|
<a href="/rowers/video/{{ video.id|encode }}/">
|
||||||
|
<img src="https://img.youtube.com/vi/{{ video.video_id }}/hqdefault.jpg"/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<li class="grid_4">
|
||||||
|
<p>
|
||||||
|
No videos found
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
{% include 'menu_analytics.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -2,6 +2,24 @@
|
|||||||
{% load rowerfilters %}
|
{% load rowerfilters %}
|
||||||
<h1><a href="/rowers/analysis/">Analysis</a></h1>
|
<h1><a href="/rowers/analysis/">Analysis</a></h1>
|
||||||
<ul class="cd-accordion-menu animated">
|
<ul class="cd-accordion-menu animated">
|
||||||
|
<li class="has-children" id="videos">
|
||||||
|
<input type="checkbox" name="group-videos" id="group-videos" checked>
|
||||||
|
<label for="group-videos">
|
||||||
|
<i class="fas fa-film fa-fw"></i> Video Analysis
|
||||||
|
</label>
|
||||||
|
<ul>
|
||||||
|
<li id="videos-analyses">
|
||||||
|
<a href="/rowers/videos/">
|
||||||
|
<i class="fas fa-film fa-fw"></i> Video Analyses
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li id="video-add">
|
||||||
|
<a href="/rowers/add-video/">
|
||||||
|
<i class="fas fa-video-plus fa-fw"></i> New Video Analysis
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
<li class="has-children" id="fitness">
|
<li class="has-children" id="fitness">
|
||||||
<input type="checkbox" name="group-fitness" id="group-fitness" checked>
|
<input type="checkbox" name="group-fitness" id="group-fitness" checked>
|
||||||
<label for="group-fitness">
|
<label for="group-fitness">
|
||||||
@@ -18,7 +36,7 @@
|
|||||||
<i class="fas fa-balance-scale fa-fw"></i> Compare
|
<i class="fas fa-balance-scale fa-fw"></i> Compare
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
<li id="fitness-powerprogress">
|
<li id="fitness-powerprogress">
|
||||||
<a href="/rowers/fitness-progress/">
|
<a href="/rowers/fitness-progress/">
|
||||||
<i class="far fa-watch-fitness fa-fw"></i> Power Progress
|
<i class="far fa-watch-fitness fa-fw"></i> Power Progress
|
||||||
@@ -61,79 +79,79 @@
|
|||||||
<a href="/rowers/user-analysis-select/stats/">
|
<a href="/rowers/user-analysis-select/stats/">
|
||||||
<i class="fal fa-table fa-fw"></i> Statistics
|
<i class="fal fa-table fa-fw"></i> Statistics
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li id="stats-histo">
|
<li id="stats-histo">
|
||||||
<a href="/rowers/user-analysis-select/histo/">
|
<a href="/rowers/user-analysis-select/histo/">
|
||||||
<i class="fas fa-chart-bar fa-fw"></i> Histogram
|
<i class="fas fa-chart-bar fa-fw"></i> Histogram
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/rowers/alerts/">
|
<a href="/rowers/alerts/">
|
||||||
<i class="fas fa-bell fa-fw"></i> Alerts
|
<i class="fas fa-bell fa-fw"></i> Alerts
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/rowers/user-analysis-select/flexall/">
|
<a href="/rowers/user-analysis-select/flexall/">
|
||||||
<i class="fas fa-chart-line fa-fw"></i> Cumulative Flex Chart
|
<i class="fas fa-chart-line fa-fw"></i> Cumulative Flex Chart
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/rowers/laboratory/">
|
<a href="/rowers/laboratory/">
|
||||||
<i class="fas fa-flask fa-fw"></i> Laboratory</a>
|
<i class="fas fa-flask fa-fw"></i> Laboratory</a>
|
||||||
</li>
|
</li>
|
||||||
</ul><!-- cd-accordion-menu -->
|
</ul><!-- cd-accordion-menu -->
|
||||||
|
|
||||||
{% if user.is_authenticated and user|is_manager and rower %}
|
{% if user.is_authenticated and user|is_manager and rower %}
|
||||||
<p> </p>
|
<p> </p>
|
||||||
|
|
||||||
{% if user|coach_rowers %}
|
{% if user|coach_rowers %}
|
||||||
<ul class="cd-accordion-menu animated">
|
<ul class="cd-accordion-menu animated">
|
||||||
<li class="has-children" id="athletes">
|
<li class="has-children" id="athletes">
|
||||||
<input type="checkbox" name="athlete-selector" id="athlete-selector">
|
<input type="checkbox" name="athlete-selector" id="athlete-selector">
|
||||||
<label for="athlete-selector"><i class="fas fa-users fa-fw"></i> Athletes</label>
|
<label for="athlete-selector"><i class="fas fa-users fa-fw"></i> Athletes</label>
|
||||||
<ul>
|
<ul>
|
||||||
{% for member in user|coach_rowers %}
|
{% for member in user|coach_rowers %}
|
||||||
<a href={{ request.path|userurl:member.user }}>
|
<a href={{ request.path|userurl:member.user }}>
|
||||||
<i class="fas fa-user fa-fw"></i>
|
<i class="fas fa-user fa-fw"></i>
|
||||||
{% if member.user == rower.user %}
|
{% if member.user == rower.user %}
|
||||||
•
|
•
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ member.user.first_name }} {{ member.user.last_name }}
|
{{ member.user.first_name }} {{ member.user.last_name }}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if user|user_teams %}
|
{% if user|user_teams %}
|
||||||
<p> </p>
|
<p> </p>
|
||||||
<ul class="cd-accordion-menu animated">
|
<ul class="cd-accordion-menu animated">
|
||||||
<li class="has-children" id="teams">
|
<li class="has-children" id="teams">
|
||||||
<input type="checkbox" name="team-selector" id="team-selector">
|
<input type="checkbox" name="team-selector" id="team-selector">
|
||||||
<label for="team-selector"><i class="fas fa-bullhorn fa-fw"></i> Groups</label>
|
<label for="team-selector"><i class="fas fa-bullhorn fa-fw"></i> Groups</label>
|
||||||
<ul>
|
<ul>
|
||||||
{% for tteam in teams %}
|
{% for tteam in teams %}
|
||||||
<li>
|
<li>
|
||||||
<a href={{ request.path|teamurl:tteam }}>
|
<a href={{ request.path|teamurl:tteam }}>
|
||||||
<i class="fas fa-users fa-fw"></i>
|
<i class="fas fa-users fa-fw"></i>
|
||||||
{% if tteam == team %}
|
{% if tteam == team %}
|
||||||
•
|
•
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ tteam.name }}
|
{{ tteam.name }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% include 'menuscript.html' %}
|
{% include 'menuscript.html' %}
|
||||||
|
|||||||
@@ -45,6 +45,11 @@
|
|||||||
<i class="fas fa-balance-scale fa-fw"></i> Compare
|
<i class="fas fa-balance-scale fa-fw"></i> Compare
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li id="video-analysis">
|
||||||
|
<a href="/rowers/workout/{{ workout.id|encode }}/video/">
|
||||||
|
<i class="fas fa-video-plus fa-fw"></i> Video Analysis
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
{% if user.is_authenticated and workout|may_edit:request %}
|
{% if user.is_authenticated and workout|may_edit:request %}
|
||||||
<li id="chart-image">
|
<li id="chart-image">
|
||||||
<a href="/rowers/workout/{{ workout.id|encode }}/image/">
|
<a href="/rowers/workout/{{ workout.id|encode }}/image/">
|
||||||
@@ -106,7 +111,7 @@
|
|||||||
<a href="/rowers/workout/{{ workout.id|encode }}/addstatic/13/">
|
<a href="/rowers/workout/{{ workout.id|encode }}/addstatic/13/">
|
||||||
<i class="far fa-chart-pie fa-fw"></i> Power (Pie)
|
<i class="far fa-chart-pie fa-fw"></i> Power (Pie)
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li id="chart-hrpie">
|
<li id="chart-hrpie">
|
||||||
<a href="/rowers/workout/{{ workout.id|encode }}/addstatic/3/">
|
<a href="/rowers/workout/{{ workout.id|encode }}/addstatic/3/">
|
||||||
<i class="fas fa-heartbeat fa-fw"></i> Heart Rate (Pie)
|
<i class="fas fa-heartbeat fa-fw"></i> Heart Rate (Pie)
|
||||||
@@ -266,7 +271,7 @@
|
|||||||
<input type="checkbox" name="group-advanced" id="group-advanced">
|
<input type="checkbox" name="group-advanced" id="group-advanced">
|
||||||
<label for="group-advanced">Advanced</label>
|
<label for="group-advanced">Advanced</label>
|
||||||
<ul>
|
<ul>
|
||||||
{% if workout|water %}
|
{% if workout|water %}
|
||||||
<li id="advanced-wind">
|
<li id="advanced-wind">
|
||||||
<a href="/rowers/workout/{{ workout.id|encode }}/wind/">
|
<a href="/rowers/workout/{{ workout.id|encode }}/wind/">
|
||||||
<i class="fas fa-pennant fa-fw"></i> Wind
|
<i class="fas fa-pennant fa-fw"></i> Wind
|
||||||
|
|||||||
@@ -2,12 +2,9 @@
|
|||||||
<li class="grid_2">
|
<li class="grid_2">
|
||||||
<div class="mapdiv">
|
<div class="mapdiv">
|
||||||
{{ mapdiv|safe }}
|
{{ mapdiv|safe }}
|
||||||
|
|
||||||
|
|
||||||
{{ mapscript|safe }}
|
{{ mapscript|safe }}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% load rowerfilters %}
|
{% load rowerfilters %}
|
||||||
|
{% load leaflet_tags %}
|
||||||
<div class="grid_2 alpha">
|
<div class="grid_2 alpha">
|
||||||
<p>
|
<p>
|
||||||
<a class="button gray small" href="/rowers/workout/{{ workout.id|encode }}/map">Map View</a>
|
<a class="button gray small" href="/rowers/workout/{{ workout.id|encode }}/map">Map View</a>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{% extends "newbase.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
|
||||||
|
{% block title %}Delete Graph Image {% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<ul class="main-content">
|
||||||
|
<li class="grid_2">
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>Are you sure you want to delete this video analysis?</p>
|
||||||
|
<p>
|
||||||
|
<input class="button red" type="submit" value="Confirm">
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
|
<a href="/rowers/video/{{ object.id|encode }}">
|
||||||
|
<img src="https://img.youtube.com/vi/{{ object.video_id }}/hqdefault.jpg"/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
{% include 'menu_workouts.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
{% extends "newbase.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
|
||||||
|
{% block title %}Workouts{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<script>
|
||||||
|
function toggle(source) {
|
||||||
|
checkboxes = document.querySelectorAll("input[name='workouts']");
|
||||||
|
for(var i=0, n=checkboxes.length;i<n;i++) {
|
||||||
|
checkboxes[i].checked = source.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(function() {
|
||||||
|
|
||||||
|
// Get the form fields and hidden div
|
||||||
|
var modality = $("#id_modality");
|
||||||
|
var hidden = $("#id_waterboattype");
|
||||||
|
|
||||||
|
|
||||||
|
// Hide the fields.
|
||||||
|
// Use JS to do this in case the user doesn't have JS
|
||||||
|
// enabled.
|
||||||
|
|
||||||
|
hidden.hide();
|
||||||
|
|
||||||
|
if (modality.val() == 'water') {
|
||||||
|
hidden.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Setup an event listener for when the state of the
|
||||||
|
// checkbox changes.
|
||||||
|
modality.change(function() {
|
||||||
|
// Check to see if the checkbox is checked.
|
||||||
|
// If it is, show the fields and populate the input.
|
||||||
|
// If not, hide the fields.
|
||||||
|
var Value = modality.val();
|
||||||
|
var otwtypes = ['water','coastal','c-boat','churchboat']
|
||||||
|
if (otwtypes.includes(Value)) {
|
||||||
|
// Show the hidden fields.
|
||||||
|
hidden.show();
|
||||||
|
} else {
|
||||||
|
// Make sure that the hidden fields are indeed
|
||||||
|
// hidden.
|
||||||
|
hidden.hide();
|
||||||
|
|
||||||
|
// You may also want to clear the value of the
|
||||||
|
// hidden fields here. Just in case somebody
|
||||||
|
// shows the fields, enters data to them and then
|
||||||
|
// unticks the checkbox.
|
||||||
|
//
|
||||||
|
// This would do the job:
|
||||||
|
//
|
||||||
|
// $("#hidden_field").val("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ul class="main-content">
|
||||||
|
<li class="grid_4">
|
||||||
|
<p>Select the workout you want to add a video to</p>
|
||||||
|
</li>
|
||||||
|
<li class="grid_2 maxheight">
|
||||||
|
<form id="searchform" action=""
|
||||||
|
method="get" accept-charset="utf-8">
|
||||||
|
{{ searchform }}
|
||||||
|
<input type="submit" value="GO"></input>
|
||||||
|
</form>
|
||||||
|
<form enctype="multipart/form-data" action="" method="post">
|
||||||
|
|
||||||
|
{% if workouts %}
|
||||||
|
|
||||||
|
<table width="100%" class="listtable">
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p> No workouts found </p>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
|
<form enctype="multipart/form-data" method="post">
|
||||||
|
<table>
|
||||||
|
{{ dateform.as_table }}
|
||||||
|
</table>
|
||||||
|
{% csrf_token %}
|
||||||
|
<input name='optionsform' type="submit" value="Submit">
|
||||||
|
<input name='workoutform' type='submit' value="Create Video">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% if request.method == 'POST' %}
|
||||||
|
<script type='text/javascript'
|
||||||
|
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
{% include 'menu_analytics.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -35,9 +35,9 @@ $('#id_workouttype').on('change', function(){
|
|||||||
|| $(this).val() == 'churchboat'
|
|| $(this).val() == 'churchboat'
|
||||||
) {
|
) {
|
||||||
$('#id_boattype').toggle(true);
|
$('#id_boattype').toggle(true);
|
||||||
} else {
|
} else {
|
||||||
$('#id_boattype').toggle(false);
|
$('#id_boattype').toggle(false);
|
||||||
$('#id_boattype').val('1x');
|
$('#id_boattype').val('1x');
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
$(this).val() == 'rower'
|
$(this).val() == 'rower'
|
||||||
@@ -45,13 +45,13 @@ $('#id_workouttype').on('change', function(){
|
|||||||
|| $(this).val() == 'slides'
|
|| $(this).val() == 'slides'
|
||||||
) {
|
) {
|
||||||
$('#id_dragfactor').toggle(true);
|
$('#id_dragfactor').toggle(true);
|
||||||
} else {
|
} else {
|
||||||
$('#id_dragfactor').toggle(false);
|
$('#id_dragfactor').toggle(false);
|
||||||
$('#id_dragfactor').val('0');
|
$('#id_dragfactor').val('0');
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
$('#id_workouttype').change();
|
$('#id_workouttype').change();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -114,7 +114,7 @@ $('#id_workouttype').change();
|
|||||||
Please correct the error{{ form.errors|pluralize }} below.
|
Please correct the error{{ form.errors|pluralize }} below.
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<form id="importantform"
|
<form id="importantform"
|
||||||
enctype="multipart/form-data" action="" method="post">
|
enctype="multipart/form-data" action="" method="post">
|
||||||
@@ -126,9 +126,9 @@ $('#id_workouttype').change();
|
|||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
<li class="grid_2">
|
<li class="grid_2">
|
||||||
|
|
||||||
<h1>Workout Summary</h1>
|
<h1>Workout Summary</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<pre>
|
<pre>
|
||||||
{{ workout.summary }}
|
{{ workout.summary }}
|
||||||
@@ -151,13 +151,13 @@ $('#id_workouttype').change();
|
|||||||
<script async="true" type="text/javascript">
|
<script async="true" type="text/javascript">
|
||||||
Bokeh.set_log_level("info");
|
Bokeh.set_log_level("info");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="mapdiv">
|
<div class="mapdiv">
|
||||||
{{ mapdiv|safe }}
|
{{ mapdiv|safe }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ mapscript|safe }}
|
{{ mapscript|safe }}
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
@@ -165,12 +165,20 @@ $('#id_workouttype').change();
|
|||||||
{% for graph in graphs %}
|
{% for graph in graphs %}
|
||||||
<li>
|
<li>
|
||||||
<a href="/rowers/graph/{{ graph.id }}/">
|
<a href="/rowers/graph/{{ graph.id }}/">
|
||||||
<img src="/{{ graph.filename }}"
|
<img src="/{{ graph.filename }}"
|
||||||
onerror="this.src='/static/img/rowingtimer.gif'"
|
onerror="this.src='/static/img/rowingtimer.gif'"
|
||||||
alt="{{ graph.filename }}" width="120" height="100">
|
alt="{{ graph.filename }}" width="120" height="100">
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% for video in videos %}
|
||||||
|
<li>
|
||||||
|
<div>{{ video.name }}</div>
|
||||||
|
<a href="/rowers/video/{{ video.id|encode }}/">
|
||||||
|
<img src="https://img.youtube.com/vi/{{ video.video_id }}/hqdefault.jpg"/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -141,6 +141,16 @@ def mocked_read_df_sql(id):
|
|||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
def mocked_get_video_data(*args, **kwargs):
|
||||||
|
with open('rowers/tests/testdata/videodata.json','r') as infile:
|
||||||
|
data = json.load(infile)
|
||||||
|
|
||||||
|
maxtime = 4135.
|
||||||
|
with open('rowers/tests/testdata/videometrics.json','r') as infile:
|
||||||
|
metrics = json.load(infile)
|
||||||
|
|
||||||
|
return data,metrics,maxtime
|
||||||
|
|
||||||
def mocked_getrowdata_db(*args, **kwargs):
|
def mocked_getrowdata_db(*args, **kwargs):
|
||||||
df = pd.read_csv('rowers/tests/testdata/getrowdata_mock.csv')
|
df = pd.read_csv('rowers/tests/testdata/getrowdata_mock.csv')
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -13,7 +13,7 @@ tested = [
|
|||||||
'/rowers/me/delete/'
|
'/rowers/me/delete/'
|
||||||
]
|
]
|
||||||
|
|
||||||
#@pytest.mark.django_db
|
#@pytest.mark.django_db
|
||||||
@override_settings(TESTING=True)
|
@override_settings(TESTING=True)
|
||||||
class URLTests(TestCase):
|
class URLTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -25,7 +25,7 @@ class URLTests(TestCase):
|
|||||||
r = Rower.objects.create(user=u,rowerplan='coach',gdproptin=True,
|
r = Rower.objects.create(user=u,rowerplan='coach',gdproptin=True,
|
||||||
gdproptindate=timezone.now())
|
gdproptindate=timezone.now())
|
||||||
self.c = Client()
|
self.c = Client()
|
||||||
|
|
||||||
self.nu = datetime.datetime.now()
|
self.nu = datetime.datetime.now()
|
||||||
filename = 'rowers/tests/testdata/testdata.csv'
|
filename = 'rowers/tests/testdata/testdata.csv'
|
||||||
self.wotw = Workout.objects.create(name='testworkout',
|
self.wotw = Workout.objects.create(name='testworkout',
|
||||||
@@ -34,20 +34,20 @@ class URLTests(TestCase):
|
|||||||
starttime=self.nu.strftime('%H:%M:%S'),
|
starttime=self.nu.strftime('%H:%M:%S'),
|
||||||
duration="0:55:00",distance=8000,
|
duration="0:55:00",distance=8000,
|
||||||
csvfilename=filename)
|
csvfilename=filename)
|
||||||
|
|
||||||
self.wote = Workout.objects.create(name='testworkout',
|
self.wote = Workout.objects.create(name='testworkout',
|
||||||
workouttype='Indoor Rower',
|
workouttype='Indoor Rower',
|
||||||
user=r,date=self.nu.strftime('%Y-%m-%d'),
|
user=r,date=self.nu.strftime('%Y-%m-%d'),
|
||||||
starttime=self.nu.strftime('%H:%M:%S'),
|
starttime=self.nu.strftime('%H:%M:%S'),
|
||||||
duration="0:55:00",distance=8000,
|
duration="0:55:00",distance=8000,
|
||||||
csvfilename=filename)
|
csvfilename=filename)
|
||||||
|
|
||||||
powerperc = 100*np.array([r.pw_ut2,
|
powerperc = 100*np.array([r.pw_ut2,
|
||||||
r.pw_ut1,
|
r.pw_ut1,
|
||||||
r.pw_at,
|
r.pw_at,
|
||||||
r.pw_tr,r.pw_an])/r.ftp
|
r.pw_tr,r.pw_an])/r.ftp
|
||||||
|
|
||||||
|
|
||||||
self.hrdata = {
|
self.hrdata = {
|
||||||
'hrmax':r.max,
|
'hrmax':r.max,
|
||||||
'hrut2':r.ut2,
|
'hrut2':r.ut2,
|
||||||
@@ -103,7 +103,7 @@ class URLTests(TestCase):
|
|||||||
'/rowers/help/',
|
'/rowers/help/',
|
||||||
'/rowers/histo/',
|
'/rowers/histo/',
|
||||||
'/rowers/histo/user/1/',
|
'/rowers/histo/user/1/',
|
||||||
# '/rowers/histo/user/1/2016-01-01/2016-12-31/',
|
# '/rowers/histo/user/1/2016-01-01/2016-12-31/',
|
||||||
'/rowers/histodata/',
|
'/rowers/histodata/',
|
||||||
# '/rowers/job-kill/1/',
|
# '/rowers/job-kill/1/',
|
||||||
# '/rowers/jobs-status/',
|
# '/rowers/jobs-status/',
|
||||||
@@ -238,7 +238,7 @@ class URLTests(TestCase):
|
|||||||
'/rowers/workout/upload/team/',
|
'/rowers/workout/upload/team/',
|
||||||
'/rowers/workouts-join/',
|
'/rowers/workouts-join/',
|
||||||
'/rowers/workouts-join-select/',
|
'/rowers/workouts-join-select/',
|
||||||
# '/rowers/workouts-join-select/2016-01-01/2016-12-31/',
|
# '/rowers/workouts-join-select/2016-01-01/2016-12-31/',
|
||||||
]
|
]
|
||||||
|
|
||||||
# urlstotest = ['/rowers/createplan/user/1/']
|
# urlstotest = ['/rowers/createplan/user/1/']
|
||||||
@@ -249,19 +249,21 @@ class URLTests(TestCase):
|
|||||||
(url,200)
|
(url,200)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@parameterized.expand(lijst)
|
@parameterized.expand(lijst)
|
||||||
@patch('rowers.dataprep.create_engine')
|
@patch('rowers.dataprep.create_engine')
|
||||||
@patch('rowers.dataprep.read_df_sql')
|
@patch('rowers.dataprep.read_df_sql')
|
||||||
@patch('rowers.dataprep.getsmallrowdata_db')
|
@patch('rowers.dataprep.getsmallrowdata_db')
|
||||||
@patch('requests.get',side_effect=mocked_requests)
|
@patch('requests.get',side_effect=mocked_requests)
|
||||||
@patch('requests.post',side_effect=mocked_requests)
|
@patch('requests.post',side_effect=mocked_requests)
|
||||||
|
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
|
||||||
def test_url_generator(self,url,expected,
|
def test_url_generator(self,url,expected,
|
||||||
mocked_sqlalchemy,
|
mocked_sqlalchemy,
|
||||||
mocked_read_df_sql,
|
mocked_read_df_sql,
|
||||||
mocked_getsmallrowdata_db,
|
mocked_getsmallrowdata_db,
|
||||||
mock_get,
|
mock_get,
|
||||||
mock_post):
|
mock_post,
|
||||||
|
mocked_get_video_data):
|
||||||
|
|
||||||
if url not in tested:
|
if url not in tested:
|
||||||
login = self.c.login(username='john',password='koeinsloot')
|
login = self.c.login(username='john',password='koeinsloot')
|
||||||
@@ -276,10 +278,10 @@ class URLTests(TestCase):
|
|||||||
|
|
||||||
html = BeautifulSoup(response.content,'html.parser')
|
html = BeautifulSoup(response.content,'html.parser')
|
||||||
|
|
||||||
|
|
||||||
urls = [a['href'] for a in html.find_all('a')]
|
urls = [a['href'] for a in html.find_all('a')]
|
||||||
|
|
||||||
|
|
||||||
for u in urls:
|
for u in urls:
|
||||||
if u not in tested and 'rowers' in u and 'http' not in u and 'authorize' not in u and 'import' not in u and 'logout' not in u:
|
if u not in tested and 'rowers' in u and 'http' not in u and 'authorize' not in u and 'import' not in u and 'logout' not in u:
|
||||||
response2 = self.c.get(u)
|
response2 = self.c.get(u)
|
||||||
@@ -294,5 +296,3 @@ class URLTests(TestCase):
|
|||||||
[200,302,301])
|
[200,302,301])
|
||||||
else:
|
else:
|
||||||
tested.append(u)
|
tested.append(u)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
|||||||
|
{"boatspeed": {"name": "Boat Speed (m/s)", "metric": "velo", "unit": ""}, "cumdist": {"name": "Cumulative Distance (m)", "metric": "cumdist", "unit": ""}, "distance": {"name": "Distance (m)", "metric": "distance", "unit": ""}, "distanceperstroke": {"name": "Distance per Stroke (m)", "metric": "distanceperstroke", "unit": ""}, "pace": {"name": "Pace (/500m)", "metric": "pace", "unit": ""}, "spm": {"name": "Stroke Rate (spm)", "metric": "spm", "unit": ""}}
|
||||||
+28
-27
@@ -48,12 +48,12 @@ def cleanbody(body):
|
|||||||
body = body.decode('utf-8')
|
body = body.decode('utf-8')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
regex = r".*---\n([\s\S]*?)\.\.\..*"
|
regex = r".*---\n([\s\S]*?)\.\.\..*"
|
||||||
matches = re.finditer(regex,body)
|
matches = re.finditer(regex,body)
|
||||||
|
|
||||||
for m in matches:
|
for m in matches:
|
||||||
|
|
||||||
if m != None:
|
if m != None:
|
||||||
body = m.group(0)
|
body = m.group(0)
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ def matchrace(line):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def matchsync(line):
|
def matchsync(line):
|
||||||
results = []
|
results = []
|
||||||
tester = '((sync)|(synchronization)|(export))'
|
tester = '((sync)|(synchronization)|(export))'
|
||||||
@@ -175,7 +175,7 @@ def gettypeoptions_body2(uploadoptions,body):
|
|||||||
break
|
break
|
||||||
|
|
||||||
return uploadoptions
|
return uploadoptions
|
||||||
|
|
||||||
def getprivateoptions_body2(uploadoptions,body):
|
def getprivateoptions_body2(uploadoptions,body):
|
||||||
tester = re.compile('^(priva)')
|
tester = re.compile('^(priva)')
|
||||||
for line in body.splitlines():
|
for line in body.splitlines():
|
||||||
@@ -206,7 +206,7 @@ def getplotoptions_body2(uploadoptions,body):
|
|||||||
if chart:
|
if chart:
|
||||||
uploadoptions['make_plot'] = True
|
uploadoptions['make_plot'] = True
|
||||||
uploadoptions['plottype'] = chart
|
uploadoptions['plottype'] = chart
|
||||||
|
|
||||||
return uploadoptions
|
return uploadoptions
|
||||||
|
|
||||||
def getuseroptions_body2(uploadoptions,body):
|
def getuseroptions_body2(uploadoptions,body):
|
||||||
@@ -236,7 +236,7 @@ def getsyncoptions_body2(uploadoptions,body):
|
|||||||
uploadoptions[r] = True
|
uploadoptions[r] = True
|
||||||
|
|
||||||
return uploadoptions
|
return uploadoptions
|
||||||
|
|
||||||
def getsyncoptions(uploadoptions,values):
|
def getsyncoptions(uploadoptions,values):
|
||||||
try:
|
try:
|
||||||
value = values.lower()
|
value = values.lower()
|
||||||
@@ -247,7 +247,7 @@ def getsyncoptions(uploadoptions,values):
|
|||||||
for v in values:
|
for v in values:
|
||||||
try:
|
try:
|
||||||
v = v.lower()
|
v = v.lower()
|
||||||
|
|
||||||
if v in ['c2','concept2','logbook']:
|
if v in ['c2','concept2','logbook']:
|
||||||
uploadoptions['upload_to_C2'] = True
|
uploadoptions['upload_to_C2'] = True
|
||||||
if v in ['tp','trainingpeaks']:
|
if v in ['tp','trainingpeaks']:
|
||||||
@@ -262,7 +262,7 @@ def getsyncoptions(uploadoptions,values):
|
|||||||
uploadoptions['upload_to_MapMyFitness'] = True
|
uploadoptions['upload_to_MapMyFitness'] = True
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return uploadoptions
|
return uploadoptions
|
||||||
|
|
||||||
def getplotoptions(uploadoptions,value):
|
def getplotoptions(uploadoptions,value):
|
||||||
@@ -437,7 +437,7 @@ def make_plot(r,w,f1,f2,plottype,title,imagename='',plotnr=0):
|
|||||||
|
|
||||||
if plotnr == 0:
|
if plotnr == 0:
|
||||||
plotnr = plotnrs[plottype]
|
plotnr = plotnrs[plottype]
|
||||||
|
|
||||||
if w.workouttype in otwtypes:
|
if w.workouttype in otwtypes:
|
||||||
plotnr = plotnr+3
|
plotnr = plotnr+3
|
||||||
|
|
||||||
@@ -445,20 +445,20 @@ def make_plot(r,w,f1,f2,plottype,title,imagename='',plotnr=0):
|
|||||||
job = myqueue(queuehigh,handle_makeplot,f1,f2,
|
job = myqueue(queuehigh,handle_makeplot,f1,f2,
|
||||||
title,hrpwrdata,
|
title,hrpwrdata,
|
||||||
plotnr,imagename)
|
plotnr,imagename)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
width,height = Image.open(fullpathimagename).size
|
width,height = Image.open(fullpathimagename).size
|
||||||
except:
|
except:
|
||||||
width = 1200
|
width = 1200
|
||||||
height = 600
|
height = 600
|
||||||
|
|
||||||
imgs = GraphImage.objects.filter(workout=w)
|
imgs = GraphImage.objects.filter(workout=w)
|
||||||
if len(imgs) < 7:
|
if len(imgs) < 7:
|
||||||
i = GraphImage(workout=w,
|
i = GraphImage(workout=w,
|
||||||
creationdatetime=timezone.now(),
|
creationdatetime=timezone.now(),
|
||||||
filename=fullpathimagename,
|
filename=fullpathimagename,
|
||||||
width=width,height=height)
|
width=width,height=height)
|
||||||
|
|
||||||
i.save()
|
i.save()
|
||||||
else:
|
else:
|
||||||
return 0,'You have reached the maximum number of static images for this workout. Delete an image first'
|
return 0,'You have reached the maximum number of static images for this workout. Delete an image first'
|
||||||
@@ -501,7 +501,7 @@ def make_private(w,options):
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
from rowers.utils import isprorower
|
from rowers.utils import isprorower
|
||||||
|
|
||||||
def do_sync(w,options):
|
def do_sync(w,options):
|
||||||
try:
|
try:
|
||||||
if options['stravaid'] != 0:
|
if options['stravaid'] != 0:
|
||||||
@@ -524,20 +524,21 @@ def do_sync(w,options):
|
|||||||
if ('upload_to_Strava' in options and not options['upload_to_Strava']):
|
if ('upload_to_Strava' in options and not options['upload_to_Strava']):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
try:
|
if options['stravaid'] != 0:
|
||||||
message,id = stravastuff.workout_strava_upload(
|
try:
|
||||||
|
message,id = stravastuff.workout_strava_upload(
|
||||||
w.user.user,w
|
w.user.user,w
|
||||||
)
|
)
|
||||||
except NoTokenError:
|
except NoTokenError:
|
||||||
id = 0
|
id = 0
|
||||||
message = "Please connect to Strava first"
|
message = "Please connect to Strava first"
|
||||||
|
|
||||||
|
|
||||||
if ('upload_to_SportTracks' in options and options['upload_to_SportTracks']) or (w.user.sporttracks_auto_export and isprorower(w.user)):
|
if ('upload_to_SportTracks' in options and options['upload_to_SportTracks']) or (w.user.sporttracks_auto_export and isprorower(w.user)):
|
||||||
if ('upload_to_SportTracks' in options and not options['upload_to_SportTracks']):
|
if ('upload_to_SportTracks' in options and not options['upload_to_SportTracks']):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message,id = sporttracksstuff.workout_sporttracks_upload(
|
message,id = sporttracksstuff.workout_sporttracks_upload(
|
||||||
w.user.user,w
|
w.user.user,w
|
||||||
@@ -545,13 +546,13 @@ def do_sync(w,options):
|
|||||||
except NoTokenError:
|
except NoTokenError:
|
||||||
message = "Please connect to SportTracks first"
|
message = "Please connect to SportTracks first"
|
||||||
id = 0
|
id = 0
|
||||||
|
|
||||||
|
|
||||||
if ('upload_to_RunKeeper' in options and options['upload_to_RunKeeper']) or (w.user.runkeeper_auto_export and isprorower(w.user)):
|
if ('upload_to_RunKeeper' in options and options['upload_to_RunKeeper']) or (w.user.runkeeper_auto_export and isprorower(w.user)):
|
||||||
if ('upload_to_RunKeeper' in options and not options['upload_to_RunKeeper']):
|
if ('upload_to_RunKeeper' in options and not options['upload_to_RunKeeper']):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message,id = runkeeperstuff.workout_runkeeper_upload(
|
message,id = runkeeperstuff.workout_runkeeper_upload(
|
||||||
w.user.user,w
|
w.user.user,w
|
||||||
@@ -559,11 +560,11 @@ def do_sync(w,options):
|
|||||||
except NoTokenError:
|
except NoTokenError:
|
||||||
message = "Please connect to Runkeeper first"
|
message = "Please connect to Runkeeper first"
|
||||||
id = 0
|
id = 0
|
||||||
|
|
||||||
if ('upload_to_MapMyFitness' in options and options['upload_to_MapMyFitness']) or (w.user.mapmyfitness_auto_export and isprorower(w.user)):
|
if ('upload_to_MapMyFitness' in options and options['upload_to_MapMyFitness']) or (w.user.mapmyfitness_auto_export and isprorower(w.user)):
|
||||||
if ('upload_to_MapMyFitness' in options and not options['upload_to_MapMyFitness']):
|
if ('upload_to_MapMyFitness' in options and not options['upload_to_MapMyFitness']):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
message,id = underarmourstuff.workout_ua_upload(
|
message,id = underarmourstuff.workout_ua_upload(
|
||||||
w.user.user,w
|
w.user.user,w
|
||||||
@@ -572,7 +573,7 @@ def do_sync(w,options):
|
|||||||
message = "Please connect to MapMyFitness first"
|
message = "Please connect to MapMyFitness first"
|
||||||
id = 0
|
id = 0
|
||||||
|
|
||||||
|
|
||||||
if ('upload_to_TrainingPeaks' in options and options['upload_to_TrainingPeaks']) or (w.user.trainingpeaks_auto_export and isprorower(w.user)):
|
if ('upload_to_TrainingPeaks' in options and options['upload_to_TrainingPeaks']) or (w.user.trainingpeaks_auto_export and isprorower(w.user)):
|
||||||
if ('upload_to_TrainingPeaks' in options and not options['upload_to_TrainingPeaks']):
|
if ('upload_to_TrainingPeaks' in options and not options['upload_to_TrainingPeaks']):
|
||||||
pass
|
pass
|
||||||
|
|||||||
+14
-8
@@ -45,8 +45,8 @@ class WorkoutViewSet(viewsets.ModelViewSet):
|
|||||||
except TypeError:
|
except TypeError:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
permission_classes = (
|
permission_classes = (
|
||||||
#DjangoModelPermissions,
|
#DjangoModelPermissions,
|
||||||
IsOwnerOrNot,
|
IsOwnerOrNot,
|
||||||
@@ -56,7 +56,7 @@ class RowerViewSet(viewsets.ModelViewSet):
|
|||||||
model = Rower
|
model = Rower
|
||||||
serializer_class = RowerSerializer
|
serializer_class = RowerSerializer
|
||||||
#queryset = Rower.objects.all()
|
#queryset = Rower.objects.all()
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
try:
|
try:
|
||||||
r = Rower.objects.filter(user=self.request.user)
|
r = Rower.objects.filter(user=self.request.user)
|
||||||
@@ -70,7 +70,7 @@ class RowerViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
http_method_names = ['get','patch']
|
http_method_names = ['get','patch']
|
||||||
|
|
||||||
|
|
||||||
class FavoriteChartViewSet(viewsets.ModelViewSet):
|
class FavoriteChartViewSet(viewsets.ModelViewSet):
|
||||||
model = FavoriteChart
|
model = FavoriteChart
|
||||||
serializer_class = FavoriteChartSerializer
|
serializer_class = FavoriteChartSerializer
|
||||||
@@ -88,10 +88,10 @@ class FavoriteChartViewSet(viewsets.ModelViewSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
http_method_names = ['get','put','patch','delete']
|
http_method_names = ['get','put','patch','delete']
|
||||||
|
|
||||||
class StrokeDataViewSet(viewsets.ModelViewSet):
|
class StrokeDataViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = StrokeDataSerializer
|
serializer_class = StrokeDataSerializer
|
||||||
|
|
||||||
# Routers provide an easy way of automatically determining the URL conf.
|
# Routers provide an easy way of automatically determining the URL conf.
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.register(r'api/workouts',WorkoutViewSet, 'workout')
|
router.register(r'api/workouts',WorkoutViewSet, 'workout')
|
||||||
@@ -104,7 +104,6 @@ def permissiondenied_view(request):
|
|||||||
|
|
||||||
|
|
||||||
def filenotfound_view(request):
|
def filenotfound_view(request):
|
||||||
print('aapje')
|
|
||||||
return rowers.views.error403_view(request)
|
return rowers.views.error403_view(request)
|
||||||
|
|
||||||
def response_error_handler(request, exception=None):
|
def response_error_handler(request, exception=None):
|
||||||
@@ -344,6 +343,13 @@ urlpatterns = [
|
|||||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/split/$',views.workout_split_view,name='workout_split_view'),
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/split/$',views.workout_split_view,name='workout_split_view'),
|
||||||
# re_path(r'^workout/(?P<id>\d+)/interactiveplot/$',views.workout_biginteractive_view),
|
# re_path(r'^workout/(?P<id>\d+)/interactiveplot/$',views.workout_biginteractive_view),
|
||||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/view/$',views.workout_view,name='workout_view'),
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/view/$',views.workout_view,name='workout_view'),
|
||||||
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/video/$',views.workout_video_create_view,
|
||||||
|
name='workout_video_create_view'),
|
||||||
|
re_path(r'^video/(?P<pk>\d+)/delete/$',views.VideoDelete.as_view(),name='video_delete'),
|
||||||
|
re_path(r'^video/(?P<id>\w.+)/$',views.workout_video_view,
|
||||||
|
name='workout_video_view'),
|
||||||
|
re_path(r'^videos/',views.list_videos,name='list_videos'),
|
||||||
|
re_path(r'^add-video/',views.video_selectworkout,name='video_selectworkout'),
|
||||||
# re_path(r'^workout/(?P<id>\d+)/$',views.workout_view,name='workout_view'),
|
# re_path(r'^workout/(?P<id>\d+)/$',views.workout_view,name='workout_view'),
|
||||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/$',views.workout_view,name='workout_view'),
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/$',views.workout_view,name='workout_view'),
|
||||||
re_path(r'^workout/fusion/(?P<id1>\b[0-9A-Fa-f]+\b)/(?P<id2>\b[0-9A-Fa-f]+\b)/$',views.workout_fusion_view,name='workout_fusion_view'),
|
re_path(r'^workout/fusion/(?P<id1>\b[0-9A-Fa-f]+\b)/(?P<id2>\b[0-9A-Fa-f]+\b)/$',views.workout_fusion_view,name='workout_fusion_view'),
|
||||||
@@ -475,7 +481,7 @@ urlpatterns = [
|
|||||||
re_path(r'^me/coachrequest/(?P<code>\w+.*)/accept/$',views.coach_accept_coachrequest_view,
|
re_path(r'^me/coachrequest/(?P<code>\w+.*)/accept/$',views.coach_accept_coachrequest_view,
|
||||||
name='coach_accept_coachrequest_view'),
|
name='coach_accept_coachrequest_view'),
|
||||||
re_path(r'^me/coachoffer/(?P<code>\w+.*)/accept/$',views.rower_accept_coachoffer_view,
|
re_path(r'^me/coachoffer/(?P<code>\w+.*)/accept/$',views.rower_accept_coachoffer_view,
|
||||||
name='rower_accept_coachoffer_view'),
|
name='rower_accept_coachoffer_view'),
|
||||||
re_path(r'^team/(?P<id>\d+)/delete/$',views.team_delete_view,name='team_delete_view'),
|
re_path(r'^team/(?P<id>\d+)/delete/$',views.team_delete_view,name='team_delete_view'),
|
||||||
re_path(r'^team/create/$',views.team_create_view,name='team_create_view'),
|
re_path(r'^team/create/$',views.team_create_view,name='team_create_view'),
|
||||||
re_path(r'^me/team/(?P<teamid>\d+)/drop/(?P<userid>\d+)/$',views.manager_member_drop_view,name='manager_member_drop_view'),
|
re_path(r'^me/team/(?P<teamid>\d+)/drop/(?P<userid>\d+)/$',views.manager_member_drop_view,name='manager_member_drop_view'),
|
||||||
|
|||||||
+68
-67
@@ -58,7 +58,9 @@ from rowers.forms import (
|
|||||||
RaceResultFilterForm,PowerIntervalUpdateForm,FlexAxesForm,
|
RaceResultFilterForm,PowerIntervalUpdateForm,FlexAxesForm,
|
||||||
FlexOptionsForm,DataFrameColumnsForm,OteWorkoutTypeForm,
|
FlexOptionsForm,DataFrameColumnsForm,OteWorkoutTypeForm,
|
||||||
MetricsForm,DisqualificationForm,disqualificationreasons,
|
MetricsForm,DisqualificationForm,disqualificationreasons,
|
||||||
disqualifiers,SearchForm,BillingForm,PlanSelectForm
|
disqualifiers,SearchForm,BillingForm,PlanSelectForm,
|
||||||
|
VideoAnalysisCreateForm,WorkoutSingleSelectForm,
|
||||||
|
VideoAnalysisMetricsForm,
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
@@ -80,7 +82,7 @@ from rowers.forms import (
|
|||||||
UpdateStreamForm,WorkoutMultipleCompareForm,ChartParamChoiceForm,
|
UpdateStreamForm,WorkoutMultipleCompareForm,ChartParamChoiceForm,
|
||||||
FusionMetricChoiceForm,BoxPlotChoiceForm,MultiFlexChoiceForm,
|
FusionMetricChoiceForm,BoxPlotChoiceForm,MultiFlexChoiceForm,
|
||||||
TrendFlexModalForm,WorkoutSplitForm,WorkoutJoinParamForm,
|
TrendFlexModalForm,WorkoutSplitForm,WorkoutJoinParamForm,
|
||||||
AnalysisOptionsForm, AnalysisChoiceForm,
|
AnalysisOptionsForm, AnalysisChoiceForm,
|
||||||
PlannedSessionMultipleCloneForm,SessionDateShiftForm,RowerTeamForm,
|
PlannedSessionMultipleCloneForm,SessionDateShiftForm,RowerTeamForm,
|
||||||
)
|
)
|
||||||
from rowers.models import (
|
from rowers.models import (
|
||||||
@@ -95,7 +97,8 @@ from rowers.models import (
|
|||||||
TrainingMesoCycleForm, TrainingMicroCycleForm,
|
TrainingMesoCycleForm, TrainingMicroCycleForm,
|
||||||
RaceLogo,RowerBillingAddressForm,PaidPlan,
|
RaceLogo,RowerBillingAddressForm,PaidPlan,
|
||||||
AlertEditForm, ConditionEditForm,
|
AlertEditForm, ConditionEditForm,
|
||||||
PlannedSessionComment,CoachRequest,CoachOffer,checkaccessplanuser
|
PlannedSessionComment,CoachRequest,CoachOffer,checkaccessplanuser,
|
||||||
|
VideoAnalysis
|
||||||
)
|
)
|
||||||
from rowers.models import (
|
from rowers.models import (
|
||||||
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm,
|
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm,
|
||||||
@@ -115,7 +118,7 @@ from rowers.models import (
|
|||||||
FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet,
|
FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet,
|
||||||
get_course_timezone,BaseConditionFormSet,
|
get_course_timezone,BaseConditionFormSet,
|
||||||
)
|
)
|
||||||
from rowers.metrics import rowingmetrics,defaultfavoritecharts,nometrics
|
from rowers.metrics import rowingmetrics,defaultfavoritecharts,nometrics,metricsgroups
|
||||||
from rowers import metrics as metrics
|
from rowers import metrics as metrics
|
||||||
from rowers import courses as courses
|
from rowers import courses as courses
|
||||||
import rowers.uploads as uploads
|
import rowers.uploads as uploads
|
||||||
@@ -255,7 +258,7 @@ def getfavorites(r,row):
|
|||||||
workoutsource = row.workoutsource
|
workoutsource = row.workoutsource
|
||||||
if 'speedcoach2' in row.workoutsource:
|
if 'speedcoach2' in row.workoutsource:
|
||||||
workoutsource = 'speedcoach2'
|
workoutsource = 'speedcoach2'
|
||||||
|
|
||||||
favorites = FavoriteChart.objects.filter(user=r,
|
favorites = FavoriteChart.objects.filter(user=r,
|
||||||
workouttype__in=matchworkouttypes).order_by("id")
|
workouttype__in=matchworkouttypes).order_by("id")
|
||||||
favorites2 = FavoriteChart.objects.filter(user=r,
|
favorites2 = FavoriteChart.objects.filter(user=r,
|
||||||
@@ -264,12 +267,12 @@ def getfavorites(r,row):
|
|||||||
|
|
||||||
|
|
||||||
favorites = favorites | favorites2
|
favorites = favorites | favorites2
|
||||||
|
|
||||||
maxfav = len(favorites)-1
|
maxfav = len(favorites)-1
|
||||||
|
|
||||||
|
|
||||||
return favorites,maxfav
|
return favorites,maxfav
|
||||||
|
|
||||||
def get_workout_default_page(request,id):
|
def get_workout_default_page(request,id):
|
||||||
if request.user.is_anonymous:
|
if request.user.is_anonymous:
|
||||||
return reverse('workout_view',kwargs={'id':id})
|
return reverse('workout_view',kwargs={'id':id})
|
||||||
@@ -279,7 +282,7 @@ def get_workout_default_page(request,id):
|
|||||||
return reverse('workout_edit_view',kwargs={'id':id})
|
return reverse('workout_edit_view',kwargs={'id':id})
|
||||||
else:
|
else:
|
||||||
return reverse('workout_workflow_view',kwargs={'id':id})
|
return reverse('workout_workflow_view',kwargs={'id':id})
|
||||||
|
|
||||||
def getrequestrower(request,rowerid=0,userid=0,notpermanent=False):
|
def getrequestrower(request,rowerid=0,userid=0,notpermanent=False):
|
||||||
|
|
||||||
userid = int(userid)
|
userid = int(userid)
|
||||||
@@ -288,12 +291,12 @@ def getrequestrower(request,rowerid=0,userid=0,notpermanent=False):
|
|||||||
if notpermanent == False:
|
if notpermanent == False:
|
||||||
if rowerid == 0 and 'rowerid' in request.session:
|
if rowerid == 0 and 'rowerid' in request.session:
|
||||||
rowerid = request.session['rowerid']
|
rowerid = request.session['rowerid']
|
||||||
|
|
||||||
if userid != 0:
|
if userid != 0:
|
||||||
rowerid = 0
|
rowerid = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
if rowerid != 0:
|
if rowerid != 0:
|
||||||
r = Rower.objects.get(id=rowerid)
|
r = Rower.objects.get(id=rowerid)
|
||||||
elif userid != 0:
|
elif userid != 0:
|
||||||
@@ -301,7 +304,7 @@ def getrequestrower(request,rowerid=0,userid=0,notpermanent=False):
|
|||||||
r = getrower(u)
|
r = getrower(u)
|
||||||
else:
|
else:
|
||||||
r = getrower(request.user)
|
r = getrower(request.user)
|
||||||
|
|
||||||
except Rower.DoesNotExist:
|
except Rower.DoesNotExist:
|
||||||
raise Http404("Rower doesn't exist")
|
raise Http404("Rower doesn't exist")
|
||||||
|
|
||||||
@@ -321,12 +324,12 @@ def getrequestplanrower(request,rowerid=0,userid=0,notpermanent=False):
|
|||||||
if notpermanent == False:
|
if notpermanent == False:
|
||||||
if rowerid == 0 and 'rowerid' in request.session:
|
if rowerid == 0 and 'rowerid' in request.session:
|
||||||
rowerid = request.session['rowerid']
|
rowerid = request.session['rowerid']
|
||||||
|
|
||||||
if userid != 0:
|
if userid != 0:
|
||||||
rowerid = 0
|
rowerid = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
if rowerid != 0:
|
if rowerid != 0:
|
||||||
r = Rower.objects.get(id=rowerid)
|
r = Rower.objects.get(id=rowerid)
|
||||||
elif userid != 0:
|
elif userid != 0:
|
||||||
@@ -334,7 +337,7 @@ def getrequestplanrower(request,rowerid=0,userid=0,notpermanent=False):
|
|||||||
r = getrower(u)
|
r = getrower(u)
|
||||||
else:
|
else:
|
||||||
r = getrower(request.user)
|
r = getrower(request.user)
|
||||||
|
|
||||||
except Rower.DoesNotExist:
|
except Rower.DoesNotExist:
|
||||||
raise Http404("Rower doesn't exist")
|
raise Http404("Rower doesn't exist")
|
||||||
|
|
||||||
@@ -374,20 +377,20 @@ def get_workout(id):
|
|||||||
|
|
||||||
def get_workout_permitted(user,id):
|
def get_workout_permitted(user,id):
|
||||||
w = get_workout(id)
|
w = get_workout(id)
|
||||||
|
|
||||||
if (checkworkoutuser(user,w)==False):
|
if (checkworkoutuser(user,w)==False):
|
||||||
raise PermissionDenied("Access denied")
|
raise PermissionDenied("Access denied")
|
||||||
|
|
||||||
return w
|
return w
|
||||||
|
|
||||||
def get_workout_permittedview(user,id):
|
def get_workout_permittedview(user,id):
|
||||||
w = get_workout(id)
|
w = get_workout(id)
|
||||||
|
|
||||||
if (checkworkoutuserview(user,w)==False):
|
if (checkworkoutuserview(user,w)==False):
|
||||||
raise PermissionDenied("Access denied")
|
raise PermissionDenied("Access denied")
|
||||||
|
|
||||||
return w
|
return w
|
||||||
|
|
||||||
def getvalue(data):
|
def getvalue(data):
|
||||||
perc = 0
|
perc = 0
|
||||||
total = 1
|
total = 1
|
||||||
@@ -412,7 +415,7 @@ class SessionTaskListener(threading.Thread):
|
|||||||
self.redis = r
|
self.redis = r
|
||||||
self.pubsub = self.redis.pubsub()
|
self.pubsub = self.redis.pubsub()
|
||||||
self.pubsub.subscribe(channels)
|
self.pubsub.subscribe(channels)
|
||||||
|
|
||||||
def work(self, item):
|
def work(self, item):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -420,10 +423,10 @@ class SessionTaskListener(threading.Thread):
|
|||||||
total,done,id,session_key = getvalue(data)
|
total,done,id,session_key = getvalue(data)
|
||||||
perc = int(100.*done/total)
|
perc = int(100.*done/total)
|
||||||
cache.set(id,perc,3600)
|
cache.set(id,perc,3600)
|
||||||
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
for item in self.pubsub.listen():
|
for item in self.pubsub.listen():
|
||||||
if item['data'] == "KILL":
|
if item['data'] == "KILL":
|
||||||
@@ -497,14 +500,14 @@ def remove_asynctask(request,id):
|
|||||||
oldtasks = request.session['async_tasks']
|
oldtasks = request.session['async_tasks']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
oldtasks = []
|
oldtasks = []
|
||||||
|
|
||||||
newtasks = []
|
newtasks = []
|
||||||
for task in oldtasks:
|
for task in oldtasks:
|
||||||
if id not in task[0]:
|
if id not in task[0]:
|
||||||
newtasks += [(task[0],task[1])]
|
newtasks += [(task[0],task[1])]
|
||||||
|
|
||||||
request.session['async_tasks'] = newtasks
|
request.session['async_tasks'] = newtasks
|
||||||
|
|
||||||
def get_job_result(jobid):
|
def get_job_result(jobid):
|
||||||
if settings.TESTING:
|
if settings.TESTING:
|
||||||
return None
|
return None
|
||||||
@@ -522,7 +525,7 @@ def get_job_result(jobid):
|
|||||||
result = job.result
|
result = job.result
|
||||||
except NoSuchJobError:
|
except NoSuchJobError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
verbose_job_status = {
|
verbose_job_status = {
|
||||||
@@ -551,7 +554,7 @@ def get_job_status(jobid):
|
|||||||
job = celery_result.AsyncResult(jobid)
|
job = celery_result.AsyncResult(jobid)
|
||||||
jobresult = job.result
|
jobresult = job.result
|
||||||
|
|
||||||
|
|
||||||
if 'fail' in job.status.lower():
|
if 'fail' in job.status.lower():
|
||||||
jobresult = '0'
|
jobresult = '0'
|
||||||
summary = {
|
summary = {
|
||||||
@@ -566,7 +569,7 @@ def get_job_status(jobid):
|
|||||||
status = job.status
|
status = job.status
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
status = job.get_status()
|
status = job.get_status()
|
||||||
|
|
||||||
summary = {
|
summary = {
|
||||||
'status':status,
|
'status':status,
|
||||||
'result':job.result,
|
'result':job.result,
|
||||||
@@ -611,7 +614,7 @@ def kill_async_job(request,id='aap'):
|
|||||||
cancel_job(id,connection=redis_connection)
|
cancel_job(id,connection=redis_connection)
|
||||||
except NoSuchJobError:
|
except NoSuchJobError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
remove_asynctask(request,id)
|
remove_asynctask(request,id)
|
||||||
cache.delete(id)
|
cache.delete(id)
|
||||||
url = reverse(session_jobs_status)
|
url = reverse(session_jobs_status)
|
||||||
@@ -637,7 +640,7 @@ def raise_500(request):
|
|||||||
# try:
|
# try:
|
||||||
# request.session['async_tasks'] += [(job.id,'long_test_task')]
|
# request.session['async_tasks'] += [(job.id,'long_test_task')]
|
||||||
# except KeyError:
|
# except KeyError:
|
||||||
# request.session['async_tasks'] = [(job.id,'long_test_task')]
|
# request.session['async_tasks'] = [(job.id,'long_test_task')]
|
||||||
#
|
#
|
||||||
# url = reverse(session_jobs_status)
|
# url = reverse(session_jobs_status)
|
||||||
#
|
#
|
||||||
@@ -654,7 +657,7 @@ def raise_500(request):
|
|||||||
# try:
|
# try:
|
||||||
# request.session['async_tasks'] += [(job.id,'long_test_task2')]
|
# request.session['async_tasks'] += [(job.id,'long_test_task2')]
|
||||||
# except KeyError:
|
# except KeyError:
|
||||||
# request.session['async_tasks'] = [(job.id,'long_test_task2')]
|
# request.session['async_tasks'] = [(job.id,'long_test_task2')]
|
||||||
#
|
#
|
||||||
# url = reverse(session_jobs_status)
|
# url = reverse(session_jobs_status)
|
||||||
#
|
#
|
||||||
@@ -689,7 +692,7 @@ def post_progress(request,id=None,value=0):
|
|||||||
|
|
||||||
else: # request method is not POST
|
else: # request method is not POST
|
||||||
return HttpResponse('GET method not allowed',status=405)
|
return HttpResponse('GET method not allowed',status=405)
|
||||||
|
|
||||||
def get_all_queued_jobs(userid=0):
|
def get_all_queued_jobs(userid=0):
|
||||||
r = StrictRedis()
|
r = StrictRedis()
|
||||||
|
|
||||||
@@ -709,13 +712,13 @@ def get_all_queued_jobs(userid=0):
|
|||||||
'function':'',
|
'function':'',
|
||||||
'meta':job.info,
|
'meta':job.info,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
ids = [j.id for j in queue.jobs]
|
ids = [j.id for j in queue.jobs]
|
||||||
ids += [j.id for j in queuehigh.jobs]
|
ids += [j.id for j in queuehigh.jobs]
|
||||||
ids += [j.id for j in queuelow.jobs]
|
ids += [j.id for j in queuelow.jobs]
|
||||||
ids += [j.id for j in queuefailed.jobs]
|
ids += [j.id for j in queuefailed.jobs]
|
||||||
|
|
||||||
|
|
||||||
for id in ids:
|
for id in ids:
|
||||||
job = Job.fetch(id,connection=redis_connection)
|
job = Job.fetch(id,connection=redis_connection)
|
||||||
jobs.append(
|
jobs.append(
|
||||||
@@ -766,14 +769,14 @@ def get_stored_tasks_status(request):
|
|||||||
|
|
||||||
taskstatus.append(this_task_status)
|
taskstatus.append(this_task_status)
|
||||||
|
|
||||||
|
|
||||||
return taskstatus
|
return taskstatus
|
||||||
|
|
||||||
@login_required()
|
@login_required()
|
||||||
def get_thumbnails(request,id):
|
def get_thumbnails(request,id):
|
||||||
row = get_workout_permitted(request.user,id)
|
row = get_workout_permitted(request.user,id)
|
||||||
|
|
||||||
|
|
||||||
r = getrower(request.user)
|
r = getrower(request.user)
|
||||||
result = request.user.is_authenticated and ispromember(request.user)
|
result = request.user.is_authenticated and ispromember(request.user)
|
||||||
if result:
|
if result:
|
||||||
@@ -788,15 +791,15 @@ def get_thumbnails(request,id):
|
|||||||
favorites,maxfav = getfavorites(r,row)
|
favorites,maxfav = getfavorites(r,row)
|
||||||
|
|
||||||
charts = []
|
charts = []
|
||||||
|
|
||||||
charts = thumbnails_set(r,encoder.decode_hex(id),favorites)
|
charts = thumbnails_set(r,encoder.decode_hex(id),favorites)
|
||||||
try:
|
try:
|
||||||
if charts[0]['script'] == '':
|
if charts[0]['script'] == '':
|
||||||
charts = []
|
charts = []
|
||||||
except IndexError:
|
except IndexError:
|
||||||
charts = []
|
charts = []
|
||||||
|
|
||||||
|
|
||||||
return JSONResponse(charts)
|
return JSONResponse(charts)
|
||||||
|
|
||||||
def get_blog_posts(request):
|
def get_blog_posts(request):
|
||||||
@@ -810,7 +813,7 @@ def get_blog_posts(request):
|
|||||||
'title':blogpost.title,
|
'title':blogpost.title,
|
||||||
'link':blogpost.link,
|
'link':blogpost.link,
|
||||||
}
|
}
|
||||||
|
|
||||||
jsondata.append(thedict)
|
jsondata.append(thedict)
|
||||||
|
|
||||||
return JSONResponse(jsondata)
|
return JSONResponse(jsondata)
|
||||||
@@ -827,12 +830,12 @@ def get_blog_posts_old(request):
|
|||||||
blogs_json = []
|
blogs_json = []
|
||||||
except ConnectionError:
|
except ConnectionError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
blogposts = []
|
blogposts = []
|
||||||
|
|
||||||
|
|
||||||
for postdata in blogs_json[0:3]:
|
for postdata in blogs_json[0:3]:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
title = postdata['title']['rendered'].encode(
|
title = postdata['title']['rendered'].encode(
|
||||||
'ascii','xmlcharrefreplace')
|
'ascii','xmlcharrefreplace')
|
||||||
@@ -841,7 +844,7 @@ def get_blog_posts_old(request):
|
|||||||
title = postdata['title']['rendered'].encode(
|
title = postdata['title']['rendered'].encode(
|
||||||
'ascii','xmlcharrefreplace').decode('utf-8')
|
'ascii','xmlcharrefreplace').decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
thedict = {
|
thedict = {
|
||||||
'title': title,
|
'title': title,
|
||||||
# 'author': '',
|
# 'author': '',
|
||||||
@@ -874,7 +877,7 @@ Hoi
|
|||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return JSONResponse([object,object])
|
return JSONResponse([object,object])
|
||||||
|
|
||||||
@login_required()
|
@login_required()
|
||||||
@@ -935,7 +938,7 @@ def get_my_teams(user):
|
|||||||
teams1 = therower.team.all()
|
teams1 = therower.team.all()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
teams1 = []
|
teams1 = []
|
||||||
|
|
||||||
teams2 = Team.objects.filter(manager=user)
|
teams2 = Team.objects.filter(manager=user)
|
||||||
teams = list(set(teams1).union(set(teams2)))
|
teams = list(set(teams1).union(set(teams2)))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
@@ -960,10 +963,10 @@ def get_time(second):
|
|||||||
hours = int((second-24.*3600.*days)/3600.) % 24
|
hours = int((second-24.*3600.*days)/3600.) % 24
|
||||||
minutes = int((second-3600.*(hours+24.*days))/60.) % 60
|
minutes = int((second-3600.*(hours+24.*days))/60.) % 60
|
||||||
sec = int(second-3600.*(hours+24.*days)-60.*minutes) % 60
|
sec = int(second-3600.*(hours+24.*days)-60.*minutes) % 60
|
||||||
microsecond = int(1.0e6*(second-3600.*(hours+24.*days)-60.*minutes-sec))
|
microsecond = int(1.0e6*(second-3600.*(hours+24.*days)-60.*minutes-sec))
|
||||||
return datetime.time(hours,minutes,sec,microsecond)
|
return datetime.time(hours,minutes,sec,microsecond)
|
||||||
|
|
||||||
|
|
||||||
# get the workout ID from the SportTracks URI
|
# get the workout ID from the SportTracks URI
|
||||||
def getidfromsturi(uri,length=8):
|
def getidfromsturi(uri,length=8):
|
||||||
return uri[len(uri)-length:]
|
return uri[len(uri)-length:]
|
||||||
@@ -975,7 +978,7 @@ def getidfromuri(uri):
|
|||||||
return m.group(2)
|
return m.group(2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from rowers.utils import (
|
from rowers.utils import (
|
||||||
geo_distance,serialize_list,deserialize_list,uniqify,
|
geo_distance,serialize_list,deserialize_list,uniqify,
|
||||||
str2bool,range_to_color_hex,absolute,myqueue,get_call,
|
str2bool,range_to_color_hex,absolute,myqueue,get_call,
|
||||||
@@ -997,11 +1000,11 @@ def iscoachmember(user):
|
|||||||
except Rower.DoesNotExist:
|
except Rower.DoesNotExist:
|
||||||
r = Rower(user=user)
|
r = Rower(user=user)
|
||||||
r.save()
|
r.save()
|
||||||
|
|
||||||
result = user.is_authenticated and ('coach' in r.rowerplan)
|
result = user.is_authenticated and ('coach' in r.rowerplan)
|
||||||
else:
|
else:
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def cancreateteam(user):
|
def cancreateteam(user):
|
||||||
@@ -1092,7 +1095,7 @@ def sendmail(request):
|
|||||||
messages.error(request,'You have to answer YES to the question')
|
messages.error(request,'You have to answer YES to the question')
|
||||||
return HttpResponseRedirect('/rowers/email/')
|
return HttpResponseRedirect('/rowers/email/')
|
||||||
else:
|
else:
|
||||||
return HttpResponseRedirect('/rowers/email/')
|
return HttpResponseRedirect('/rowers/email/')
|
||||||
else:
|
else:
|
||||||
return HttpResponseRedirect('/rowers/email/')
|
return HttpResponseRedirect('/rowers/email/')
|
||||||
|
|
||||||
@@ -1106,7 +1109,7 @@ def add_workout_from_strokedata(user,importid,data,strokedata,
|
|||||||
workouttype = data['type']
|
workouttype = data['type']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
workouttype = 'rower'
|
workouttype = 'rower'
|
||||||
|
|
||||||
if workouttype not in [x[0] for x in Workout.workouttypes]:
|
if workouttype not in [x[0] for x in Workout.workouttypes]:
|
||||||
workouttype = 'other'
|
workouttype = 'other'
|
||||||
try:
|
try:
|
||||||
@@ -1128,14 +1131,14 @@ def add_workout_from_strokedata(user,importid,data,strokedata,
|
|||||||
rowdatetime = iso8601.parse_date(data['start_date'])
|
rowdatetime = iso8601.parse_date(data['start_date'])
|
||||||
except ParseError:
|
except ParseError:
|
||||||
rowdatetime = iso8601.parse_date(data['date'])
|
rowdatetime = iso8601.parse_date(data['date'])
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
c2intervaltype = data['workout_type']
|
c2intervaltype = data['workout_type']
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
c2intervaltype = ''
|
c2intervaltype = ''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
title = data['name']
|
title = data['name']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -1177,7 +1180,7 @@ def add_workout_from_strokedata(user,importid,data,strokedata,
|
|||||||
spm = strokedata.loc[:,'spm']
|
spm = strokedata.loc[:,'spm']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
spm = 0*dist2
|
spm = 0*dist2
|
||||||
|
|
||||||
try:
|
try:
|
||||||
hr = strokedata.loc[:,'hr']
|
hr = strokedata.loc[:,'hr']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -1189,7 +1192,7 @@ def add_workout_from_strokedata(user,importid,data,strokedata,
|
|||||||
velo = 500./pace
|
velo = 500./pace
|
||||||
|
|
||||||
power = 2.8*velo**3
|
power = 2.8*velo**3
|
||||||
|
|
||||||
# save csv
|
# save csv
|
||||||
# Create data frame with all necessary data to write to csv
|
# Create data frame with all necessary data to write to csv
|
||||||
df = pd.DataFrame({'TimeStamp (sec)':unixtime,
|
df = pd.DataFrame({'TimeStamp (sec)':unixtime,
|
||||||
@@ -1211,9 +1214,9 @@ def add_workout_from_strokedata(user,importid,data,strokedata,
|
|||||||
' ElapsedTime (sec)':seconds
|
' ElapsedTime (sec)':seconds
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
df.sort_values(by='TimeStamp (sec)',ascending=True)
|
df.sort_values(by='TimeStamp (sec)',ascending=True)
|
||||||
|
|
||||||
timestr = strftime("%Y%m%d-%H%M%S")
|
timestr = strftime("%Y%m%d-%H%M%S")
|
||||||
|
|
||||||
|
|
||||||
@@ -1249,11 +1252,11 @@ def add_workout_from_strokedata(user,importid,data,strokedata,
|
|||||||
dosummary=True
|
dosummary=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return id,message
|
return id,message
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def keyvalue_get_default(key,options,def_options):
|
def keyvalue_get_default(key,options,def_options):
|
||||||
@@ -1288,5 +1291,3 @@ def trydf(df,aantal,column):
|
|||||||
|
|
||||||
import rowers.teams as teams
|
import rowers.teams as teams
|
||||||
from rowers.models import C2WorldClassAgePerformance
|
from rowers.models import C2WorldClassAgePerformance
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,267 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from rowers.views.statements import *
|
from rowers.views.statements import *
|
||||||
import rowers.teams as teams
|
import rowers.teams as teams
|
||||||
|
import rowers.mytypes as mytypes
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
|
||||||
|
def default(o):
|
||||||
|
if isinstance(o, numpy.int64): return int(o)
|
||||||
|
if isinstance(o, numpy.int32): return int(o)
|
||||||
|
raise TypeError
|
||||||
|
|
||||||
|
def get_video_id(url):
|
||||||
|
"""Returns Video_ID extracting from the given url of Youtube
|
||||||
|
|
||||||
|
Examples of URLs:
|
||||||
|
Valid:
|
||||||
|
'http://youtu.be/_lOT2p_FCvA',
|
||||||
|
'www.youtube.com/watch?v=_lOT2p_FCvA&feature=feedu',
|
||||||
|
'http://www.youtube.com/embed/_lOT2p_FCvA',
|
||||||
|
'http://www.youtube.com/v/_lOT2p_FCvA?version=3&hl=en_US',
|
||||||
|
'https://www.youtube.com/watch?v=rTHlyTphWP0&index=6&list=PLjeDyYvG6-40qawYNR4juzvSOg-ezZ2a6',
|
||||||
|
'youtube.com/watch?v=_lOT2p_FCvA',
|
||||||
|
|
||||||
|
Invalid:
|
||||||
|
'youtu.be/watch?v=_lOT2p_FCvA',
|
||||||
|
"""
|
||||||
|
|
||||||
|
if url.startswith(('youtu', 'www')):
|
||||||
|
url = 'http://' + url
|
||||||
|
elif 'http' not in url:
|
||||||
|
return url
|
||||||
|
|
||||||
|
query = urlparse(url)
|
||||||
|
|
||||||
|
if 'youtube' in query.hostname:
|
||||||
|
if query.path == '/watch':
|
||||||
|
return parse_qs(query.query)['v'][0]
|
||||||
|
elif query.path.startswith(('/embed/', '/v/')):
|
||||||
|
return query.path.split('/')[2]
|
||||||
|
elif 'youtu.be' in query.hostname:
|
||||||
|
return query.path[1:]
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
|
||||||
|
# Show a video compared with data
|
||||||
|
def workout_video_view(request,id=''):
|
||||||
|
try:
|
||||||
|
id = encoder.decode_hex(id)
|
||||||
|
analysis = VideoAnalysis.objects.get(id=id)
|
||||||
|
except VideoAnalysis.DoesNotExist:
|
||||||
|
raise Http404("Video Analysis does not exist")
|
||||||
|
|
||||||
|
w = analysis.workout
|
||||||
|
delay = analysis.delay
|
||||||
|
|
||||||
|
if w.workouttype in mytypes.otwtypes:
|
||||||
|
mode = 'water'
|
||||||
|
else:
|
||||||
|
mode = 'erg'
|
||||||
|
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
mayedit = checkworkoutuser(request.user,w) and isprorower(request.user.rower)
|
||||||
|
rower = request.user.rower
|
||||||
|
else:
|
||||||
|
mayedit = False
|
||||||
|
rower = None
|
||||||
|
|
||||||
|
# get video ID and offset
|
||||||
|
if mayedit and request.method == 'POST':
|
||||||
|
form = VideoAnalysisCreateForm(request.POST)
|
||||||
|
metricsform = VideoAnalysisMetricsForm(request.POST,mode=mode)
|
||||||
|
if form.is_valid() and metricsform.is_valid():
|
||||||
|
video_id = form.cleaned_data['url']
|
||||||
|
try:
|
||||||
|
video_id = get_video_id(form.cleaned_data['url'])
|
||||||
|
except (TypeError,ValueError):
|
||||||
|
pass
|
||||||
|
delay = form.cleaned_data['delay']
|
||||||
|
metricsgroups = metricsform.cleaned_data['groups']
|
||||||
|
if 'save_button' in request.POST:
|
||||||
|
analysis.name = form.cleaned_data['name']
|
||||||
|
analysis.video_id = video_id
|
||||||
|
analysis.delay = delay
|
||||||
|
analysis.metricsgroups = metricsgroups
|
||||||
|
analysis.save()
|
||||||
|
else:
|
||||||
|
video_id = id
|
||||||
|
delay = 0
|
||||||
|
elif mayedit:
|
||||||
|
form = VideoAnalysisCreateForm(
|
||||||
|
initial = {
|
||||||
|
'name':analysis.name,
|
||||||
|
'delay': analysis.delay,
|
||||||
|
'url': analysis.video_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
metricsform = VideoAnalysisMetricsForm(initial={'groups':analysis.metricsgroups},
|
||||||
|
mode=mode)
|
||||||
|
metricsgroups = analysis.metricsgroups
|
||||||
|
video_id = analysis.video_id
|
||||||
|
else:
|
||||||
|
form = None
|
||||||
|
metricsform = None
|
||||||
|
metricsgroups = analysis.metricsgroups
|
||||||
|
|
||||||
|
data, metrics, maxtime = dataprep.get_video_data(w,groups=metricsgroups,mode=mode)
|
||||||
|
hascoordinates = pd.Series(data['latitude']).std() > 0
|
||||||
|
# create map
|
||||||
|
if hascoordinates:
|
||||||
|
mapscript, mapdiv = leaflet_chart_video(data['latitude'],data['longitude'],
|
||||||
|
w.name)
|
||||||
|
else:
|
||||||
|
mapscript, mapdiv = interactive_chart_video(data)
|
||||||
|
data['longitude'] = data['spm']
|
||||||
|
data['latitude'] = list(range(len(data['spm'])))
|
||||||
|
|
||||||
|
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
'url':'/rowers/list-workouts/',
|
||||||
|
'name':'Workouts'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':get_workout_default_page(request,encoder.encode_hex(w.id)),
|
||||||
|
'name': w.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':reverse('workout_video_view',kwargs={'id':encoder.encode_hex(analysis.id)}),
|
||||||
|
'name': 'Video Analysis'
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
'embedded_video.html',
|
||||||
|
{
|
||||||
|
'workout':w,
|
||||||
|
'rower':rower,
|
||||||
|
'data': json.dumps(data,default=default),
|
||||||
|
'mapscript': mapscript,
|
||||||
|
'mapdiv': mapdiv,
|
||||||
|
'video_id': analysis.video_id,
|
||||||
|
'form':form,
|
||||||
|
'breadcrumbs':breadcrumbs,
|
||||||
|
'analysis':analysis,
|
||||||
|
'maxtime':maxtime,
|
||||||
|
'metrics':metrics,
|
||||||
|
'locked': True,
|
||||||
|
'metricsform':metricsform,
|
||||||
|
'metricsgroups': metricsgroups,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# Create a video compared with data
|
||||||
|
@user_passes_test(ispromember,login_url="/rowers/paidplans/",
|
||||||
|
message="This functionality requires a Pro plan or higher",
|
||||||
|
redirect_field_name=None)
|
||||||
|
def workout_video_create_view(request,id=0):
|
||||||
|
# get workout
|
||||||
|
w = get_workout_permitted(request.user,id)
|
||||||
|
if w.workouttype in mytypes.otwtypes:
|
||||||
|
mode = 'water'
|
||||||
|
else:
|
||||||
|
mode = 'erg'
|
||||||
|
|
||||||
|
# get video ID and offset
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = VideoAnalysisCreateForm(request.POST)
|
||||||
|
metricsform = VideoAnalysisMetricsForm(request.POST,mode=mode)
|
||||||
|
if form.is_valid() and metricsform.is_valid():
|
||||||
|
url = form.cleaned_data['url']
|
||||||
|
delay = form.cleaned_data['delay']
|
||||||
|
metricsgroups = metricsform.cleaned_data['groups']
|
||||||
|
video_id = get_video_id(url)
|
||||||
|
if 'save_button' in request.POST:
|
||||||
|
analysis = VideoAnalysis(
|
||||||
|
workout=w,
|
||||||
|
name=form.cleaned_data['name'],
|
||||||
|
video_id = video_id,
|
||||||
|
delay=delay,
|
||||||
|
metricsgroups=metricsgroups
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
analysis.save()
|
||||||
|
url = reverse('workout_video_view',
|
||||||
|
kwargs={'id':encoder.encode_hex(analysis.id)})
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
except IntegrityError:
|
||||||
|
messages.error(request,'You cannot save two video analysis with the same YouTube video and Workout. Redirecting to your existing analysis')
|
||||||
|
analysis = VideoAnalysis.objects.filter(workout=w,video_id=video_id)
|
||||||
|
if analysis:
|
||||||
|
url = reverse('workout_video_view',
|
||||||
|
kwargs={'id':encoder.encode_hex(analysis[0].id)})
|
||||||
|
else:
|
||||||
|
url = reverse('workout_video_create_view',
|
||||||
|
kwargs={'id':encoder.encode_hex(w.id)})
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
else:
|
||||||
|
video_id = None
|
||||||
|
delay = 0
|
||||||
|
metricsgroups = ['basic']
|
||||||
|
else:
|
||||||
|
form = VideoAnalysisCreateForm()
|
||||||
|
metricsform = VideoAnalysisMetricsForm(initial={'groups':['basic']},mode=mode)
|
||||||
|
video_id = None
|
||||||
|
delay = 0
|
||||||
|
metricsgroups = ['basic']
|
||||||
|
|
||||||
|
# get data
|
||||||
|
data, metrics, maxtime = dataprep.get_video_data(w,groups=metricsgroups,mode=mode)
|
||||||
|
hascoordinates = pd.Series(data['latitude']).std() > 0
|
||||||
|
|
||||||
|
# create map
|
||||||
|
if hascoordinates:
|
||||||
|
mapscript, mapdiv = leaflet_chart_video(data['latitude'],data['longitude'],
|
||||||
|
w.name)
|
||||||
|
else:
|
||||||
|
mapscript, mapdiv = interactive_chart_video(data)
|
||||||
|
data['longitude'] = data['spm']
|
||||||
|
data['latitude'] = list(range(len(data['spm'])))
|
||||||
|
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
'url':'/rowers/list-workouts/',
|
||||||
|
'name':'Workouts'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':get_workout_default_page(request,encoder.encode_hex(w.id)),
|
||||||
|
'name': w.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':reverse('workout_video_create_view',kwargs={'id':encoder.encode_hex(w.id)}),
|
||||||
|
'name': 'Video Analysis'
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
analysis = {'delay':delay}
|
||||||
|
|
||||||
|
template = 'embedded_video.html'
|
||||||
|
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
template,
|
||||||
|
{
|
||||||
|
'workout':w,
|
||||||
|
'rower':request.user.rower,
|
||||||
|
'data': json.dumps(data,default=default),
|
||||||
|
'mapscript': mapscript,
|
||||||
|
'mapdiv': mapdiv,
|
||||||
|
'video_id': video_id,
|
||||||
|
'form':form,
|
||||||
|
'metricsform':metricsform,
|
||||||
|
'analysis':analysis,
|
||||||
|
'breadcrumbs':breadcrumbs,
|
||||||
|
'maxtime':maxtime,
|
||||||
|
'metrics':metrics,
|
||||||
|
'metricsgroups': metricsgroups,
|
||||||
|
'locked': False,
|
||||||
|
})
|
||||||
|
|
||||||
# Show the EMpower Oarlock generated Stroke Profile
|
# Show the EMpower Oarlock generated Stroke Profile
|
||||||
@user_passes_test(ispromember,login_url="/rowers/paidplans/",
|
@user_passes_test(ispromember,login_url="/rowers/paidplans/",
|
||||||
@@ -446,6 +707,205 @@ def workouts_join_view(request):
|
|||||||
url = reverse('workouts_join_select')
|
url = reverse('workouts_join_select')
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
defaultoptions = {
|
||||||
|
'includereststrokes': False,
|
||||||
|
'workouttypes':['rower','dynamic','slides'],
|
||||||
|
'waterboattype': mytypes.waterboattype,
|
||||||
|
'rankingonly': False,
|
||||||
|
'function':'boxplot'
|
||||||
|
}
|
||||||
|
|
||||||
|
@user_passes_test(ispromember, login_url="/rowers/paidplans",
|
||||||
|
message="This functionality requires a Pro plan or higher",
|
||||||
|
redirect_field_name=None)
|
||||||
|
def video_selectworkout(request,userid=0,teamid=0):
|
||||||
|
r = getrequestrower(request, userid=userid)
|
||||||
|
user = r.user
|
||||||
|
userid = user.id
|
||||||
|
workouts = Workout.objects.filter(user=r).order_by('-date')
|
||||||
|
|
||||||
|
try:
|
||||||
|
theteam = Team.objects.get(id=teamid)
|
||||||
|
except Team.DoesNotExist:
|
||||||
|
theteam = None
|
||||||
|
|
||||||
|
|
||||||
|
if 'options' in request.session:
|
||||||
|
options = request.session['options']
|
||||||
|
else:
|
||||||
|
options=defaultoptions
|
||||||
|
|
||||||
|
options['userid'] = userid
|
||||||
|
try:
|
||||||
|
workouttypes = options['workouttypes']
|
||||||
|
except KeyError:
|
||||||
|
workouttypes = ['rower','dynamic','slides']
|
||||||
|
|
||||||
|
try:
|
||||||
|
modalities = options['modalities']
|
||||||
|
modality = modalities[0]
|
||||||
|
except KeyError:
|
||||||
|
modalities = [m[0] for m in mytypes.workouttypes_ordered.items()]
|
||||||
|
modality = 'all'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
rankingonly = options['rankingonly']
|
||||||
|
except KeyError:
|
||||||
|
rankingonly = False
|
||||||
|
|
||||||
|
query = request.GET.get('q')
|
||||||
|
if query:
|
||||||
|
query_list = query.split()
|
||||||
|
workouts = workouts.filter(
|
||||||
|
reduce(operator.and_,
|
||||||
|
(Q(name__icontains=q) for q in query_list)) |
|
||||||
|
reduce(operator.and_,
|
||||||
|
(Q(notes__icontains=q) for q in query_list))
|
||||||
|
)
|
||||||
|
searchform = SearchForm(initial={'q':query})
|
||||||
|
else:
|
||||||
|
searchform = SearchForm()
|
||||||
|
|
||||||
|
|
||||||
|
if 'startdate' in request.session:
|
||||||
|
startdate = iso8601.parse_date(request.session['startdate'])
|
||||||
|
else:
|
||||||
|
startdate=timezone.now()-datetime.timedelta(days=42)
|
||||||
|
|
||||||
|
if 'enddate' in request.session:
|
||||||
|
enddate = iso8601.parse_date(request.session['enddate'])
|
||||||
|
else:
|
||||||
|
enddate = timezone.now()
|
||||||
|
|
||||||
|
|
||||||
|
workouts = workouts.filter(date__gte=startdate,date__lte=enddate)
|
||||||
|
|
||||||
|
waterboattype = mytypes.waterboattype
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
thediv = get_call()
|
||||||
|
dateform = DateRangeForm(request.POST)
|
||||||
|
if dateform.is_valid():
|
||||||
|
startdate = dateform.cleaned_data['startdate']
|
||||||
|
enddate = dateform.cleaned_data['enddate']
|
||||||
|
startdatestring = startdate.strftime('%Y-%m-%d')
|
||||||
|
enddatestring = enddate.strftime('%Y-%m-%d')
|
||||||
|
request.session['startdate'] = startdatestring
|
||||||
|
request.session['enddate'] = enddatestring
|
||||||
|
|
||||||
|
form = WorkoutSingleSelectForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
cd = form.cleaned_data
|
||||||
|
selectedworkout = cd['workout']
|
||||||
|
url = reverse('workout_video_create_view',
|
||||||
|
kwargs={'id':encoder.encode_hex(selectedworkout.id)})
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
else:
|
||||||
|
id = 0
|
||||||
|
else:
|
||||||
|
form = WorkoutSingleSelectForm(workouts=workouts)
|
||||||
|
thediv = ''
|
||||||
|
dateform = DateRangeForm(initial={
|
||||||
|
'startdate':startdate,
|
||||||
|
'enddate':enddate,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
negtypes = []
|
||||||
|
for b in mytypes.boattypes:
|
||||||
|
if b[0] not in waterboattype:
|
||||||
|
negtypes.append(b[0])
|
||||||
|
|
||||||
|
|
||||||
|
startdate = datetime.datetime.combine(startdate,datetime.time())
|
||||||
|
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
|
||||||
|
|
||||||
|
if enddate < startdate:
|
||||||
|
s = enddate
|
||||||
|
enddate = startdate
|
||||||
|
startdate = s
|
||||||
|
|
||||||
|
negtypes = []
|
||||||
|
for b in mytypes.boattypes:
|
||||||
|
if b[0] not in waterboattype:
|
||||||
|
negtypes.append(b[0])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if theteam is not None and (theteam.viewing == 'allmembers' or theteam.manager == request.user):
|
||||||
|
workouts = Workout.objects.filter(team=theteam,
|
||||||
|
startdatetime__gte=startdate,
|
||||||
|
startdatetime__lte=enddate,
|
||||||
|
workouttype__in=modalities,
|
||||||
|
)
|
||||||
|
elif theteam is not None and theteam.viewing == 'coachonly':
|
||||||
|
workouts = Workout.objects.filter(team=theteam,user=r,
|
||||||
|
startdatetime__gte=startdate,
|
||||||
|
startdatetime__lte=enddate,
|
||||||
|
workouttype__in=modalities,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
workouts = Workout.objects.filter(user=r,
|
||||||
|
startdatetime__gte=startdate,
|
||||||
|
startdatetime__lte=enddate,
|
||||||
|
workouttype__in=modalities,
|
||||||
|
)
|
||||||
|
|
||||||
|
workouts = workouts.order_by(
|
||||||
|
"-date", "-starttime"
|
||||||
|
).exclude(boattype__in=negtypes)
|
||||||
|
|
||||||
|
|
||||||
|
if rankingonly:
|
||||||
|
workouts = workouts.exclude(rankingpiece=False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
optionsform = AnalysisOptionsForm(initial={
|
||||||
|
'modality':modality,
|
||||||
|
'waterboattype':waterboattype,
|
||||||
|
'rankingonly':rankingonly,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
startdatestring = startdate.strftime('%Y-%m-%d')
|
||||||
|
enddatestring = enddate.strftime('%Y-%m-%d')
|
||||||
|
request.session['startdate'] = startdatestring
|
||||||
|
request.session['enddate'] = enddatestring
|
||||||
|
request.session['options'] = options
|
||||||
|
|
||||||
|
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
'url':'/rowers/analysis',
|
||||||
|
'name':'Analysis'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':reverse('analysis_new',kwargs={'userid':userid}),
|
||||||
|
'name': 'Analysis Select'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
return render(request, 'video_selectworkout.html',
|
||||||
|
{'workouts': workouts,
|
||||||
|
'dateform':dateform,
|
||||||
|
'startdate':startdate,
|
||||||
|
'enddate':enddate,
|
||||||
|
'rower':r,
|
||||||
|
'breadcrumbs':breadcrumbs,
|
||||||
|
'theuser':user,
|
||||||
|
'the_div':thediv,
|
||||||
|
'form':form,
|
||||||
|
'active':'nav-analysis',
|
||||||
|
'searchform':searchform,
|
||||||
|
'teams':get_my_teams(request.user),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@user_passes_test(ispromember,login_url="/rowers/paidplans",
|
@user_passes_test(ispromember,login_url="/rowers/paidplans",
|
||||||
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality",
|
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality. If you are already a Pro user, please log in to access this functionality",
|
||||||
redirect_field_name=None)
|
redirect_field_name=None)
|
||||||
@@ -3530,6 +3990,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
videos = VideoAnalysis.objects.filter(workout=row)
|
||||||
|
|
||||||
# create interactive plot
|
# create interactive plot
|
||||||
f1 = row.csvfilename
|
f1 = row.csvfilename
|
||||||
@@ -3593,6 +4054,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
|
|||||||
'workout':row,
|
'workout':row,
|
||||||
'teams':get_my_teams(request.user),
|
'teams':get_my_teams(request.user),
|
||||||
'graphs':g,
|
'graphs':g,
|
||||||
|
'videos':videos,
|
||||||
'breadcrumbs':breadcrumbs,
|
'breadcrumbs':breadcrumbs,
|
||||||
'rower':r,
|
'rower':r,
|
||||||
'indoorraces':indoorraces,
|
'indoorraces':indoorraces,
|
||||||
@@ -4495,6 +4957,45 @@ def team_workout_upload_view(request,message="",
|
|||||||
|
|
||||||
|
|
||||||
# A page with all the recent graphs (searchable on workout name)
|
# A page with all the recent graphs (searchable on workout name)
|
||||||
|
@login_required()
|
||||||
|
def list_videos(request):
|
||||||
|
r = getrequestrower(request)
|
||||||
|
workouts = Workout.objects.filter(user=r).order_by("-date", "-starttime")
|
||||||
|
query = request.GET.get('q')
|
||||||
|
if query:
|
||||||
|
query_list = query.split()
|
||||||
|
if query:
|
||||||
|
query_list = query.split()
|
||||||
|
workouts = workouts.filter(
|
||||||
|
reduce(operator.and_,
|
||||||
|
(Q(name__icontains=q) for q in query_list)) |
|
||||||
|
reduce(operator.and_,
|
||||||
|
(Q(notes__icontains=q) for q in query_list))
|
||||||
|
)
|
||||||
|
searchform = SearchForm(initial={'q':query})
|
||||||
|
else:
|
||||||
|
searchform = SearchForm()
|
||||||
|
|
||||||
|
g = VideoAnalysis.objects.filter(workout__in=workouts).order_by("-workout__date")
|
||||||
|
|
||||||
|
|
||||||
|
paginator = Paginator(g,8)
|
||||||
|
page = request.GET.get('page')
|
||||||
|
|
||||||
|
try:
|
||||||
|
g = paginator.page(page)
|
||||||
|
except PageNotAnInteger:
|
||||||
|
g = paginator.page(1)
|
||||||
|
except EmptyPage:
|
||||||
|
g = paginator.page(paginator.num_pages)
|
||||||
|
|
||||||
|
return render(request, 'list_videos.html',
|
||||||
|
{'analyses': g,
|
||||||
|
'searchform':searchform,
|
||||||
|
'active':'nav-analysis',
|
||||||
|
'teams':get_my_teams(request.user),
|
||||||
|
})
|
||||||
|
|
||||||
@login_required()
|
@login_required()
|
||||||
def graphs_view(request):
|
def graphs_view(request):
|
||||||
request.session['referer'] = reverse('graphs_view')
|
request.session['referer'] = reverse('graphs_view')
|
||||||
@@ -5254,6 +5755,58 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
|
|||||||
'formvalues':formvalues,
|
'formvalues':formvalues,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
class VideoDelete(DeleteView):
|
||||||
|
login_required = True
|
||||||
|
model = VideoAnalysis
|
||||||
|
template_name = 'video_delete_confirm.html'
|
||||||
|
|
||||||
|
# extra parameters
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(VideoDelete, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
'url':'/rowers/list-workouts/',
|
||||||
|
'name':'Workouts'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':get_workout_default_page(
|
||||||
|
self.request,
|
||||||
|
encoder.encode_hex(self.object.workout.id)),
|
||||||
|
'name': self.object.workout.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':reverse('workout_video_view',kwargs={'id':encoder.encode_hex(self.object.id)}),
|
||||||
|
'name': 'Video Analysis'
|
||||||
|
},
|
||||||
|
{ 'url':reverse('video_delete',kwargs={'pk':str(self.object.pk)}),
|
||||||
|
'name': 'Delete'
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
context['active'] = 'nav-workouts'
|
||||||
|
context['rower'] = getrower(self.request.user)
|
||||||
|
context['breadcrumbs'] = breadcrumbs
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
w = self.object.workout
|
||||||
|
try:
|
||||||
|
w = Workout.objects.get(id=w.id)
|
||||||
|
except Workout.DoesNotExist:
|
||||||
|
return reverse('workouts_view')
|
||||||
|
|
||||||
|
return reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)})
|
||||||
|
|
||||||
|
def get_object(self, *args, **kwargs):
|
||||||
|
obj = super(VideoDelete, self).get_object(*args, **kwargs)
|
||||||
|
if not checkaccessuser(self.request.user,obj.workout.user):
|
||||||
|
raise PermissionDenied('You are not allowed to delete this analysis')
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
class GraphDelete(DeleteView):
|
class GraphDelete(DeleteView):
|
||||||
login_required = True
|
login_required = True
|
||||||
|
|||||||
@@ -77,8 +77,12 @@ CACHES = {
|
|||||||
|
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
#STATIC_ROOT = 'C:/python/rowsandallapp'
|
|
||||||
STATIC_ROOT = BASE_DIR
|
STATIC_ROOT = BASE_DIR
|
||||||
|
#STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),
|
||||||
|
os.path.join(BASE_DIR, 'static/plots'),]
|
||||||
|
|
||||||
|
|
||||||
INTERNAL_IPS = ['127.0.0.1']
|
INTERNAL_IPS = ['127.0.0.1']
|
||||||
|
|
||||||
|
|||||||
+11
-5
@@ -30,9 +30,9 @@ import django.views.i18n
|
|||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path('^', include('django.contrib.auth.urls')),
|
re_path('^', include('django.contrib.auth.urls')),
|
||||||
re_path(r'^password_change_done/$',auth_views.PasswordChangeDoneView.as_view(),name='password_change_done'),
|
re_path(r'^password_change_done/$',auth_views.PasswordChangeDoneView.as_view(),name='password_change_done'),
|
||||||
re_path(r'^password_change/$',auth_views.PasswordChangeView.as_view(),name='password_change'),
|
re_path(r'^password_change/$',auth_views.PasswordChangeView.as_view(),name='password_change'),
|
||||||
re_path(r'^password_reset/$',
|
re_path(r'^password_reset/$',
|
||||||
auth_views.PasswordResetView.as_view(),
|
auth_views.PasswordResetView.as_view(),
|
||||||
{'template_name': 'rowers/templates/registration/password_reset.html'},
|
{'template_name': 'rowers/templates/registration/password_reset.html'},
|
||||||
@@ -96,10 +96,16 @@ django.views.i18n.javascript_catalog = None
|
|||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
import debug_toolbar
|
import debug_toolbar
|
||||||
import django
|
import django
|
||||||
|
urlpatterns += [
|
||||||
|
re_path(r'^static/plots/(?P<path>.*)$',
|
||||||
|
django.views.static.serve,
|
||||||
|
kwargs={'document_root': settings.STATIC_ROOT+'plots/'})
|
||||||
|
]
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
# re_path(r'^__debug__/','debug_toolbar.urls'),
|
# re_path(r'^__debug__/','debug_toolbar.urls'),
|
||||||
re_path(r'^static/(?P<path>.*)$',
|
re_path(r'^static/(?P<path>.*)$',
|
||||||
django.views.static.serve,
|
django.views.static.serve,
|
||||||
kwargs={'document_root': settings.STATIC_ROOT,}
|
kwargs={'document_root': settings.STATIC_ROOT,
|
||||||
|
'show_indexes': True}
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// var opts = {
|
||||||
|
// lines: 12,
|
||||||
|
// angle: 0.15,
|
||||||
|
// lineWidth: 0.44,
|
||||||
|
// pointer: {
|
||||||
|
// length: 0.9,
|
||||||
|
// strokeWidth: 0.035,
|
||||||
|
// color: '#000000'
|
||||||
|
// },
|
||||||
|
// limitMax: 'false',
|
||||||
|
// // percentColors: [[0.0, "#a9d70b" ], [0.50, "#a9d70b"], [1.0, "#a9d70b"]], // !!!!
|
||||||
|
// strokeColor: '#E0E0E0',
|
||||||
|
// generateGradient: true
|
||||||
|
// };
|
||||||
|
// var target = document.getElementById('angles');
|
||||||
|
// var gauge = new Gauge(target).setOptions(opts);
|
||||||
|
// gauge.maxValue = 90;
|
||||||
|
// gauge.minValue = -90;
|
||||||
|
// gauge.animationSpeed = 5;
|
||||||
|
// gauge.set(-75);
|
||||||
|
|
||||||
|
// https://github.com/bernii/gauge.js/issues/193
|
||||||
|
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
angle: 0, // The span of the gauge arc
|
||||||
|
lineWidth: 0.2, // The line thickness
|
||||||
|
radiusScale: 0.89, // Relative radius
|
||||||
|
pointer: {
|
||||||
|
length: 0.54, // // Relative to gauge radius
|
||||||
|
strokeWidth: 0.053, // The thickness
|
||||||
|
color: '#000000' // Fill color
|
||||||
|
},
|
||||||
|
limitMax: false, // If false, max value increases automatically if value > maxValue
|
||||||
|
limitMin: false, // If true, the min value of the gauge will be fixed
|
||||||
|
colorStart: '#6FADCF', // Colors
|
||||||
|
colorStop: '#8FC0DA', // just experiment with them
|
||||||
|
strokeColor: '#E0E0E0', // to see which ones work best for you
|
||||||
|
generateGradient: true,
|
||||||
|
highDpiSupport: true, // High resolution support
|
||||||
|
staticZones: [
|
||||||
|
{strokeStyle: "#00FF00", min: 0, max: 2}, // Red from 100 to 60
|
||||||
|
{strokeStyle: "#0000FF", min: 2, max: 3}, // Yellow
|
||||||
|
{strokeStyle: "#00FFFF", min: 3, max: 4}, // Green
|
||||||
|
{strokeStyle: "#FFDD00", min: 4, max: 5}, // Yellow
|
||||||
|
{strokeStyle: "#FF0000", min: 5, max: 6} // Red
|
||||||
|
],
|
||||||
|
|
||||||
|
};
|
||||||
|
var target = document.getElementById('basic'); // your canvas element
|
||||||
|
var gaugeboatspeed = new Gauge(target).setOptions(opts); // create sexy gauge!
|
||||||
|
gaugeboatspeed.maxValue = 6; // set max gauge value
|
||||||
|
gaugeboatspeed.setMinValue(0); // Prefer setter over gauge.minValue = 0
|
||||||
|
gaugeboatspeed.animationSpeed = 10; // set animation speed (32 is default value)
|
||||||
|
gaugeboatspeed.set(0); // set actual value
|
||||||
|
|
||||||
|
// Define set_basic(values) so that gauges can be set by metricsgroups
|
||||||
|
function set_basic() {
|
||||||
|
gaugeboatspeed.set(boatspeed_now);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user