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
+46 -5
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,
@@ -816,6 +850,17 @@ class PlanSelectForm(forms.Form):
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(),
@@ -1380,7 +1425,3 @@ class FlexAxesForm(forms.Form):
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=[],
+54 -30
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,7 +55,8 @@ 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',
@@ -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,7 +96,8 @@ 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',
@@ -102,7 +106,8 @@ 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',
@@ -111,7 +116,8 @@ 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',
@@ -120,7 +126,8 @@ 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',
@@ -129,7 +136,8 @@ 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',
@@ -138,7 +146,8 @@ 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',
@@ -146,8 +155,9 @@ rowingmetrics = (
'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,7 +252,8 @@ rowingmetrics = (
'ax_max': 50, 'ax_max': 50,
'default': 0, 'default': 0,
'mode':'water', 'mode':'water',
'type': 'pro'}), 'type': 'pro',
'group':'stroke'}),
('totalangle',{ ('totalangle',{
@@ -245,7 +264,8 @@ rowingmetrics = (
'ax_max': 140, 'ax_max': 140,
'default': 0, 'default': 0,
'mode':'water', 'mode':'water',
'type': 'pro'}), 'type': 'pro',
'group':'stroke'}),
('effectiveangle',{ ('effectiveangle',{
@@ -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:
@@ -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 %}
+4 -3
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 %}
+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 %}
+90 -72
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">
@@ -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' %}
+5
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/">
-3
View File
@@ -8,6 +8,3 @@
</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 %}
+8
View File
@@ -171,6 +171,14 @@ $('#id_workouttype').change();
</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')
+3 -3
View File
@@ -256,12 +256,14 @@ class URLTests(TestCase):
@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')
@@ -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": ""}}
+7 -6
View File
@@ -524,13 +524,14 @@ 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)):
+7 -1
View File
@@ -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'),
+6 -5
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
@@ -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
@@ -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']
+7 -1
View File
@@ -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);
}