Private
Public Access
1
0
This commit is contained in:
Sander Roosendaal
2019-12-02 08:24:34 +01:00
27 changed files with 2060 additions and 349 deletions
+132 -3
View File
@@ -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)
+3
View File
@@ -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
View File
@@ -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
+229
View File
@@ -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>&nbsp;</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
View File
@@ -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
+15
View File
@@ -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
+360
View File
@@ -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>&nbsp;</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 %}
+8 -7
View File
@@ -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 %}
+90
View File
@@ -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 %}
+91 -73
View File
@@ -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>&nbsp;Video Analysis
</label>
<ul>
<li id="videos-analyses">
<a href="/rowers/videos/">
<i class="fas fa-film fa-fw"></i>&nbsp;Video Analyses
</a>
</li>
<li id="video-add">
<a href="/rowers/add-video/">
<i class="fas fa-video-plus fa-fw"></i>&nbsp;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>&nbsp;Compare <i class="fas fa-balance-scale fa-fw"></i>&nbsp;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>&nbsp;Power Progress <i class="far fa-watch-fitness fa-fw"></i>&nbsp;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>&nbsp;Statistics <i class="fal fa-table fa-fw"></i>&nbsp;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>&nbsp;Histogram <i class="fas fa-chart-bar fa-fw"></i>&nbsp;Histogram
</a> </a>
</li> </li>
<li> <li>
<a href="/rowers/alerts/"> <a href="/rowers/alerts/">
<i class="fas fa-bell fa-fw"></i>&nbsp;Alerts <i class="fas fa-bell fa-fw"></i>&nbsp;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>&nbsp;Cumulative Flex Chart <i class="fas fa-chart-line fa-fw"></i>&nbsp;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>&nbsp;Laboratory</a> <i class="fas fa-flask fa-fw"></i>&nbsp;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>&nbsp;</p> <p>&nbsp;</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>&nbsp;Athletes</label> <label for="athlete-selector"><i class="fas fa-users fa-fw"></i>&nbsp;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 %}
&bull; &bull;
{% else %} {% else %}
&nbsp; &nbsp;
{% 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>&nbsp;</p> <p>&nbsp;</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>&nbsp;Groups</label> <label for="team-selector"><i class="fas fa-bullhorn fa-fw"></i>&nbsp;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 %}
&bull; &bull;
{% else %} {% else %}
&nbsp; &nbsp;
{% 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' %}
+7 -2
View File
@@ -45,6 +45,11 @@
<i class="fas fa-balance-scale fa-fw"></i>&nbsp;Compare <i class="fas fa-balance-scale fa-fw"></i>&nbsp;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>&nbsp;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>&nbsp;Power (Pie) <i class="far fa-chart-pie fa-fw"></i>&nbsp;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>&nbsp;Heart Rate (Pie) <i class="fas fa-heartbeat fa-fw"></i>&nbsp;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>&nbsp;Wind <i class="fas fa-pennant fa-fw"></i>&nbsp;Wind
+2 -5
View File
@@ -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
View File
@@ -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 %}
+117
View File
@@ -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 %}
+19 -11
View File
@@ -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 %}
+10
View File
@@ -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
View File
@@ -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)
File diff suppressed because one or more lines are too long
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+553
View File
@@ -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&amp;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
+5 -1
View File
@@ -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
View File
@@ -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}
) )
] ]
+60
View File
@@ -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);
}