Private
Public Access
1
0

Merge branch 'release/v2.9'

This commit is contained in:
Sander Roosendaal
2017-05-18 19:25:13 +02:00
14 changed files with 537 additions and 148 deletions
+9
View File
@@ -95,6 +95,15 @@ from scipy.signal import savgol_filter
import datetime
def get_workouts(ids,userid):
goodids = []
for id in ids:
w = Workout.objects.get(id=id)
if int(w.user.user.id) == int(userid):
goodids.append(id)
return [Workout.objects.get(id=id) for id in goodids]
def filter_df(datadf,fieldname,value,largerthan=True):
try:
+2
View File
@@ -18,6 +18,7 @@ from rowsandall_app.settings import DATABASES
from utils import lbstoN
user = DATABASES['default']['USER']
password = DATABASES['default']['PASSWORD']
database_name = DATABASES['default']['NAME']
@@ -335,6 +336,7 @@ def handle_nonpainsled(f2,fileformat,summary=''):
return (f2,summary,oarlength,inboard)
# Create new workout from file and store it in the database
# This routine should be used everywhere in views.py and mailprocessing.py
# Currently there is code duplication
+3
View File
@@ -22,6 +22,9 @@ def getexpired():
if r.planexpires < datetime.datetime.now().date():
if r.rowerplan == 'pro' or r.rowerplan == 'coach':
lijst.append(r)
if r.teamplanexpires < datetime.datetime.now().date():
if r.rowerplan == 'pro' or r.rowerplan == 'coach':
lijst.append(r)
return lijst
+2 -1
View File
@@ -173,6 +173,7 @@ class Rower(models.Model):
# Power Zone Data
ftp = models.IntegerField(default=226,verbose_name="Functional Threshold Power")
otwslack = models.IntegerField(default=0,verbose_name="OTW Power slack")
pw_ut2 = models.IntegerField(default=124,verbose_name="UT2 Power")
pw_ut1 = models.IntegerField(default=171,verbose_name="UT1 Power")
@@ -576,7 +577,7 @@ class AdvancedWorkoutForm(ModelForm):
class RowerPowerForm(ModelForm):
class Meta:
model = Rower
fields = ['ftp']
fields = ['ftp','otwslack']
# Form to set rower's Power zones, including test routines
# to enable consistency
+7 -5
View File
@@ -123,12 +123,10 @@ of a rowers technique.</p>
<p>Click on the PayPal button to pay for your Premium membership.
It will be valid for one year with automatic renewal which you can stop at any time.
Before you pay, please <a href="/rowers/register">register</a> for the free Basic membership and log in.
In this way, your user name will be added to the payment details.
Your payment will be valid for one year with automatic renewal which you can stop at any time.
You will be taken to the secure PayPal payment site.
<ul>
<li>Please mention the username you are registered under in
"instructions to seller".</li>
</ul>
</p>
<p>After you do the payment, we will manually change your membership to
"Pro". Depending on our availability, this may take some time
@@ -138,6 +136,10 @@ You will be taken to the secure PayPal payment site.
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="cmd" value="_s-xclick">
<input type="hidden" name="hosted_button_id" value="964GLEXX3THAW">
{% if user.is_authenticated %}
<input type="hidden" name="os0" value="{{ user }}">
<input type="hidden" name="on0" value="username">
{% endif %}
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_subscribeCC_LG_global.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
</form>
+21 -7
View File
@@ -6,8 +6,19 @@
{% block content %}
<h1>Available on C2 Logbook</h1>
{% if workouts %}
<table width="70%" class="listtable">
{% if workouts %}
<div class="grid_2 alpha ">
<a href="/rowers/workout/c2import/all/" class="button gray">Import all NEW</a>
</div>
<div class="grid_10 omega">
<p>This imports all workouts that have not been imported to rowsandall.com.
The action may take a longer time to process, so please be patient. Click on Import in the list below to import an individual workout.
</p>
</div>
<div class="grid_12 alpha">
<table width="70%" class="listtable">
<thead>
<tr>
<th> Import </th>
@@ -17,25 +28,28 @@
<th> Type</th>
<th> Source</th>
<th> Comment</th>
<th> New</th>
</tr>
</thead>
<tbody>
{% for workout in workouts %}
<tr>
<td>
<a href="/rowers/workout/c2import/{{ workout|lookup:'id' }}/">Import</a></td>
<a href="/rowers/workout/c2import/{{ workout|lookup:'id' }}/">Import</a></td>
<td>{{ workout|lookup:'starttime' }}</td>
<td>{{ workout|lookup:'duration' }}</td>
<td>{{ workout|lookup:'distance' }}</td>
<td>{{ workout|lookup:'rowtype' }}</td>
<td>{{ workout|lookup:'source' }}</td>
<td>{{ workout|lookup:'comment' }}</td>
<td>{{ workout|lookup:'new' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p> No workouts found </p>
{% endif %}
</table>
</div>
{% else %}
<p> No workouts found </p>
{% endif %}
{% endblock %}
+55
View File
@@ -0,0 +1,55 @@
{% extends "base.html" %}
{% block title %}About us{% endblock title %}
{% block content %}
<h2>PayPal Test Button</h2>
<div class="grid_6 alpha">
<p>This is a test page for testing the paypal functionality. Do not use it.</p>
<p>Donations are welcome to keep this web site going. To help cover the hosting
costs, I have created a <q>Pro</q> membership option (for only 15 EURO per year). Once I process your
donation, I will give you access to some <q>special</q> features on this
website. </p>
<p>Currently, the Pro membership will give you the following extra functionality (and more will follow):
<ul>
<li>More stroke metrics plots</li>
<li>Power curves for OTW rowing</li>
<li>Add weather information to OTW rowing sessions</li>
<li>Power histogram</li>
</ul>
</p>
<p>Click on the PayPal button to pay for your Pro membership. Before you pay, please <a href="/rowers/register">register</a> for the free Basic membership and log in.
In this way, your user name will be added to the payment details.
Your payment will be valid for one year with automatic renewal which you can stop at any time.
You will be taken to the secure PayPal payment site.
</p>
</div>
<div class="grid_6 omega">
<form action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="cmd" value="_s-xclick">
<input type="hidden" name="hosted_button_id" value="JN7HNMTDBDARJ">
{% if user.is_authenticated %}
<input type="hidden" name="os0" value="{{ user }}">
<input type="hidden" name="on0" value="username">
{% endif %}
<input type="image" src="https://www.sandbox.paypal.com/en_US/i/btn/btn_subscribeCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
<img alt="" border="0" src="https://www.sandbox.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
</form>
</form>
<p>After you do the payment, we will manually change your membership to
"Pro". Depending on our availability, this may take some time
(typically one working day). Don't hesitate to contact us
if you have any questions at this stage.</p>
<p>If, for any reason, you are not happy with your Pro membership, please let me know through the contact form. I will contact you as soon as possible to discuss how we can make things better.</p>
</div>
{% endblock content %}
+7 -4
View File
@@ -19,11 +19,10 @@ website. </p>
<li>Power histogram</li>
</ul>
</p>
<p>Click on the PayPal button to pay for your Pro membership. It will be valid for one year with automatic renewal which you can stop at any time.
<p>Click on the PayPal button to pay for your Pro membership. Before you pay, please <a href="/rowers/register">register</a> for the free Basic membership and log in.
In this way, your user name will be added to the payment details.
Your payment will be valid for one year with automatic renewal which you can stop at any time.
You will be taken to the secure PayPal payment site.
<ul>
<li>Please mention the username you are registered under in "instructions to seller".</li>
</ul>
</p>
</div>
@@ -31,6 +30,10 @@ You will be taken to the secure PayPal payment site.
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="cmd" value="_s-xclick">
<input type="hidden" name="hosted_button_id" value="964GLEXX3THAW">
{% if user.is_authenticated %}
<input type="hidden" name="os0" value="{{ user }}">
<input type="hidden" name="on0" value="username">
{% endif %}
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_subscribeCC_LG_global.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
</form>
+10 -2
View File
@@ -7,6 +7,7 @@
<div class="grid_6 alpha">
<p>
<h2>Heart Rate Zones</h2>
<p>Set your heart rate zones with this form.</p>
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
@@ -27,6 +28,8 @@
<div class="grid_6 omega">
<p>
<h2>Power Zones</h2>
<p>The power zones are defined relative to power as measured by the
indoor rower.</p>
<form enctype="multipart/form-data" action="" method="post">
{% if powerzonesform.errors %}
<p style="color: red;">
@@ -127,10 +130,15 @@
</div>
<div class="grid_6 omega">
<p>
<h2>Functional Threshold Power</h2>
<h2>Functional Threshold Power and OTW Slack</h2>
<p>Use this form to quickly change your zones based on the power of a
recent
full out 60 minutes effort. It will update all zones defined above.</p>
full out 60 minutes effort on the ergometer.
It will update all zones defined above.</p>
<p>The OTW Power Slack is the percentage drop of your On-the-water
rowing power
vs the erg power. Typical values are around 15%. This will lower
the power zones for your OTW workouts.</p>
<form enctype="multipart/form-data" action="" method="post">
<table>
{{ powerform.as_table }}
+20 -7
View File
@@ -6,8 +6,18 @@
{% block content %}
<h1>Available on SportTracks</h1>
{% if workouts %}
<table width="70%" class="listtable">
{% if workouts %}
<div class="grid_2 alpha ">
<a href="/rowers/workout/sporttracksimport/all/" class="button gray">Import all NEW</a>
</div>
<div class="grid_10 omega">
<p>This imports all workouts that have not been imported to rowsandall.com.
The action may take a longer time to process, so please be patient. Click on Import in the list below to import an individual workout.
</p>
</div>
<div class="grid_12 alpha">
<table width="70%" class="listtable">
<thead>
<tr>
<th> Import </th>
@@ -16,24 +26,27 @@
<th> Duration </th>
<th> Total Distance</th>
<th> Type</th>
<th> New</th>
</tr>
</thead>
<tbody>
{% for workout in workouts %}
<tr>
<td>
<a href="/rowers/workout/sporttracksimport/{{ workout|lookup:'id' }}/">Import</a></td>
<a href="/rowers/workout/sporttracksimport/{{ workout|lookup:'id' }}/">Import</a></td>
<td>{{ workout|lookup:'name' }}</td>
<td>{{ workout|lookup:'starttime' }}</td>
<td>{{ workout|lookup:'duration' }} </td>
<td>{{ workout|lookup:'distance' }} m</td>
<td>{{ workout|lookup:'type' }}</td>
<td>{{ workout|lookup:'new' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p> No workouts found </p>
{% endif %}
</table>
</div>
{% else %}
<p> No workouts found </p>
{% endif %}
{% endblock %}
+31 -31
View File
@@ -6,47 +6,47 @@
{% block content %}
<h1>Available on Strava</h1>
{% if data %}
<table width="70%" class="listtable">
{% if workouts %}
<div class="grid_2 alpha ">
<a href="/rowers/workout/stravaimport/all/" class="button gray">Import all NEW</a>
</div>
<div class="grid_10 omega">
<p>This imports all workouts that have not been imported to rowsandall.com.
The action may take a longer time to process, so please be patient. Click on Import in the list below to import an individual workout.
</p>
</div>
<div class="grid_12 alpha">
<table width="70%" class="listtable">
<thead>
<tr>
<th> Import </th>
<th> Duration </th>
<th> Type</th>
<th> Date</th>
<th> Distance </th>
<th> Name</th>
<th> Date</th>
<th> Duration </th>
<th> Distance </th>
<th> Type</th>
<th> New</th>
</tr>
</thead>
<tbody>
{% for workout in data %}
{% for workout in workouts %}
<tr>
{% for key,value in workout.items %}
{% if key == "start_date" %}
<td>{{ value }}</td>
{% endif %}
{% if key == "name" %}
<td>{{ value }}</td>
{% endif %}
{% if key == "type" %}
<td>{{ value }}</td>
{% endif %}
{% if key == "distance" %}
<td>{{ value }}m</td>
{% endif %}
{% if key == "elapsed_time" %}
<td>{{ value }}</td>
{% endif %}
{% if key == "id" %}
<td><a href="/rowers/workout/stravaimport/{{ value }}/">Import</a></td>
{% endif %}
{% endfor %}
<td>
<a href="/rowers/workout/stravaimport/{{ workout|lookup:'id' }}/">Import</a></td>
<td>{{ workout|lookup:'name' }}</td>
<td>{{ workout|lookup:'starttime' }}</td>
<td>{{ workout|lookup:'duration' }} </td>
<td>{{ workout|lookup:'distance' }} m</td>
<td>{{ workout|lookup:'type' }}</td>
<td>{{ workout|lookup:'new' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p> No workouts found </p>
{% endif %}
</table>
</div>
{% else %}
<p> No workouts found </p>
{% endif %}
{% endblock %}
+5
View File
@@ -170,6 +170,7 @@ urlpatterns = [
url(r'^workout/(?P<id>\d+)/comment$',views.workout_comment_view),
url(r'^workout/(?P<id>\d+)/emailtcx$',views.workout_tcxemail_view),
url(r'^workout/(?P<id>\d+)/emailcsv$',views.workout_csvemail_view),
url(r'^workout/(?P<id>\d+)/csvtoadmin$',views.workout_csvtoadmin_view),
url(r'^workout/compare/(?P<id>\d+)/$',views.workout_comparison_list),
url(r'^workout/compare2/(?P<id1>\d+)/(?P<id2>\d+)/(?P<xparam>\w+.*)/(?P<yparam>\w+.*)/$',views.workout_comparison_view),
url(r'^workout/compare/(?P<id>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\w+.*)$',views.workout_comparison_list),
@@ -211,9 +212,12 @@ urlpatterns = [
url(r'^workout/c2import/$',views.workout_c2import_view),
url(r'^workout/stravaimport/$',views.workout_stravaimport_view),
url(r'^workout/c2import/(?P<c2id>\d+)/$',views.workout_getc2workout_view),
url(r'^workout/c2import/all/$',views.workout_getc2workout_all),
url(r'^workout/stravaimport/(?P<stravaid>\d+)/$',views.workout_getstravaworkout_view),
url(r'^workout/stravaimport/all/$',views.workout_getstravaworkout_all),
url(r'^workout/sporttracksimport/$',views.workout_sporttracksimport_view),
url(r'^workout/sporttracksimport/(?P<sporttracksid>\d+)/$',views.workout_getsporttracksworkout_view),
url(r'^workout/sporttracksimport/all/$',views.workout_getsporttracksworkout_all),
url(r'^workout/runkeeperimport/$',views.workout_runkeeperimport_view),
url(r'^workout/runkeeperimport/(?P<runkeeperid>\d+)/$',views.workout_getrunkeeperworkout_view),
url(r'^workout/underarmourimport/$',views.workout_underarmourimport_view),
@@ -274,6 +278,7 @@ urlpatterns = [
url(r'^videos', TemplateView.as_view(template_name='videos.html'),name='videos'),
url(r'^analysis', TemplateView.as_view(template_name='analysis.html'),name='analysis'),
url(r'^promembership', TemplateView.as_view(template_name='promembership.html'),name='promembership'),
url(r'^paypaltest', TemplateView.as_view(template_name='paypaltest.html'),name='paypaltest'),
url(r'^legal', TemplateView.as_view(template_name='legal.html'),name='legal'),
url(r'^register$',views.rower_register_view),
url(r'^register/thankyou/$', TemplateView.as_view(template_name='registerthankyou.html'), name='registerthankyou'),
+15
View File
@@ -4,6 +4,21 @@ import numpy as np
lbstoN = 4.44822
def uniqify(seq, idfun=None):
# order preserving
if idfun is None:
def idfun(x): return x
seen = {}
result = []
for item in seq:
marker = idfun(item)
# in old Python versions:
# if seen.has_key(marker)
# but in new ones:
if marker in seen: continue
seen[marker] = 1
result.append(item)
return result
def serialize_list(value,token=','):
assert(isinstance(value, list) or isinstance(value, tuple) or isinstance(value,np.ndarray))
+286 -27
View File
@@ -267,7 +267,7 @@ def splitstdata(lijst):
return [np.array(t),np.array(latlong)]
from utils import geo_distance,serialize_list,deserialize_list
from utils import geo_distance,serialize_list,deserialize_list,uniqify
from rowers.models import checkworkoutuser
# Check if a user is a Coach member
@@ -1150,6 +1150,41 @@ def workout_csvemail_view(request,id=0):
return response
# Get Workout CSV file and send it to user's email address
@login_required()
def workout_csvtoadmin_view(request,id=0):
message = ""
r = Rower.objects.get(user=request.user)
try:
w = Workout.objects.get(id=id)
except Workout.DoesNotExist:
raise Http404("Workout doesn't exist")
csvfile = w.csvfilename
if settings.DEBUG:
res = handle_sendemailcsv.delay(
'Sander',
'Roosendaal',
'roosendaalsander@gmail.com',csvfile)
else:
res = queuehigh.enqueue(
handle_sendemailcsv,
'Sander',
'Roosendaal',
'roosendaalsander@gmail.com',
csvfile)
successmessage = "The CSV file was sent to the site admin per email"
messages.info(request,successmessage)
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
})
response = HttpResponseRedirect(url)
return response
# Send workout to TP
@login_required()
def workout_tp_upload_view(request,id=0):
@@ -1310,8 +1345,10 @@ def workout_c2_upload_view(request,id=0):
except C2NoTokenError:
return HttpResponseRedirect("/rowers/me/c2authorize/")
if message:
if message and c2id <=0:
messages.error(request,message)
elif message:
messages.info(request,message)
url = reverse(workout_export_view,
@@ -1499,6 +1536,8 @@ def workout_sporttracks_upload_view(request,id=0):
sporttracksid = sporttracksstuff.getidfromresponse(response)
w.uploadedtosporttracks = sporttracksid
w.save()
message = "Upload to SportTracks was successful"
messages.info(message)
url = "/rowers/workout/"+str(w.id)+"/export"
return HttpResponseRedirect(url)
else:
@@ -3111,7 +3150,7 @@ def boxplot_view(request,userid=0,
})
else:
return HttpResponse("Form is not valid")
if request.method == 'POST' and 'ids' in request.session:
elif request.method == 'POST' and 'ids' in request.session:
chartform = BoxPlotChoiceForm(request.POST)
if chartform.is_valid():
plotfield = chartform.cleaned_data['yparam']
@@ -3124,7 +3163,18 @@ def boxplot_view(request,userid=0,
workstrokesonly = not includereststrokes
ids = request.session['ids']
request.session['ids'] = ids
workouts = [Workout.objects.get(id=id) for id in ids]
workouts = dataprep.get_workouts(ids,userid)
if not workouts:
message = 'Error: Workouts in session storage do not belong to this user.'
messages.error(request,message)
url = reverse(user_boxplot_select,
kwargs={
'userid':userid,
}
)
return HttpResponseRedirect(url)
# workouts = [Workout.objects.get(id=id) for id in ids]
labeldict = {
int(w.id): w.__unicode__() for w in workouts
@@ -4344,6 +4394,10 @@ def cumstats(request,theuser=0,
def workout_stats_view(request,id=0,message="",successmessage=""):
r = Rower.objects.get(user=request.user)
try:
w = Workout.objects.get(id=id)
except Workout.DoesNotExist:
raise Http404("Workout doesn't exist")
workstrokesonly = True
if request.method == 'POST' and 'workstrokesonly' in request.POST:
@@ -4412,9 +4466,13 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
pwr4 = datadf['power']**(4)
normp = (pwr4.mean())**(0.25)
if not np.isnan(normp):
intensityfactor = datadf['power'].mean()/float(r.ftp)
intensityfactor = normp/float(r.ftp)
tss = 100.*((duration*normp*intensityfactor)/(3600.*r.ftp))
ftp = float(r.ftp)
if w.workouttype == 'water':
ftp = ftp*(100.-r.otwslack)/100.
intensityfactor = datadf['power'].mean()/float(ftp)
intensityfactor = normp/float(ftp)
tss = 100.*((duration*normp*intensityfactor)/(3600.*ftp))
if not np.isnan(tss):
otherstats['tss'] = {
@@ -5249,6 +5307,10 @@ def workout_add_otw_powerplot_view(request,id):
r.pw_tr,r.pw_an])/r.ftp
ftp = r.ftp
if w.workouttype == 'water':
ftp = ftp*(100.-r.otwslack)/100.
hrpwrdata = {
'hrmax':r.max,
'hrut2':r.ut2,
@@ -5256,7 +5318,7 @@ def workout_add_otw_powerplot_view(request,id):
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
'ftp':ftp,
'powerperc':serialize_list(powerperc),
'powerzones':serialize_list(r.powerzones),
}
@@ -5306,6 +5368,9 @@ def workout_add_piechart_view(request,id):
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
ftp = float(r.ftp)
if w.workouttype == 'water':
ftp = ftp*(100.-r.otwslack)/100.
hrpwrdata = {
'hrmax':r.max,
@@ -5314,7 +5379,7 @@ def workout_add_piechart_view(request,id):
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
'ftp':ftp,
'powerperc':serialize_list(powerperc),
'powerzones':serialize_list(r.powerzones),
}
@@ -5364,6 +5429,9 @@ def workout_add_power_piechart_view(request,id):
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
ftp = float(r.ftp)
if w.workouttype == 'water':
ftp = ftp*(100.-r.otwslack)/100.
hrpwrdata = {
'hrmax':r.max,
@@ -5372,7 +5440,7 @@ def workout_add_power_piechart_view(request,id):
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
'ftp':ftp,
'powerperc':serialize_list(powerperc),
'powerzones':serialize_list(r.powerzones),
}
@@ -5419,6 +5487,9 @@ def workout_add_timeplot_view(request,id):
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
ftp = float(r.ftp)
if w.workouttype == 'water':
ftp = ftp*(100.-r.otwslack)/100.
hrpwrdata = {
'hrmax':r.max,
@@ -5427,7 +5498,7 @@ def workout_add_timeplot_view(request,id):
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
'ftp':ftp,
'powerperc':serialize_list(powerperc),
'powerzones':serialize_list(r.powerzones),
}
@@ -5476,6 +5547,9 @@ def workout_add_distanceplot_view(request,id):
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
ftp = float(r.ftp)
if w.workouttype == 'water':
ftp = ftp*(100.-r.otwslack)/100.
hrpwrdata = {
'hrmax':r.max,
@@ -5484,7 +5558,7 @@ def workout_add_distanceplot_view(request,id):
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
'ftp':ftp,
'powerperc':serialize_list(powerperc),
'powerzones':serialize_list(r.powerzones),
}
@@ -5531,6 +5605,9 @@ def workout_add_distanceplot2_view(request,id):
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
ftp = float(r.ftp)
if w.workouttype == 'water':
ftp = ftp*(100.-r.otwslack)/100.
hrpwrdata = {
'hrmax':r.max,
@@ -5539,7 +5616,7 @@ def workout_add_distanceplot2_view(request,id):
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
'ftp':ftp,
'powerperc':serialize_list(powerperc),
'powerzones':serialize_list(r.powerzones),
}
@@ -5588,6 +5665,9 @@ def workout_add_timeplot2_view(request,id):
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
ftp = float(r.ftp)
if w.workouttype == 'water':
ftp = ftp*(100.-r.otwslack)/100.
hrpwrdata = {
'hrmax':r.max,
@@ -5596,7 +5676,7 @@ def workout_add_timeplot2_view(request,id):
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
'ftp':ftp,
'powerperc':serialize_list(powerperc),
'powerzones':serialize_list(r.powerzones),
}
@@ -5640,10 +5720,33 @@ def workout_stravaimport_view(request,message=""):
url = reverse(workouts_view)
return HttpResponseRedirect(url)
else:
data = res.json()
workouts = []
r = Rower.objects.get(user=request.user)
stravaids = [int(item['id']) for item in res.json()]
knownstravaids = uniqify([
w.uploadedtostrava for w in Workout.objects.filter(user=r)
])
newids = [stravaid for stravaid in stravaids if not stravaid in knownstravaids]
for item in res.json():
d = int(float(item['distance']))
i = item['id']
if i in knownstravaids:
nnn = ''
else:
nnn = 'NEW'
n = item['name']
ttot = str(datetime.timedelta(seconds=int(float(item['elapsed_time']))))
s = item['start_date']
r = item['type']
keys = ['id','distance','duration','starttime','type','name','new']
values = [i,d,ttot,s,r,n,nnn]
res = dict(zip(keys,values))
workouts.append(res)
return render(request,'strava_list_import.html',
{'data':data,
{'workouts':workouts,
'teams':get_my_teams(request.user),
})
@@ -5753,15 +5856,25 @@ def workout_sporttracksimport_view(request,message=""):
return HttpResponseRedirect(url)
else:
workouts = []
r = Rower.objects.get(user=request.user)
stids = [int(getidfromsturi(item['uri'])) for item in res.json()['items']]
knownstids = uniqify([
w.uploadedtosporttracks for w in Workout.objects.filter(user=r)
])
newids = [stid for stid in stids if not stid in knownstids]
for item in res.json()['items']:
d = int(float(item['total_distance']))
i = getidfromsturi(item['uri'])
i = int(getidfromsturi(item['uri']))
if i in knownstids:
nnn = ''
else:
nnn = 'NEW'
n = item['name']
ttot = str(datetime.timedelta(seconds=int(float(item['duration']))))
s = item['start_time']
r = item['type']
keys = ['id','distance','duration','starttime','type','name']
values = [i,d,ttot,s,r,n]
keys = ['id','distance','duration','starttime','type','name','new']
values = [i,d,ttot,s,r,n,nnn]
res = dict(zip(keys,values))
workouts.append(res)
return render(request,'sporttracks_list_import.html',
@@ -5813,6 +5926,57 @@ def c2listdebug_view(request,message=""):
'teams':get_my_teams(request.user),
})
# Import all unknown workouts available on Concept2 logbook
@login_required()
def workout_getc2workout_all(request,message=""):
try:
thetoken = c2_open(request.user)
except C2NoTokenError:
return HttpResponseRedirect("/rowers/me/c2authorize/")
res = c2stuff.get_c2_workout_list(request.user)
if (res.status_code != 200):
message = "Something went wrong in workout_c2import_view (C2 token refresh)"
messages.error(request,message)
else:
r = Rower.objects.get(user=request.user)
c2ids = [item['id'] for item in res.json()['data']]
knownc2ids = uniqify([
w.uploadedtoc2 for w in Workout.objects.filter(user=r)
])
newids = [c2id for c2id in c2ids if not c2id in knownc2ids]
for c2id in newids:
res = c2stuff.get_c2_workout(request.user,c2id)
if (res.status_code == 200):
data = res.json()['data']
splitdata = None
if 'workout' in data:
if 'splits' in data['workout']:
splitdata = data['workout']['splits']
if 'intervals' in data['workout']:
splitdata = data['workout']['intervals']
# Check if workout has stroke data, and get the stroke data
if data['stroke_data']:
res2 = c2stuff.get_c2_workout_strokes(request.user,c2id)
# We have stroke data
if res2.status_code == 200:
strokedata = pd.DataFrame.from_dict(res2.json()['data'])
# create the workout
id,message = add_workout_from_strokedata(
request.user,c2id,data,strokedata,
source='c2')
w = Workout.objects.get(id=id)
w.uploadedtoc2=c2id
w.save()
if message:
messages.error(request,message)
url = reverse(workouts_view)
return HttpResponseRedirect(url)
# List of workouts available on Concept2 logbook - for import
@login_required()
def workout_c2import_view(request,message=""):
@@ -5833,6 +5997,12 @@ def workout_c2import_view(request,message=""):
return HttpResponseRedirect(url)
else:
workouts = []
r = Rower.objects.get(user=request.user)
c2ids = [item['id'] for item in res.json()['data']]
knownc2ids = uniqify([
w.uploadedtoc2 for w in Workout.objects.filter(user=r)
])
newids = [c2id for c2id in c2ids if not c2id in knownc2ids]
for item in res.json()['data']:
d = item['distance']
i = item['id']
@@ -5841,8 +6011,12 @@ def workout_c2import_view(request,message=""):
r = item['type']
s2 = item['source']
c = item['comments']
keys = ['id','distance','duration','starttime','rowtype','source','comment']
values = [i,d,ttot,s,r,s2,c]
if i in knownc2ids:
nnn = ''
else:
nnn = 'NEW'
keys = ['id','distance','duration','starttime','rowtype','source','comment','new']
values = [i,d,ttot,s,r,s2,c,nnn]
res = dict(zip(keys,values))
workouts.append(res)
@@ -5948,6 +6122,69 @@ def workout_getsporttracksworkout_view(request,sporttracksid):
})
return HttpResponseRedirect(url)
# Imports all new workouts from SportTracks
@login_required()
def workout_getsporttracksworkout_all(request):
res = sporttracksstuff.get_sporttracks_workout_list(request.user)
if (res.status_code == 200):
r = Rower.objects.get(user=request.user)
stids = [int(getidfromsturi(item['uri'])) for item in res.json()['items']]
knownstids = uniqify([
w.uploadedtosporttracks for w in Workout.objects.filter(user=r)
])
newids = [stid for stid in stids if not stid in knownstids]
for sporttracksid in newids:
res = sporttracksstuff.get_sporttracks_workout(
request.user,sporttracksid)
data = res.json()
id,message = add_workout_from_stdata(request.user,sporttracksid,data)
if id==0:
messages.error(request,message)
else:
w = Workout.objects.get(id=id)
w.uploadedtosporttracks=sporttracksid
w.save()
url = reverse(workouts_view)
return HttpResponseRedirect(url)
# Imports all new workouts from SportTracks
@login_required()
def workout_getstravaworkout_all(request):
res = stravastuff.get_strava_workout_list(request.user)
if (res.status_code == 200):
r = Rower.objects.get(user=request.user)
stravaids = [int(item['id']) for item in res.json()]
knownstravaids = uniqify([
w.uploadedtostrava for w in Workout.objects.filter(user=r)
])
newids = [stravaid for stravaid in stravaids if not stravaid in knownstravaids]
for stravaid in newids:
res = stravastuff.get_strava_workout(request.user,stravaid)
strokedata = res[1]
data = res[0]
id,message = add_workout_from_strokedata(request.user,stravaid,data,strokedata,
source='strava',
workoutsource='strava')
if id==0:
messages.error(request,message)
else:
w = Workout.objects.get(id=id)
w.uploadedtostrava=stravaid
w.save()
url = reverse(workouts_view)
return HttpResponseRedirect(url)
# Imports a workout from Concept2
@login_required()
def workout_getc2workout_view(request,c2id):
@@ -6180,6 +6417,10 @@ def workout_upload_view(request,
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
ftp = float(r.ftp)
if w.workouttype == 'water':
ftp = ftp*(100.-r.otwslack)/100.
hrpwrdata = {
'hrmax':r.max,
'hrut2':r.ut2,
@@ -6187,7 +6428,7 @@ def workout_upload_view(request,
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
'ftp':ftp,
'powerperc':serialize_list(powerperc),
'powerzones':serialize_list(r.powerzones),
}
@@ -6424,6 +6665,10 @@ def team_workout_upload_view(request,message="",
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
ftp = float(r.ftp)
if w.workouttype == 'water':
ftp = ftp*(100.-r.otwslack)/100.
hrpwrdata = {
'hrmax':r.max,
'hrut2':r.ut2,
@@ -6431,7 +6676,7 @@ def team_workout_upload_view(request,message="",
'hrat':r.at,
'hrtr':r.tr,
'hran':r.an,
'ftp':r.ftp,
'ftp':ftp,
'powerperc':serialize_list(powerperc),
'powerzones':serialize_list(r.powerzones),
}
@@ -6633,9 +6878,13 @@ def workout_summary_restore_view(request,id,message="",successmessage=""):
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
ftp = float(r.ftp)
if row.workouttype == 'water':
ftp = ftp*(100.-r.otwslack)/100.
rr = rrower(hrmax=r.max,hrut2=r.ut2,
hrut1=r.ut1,hrat=r.at,
hrtr=r.tr,hran=r.an,ftp=r.ftp,
hrtr=r.tr,hran=r.an,ftp=ftp,
powerperc=powerperc,powerzones=r.powerzones)
rowdata = rdata(f1,rower=rr)
if rowdata == 0:
@@ -6744,9 +6993,13 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
ftp = float(r.ftp)
if row.workouttype == 'water':
ftp = ftp*(100.-r.otwslack)/100.
rr = rrower(hrmax=r.max,hrut2=r.ut2,
hrut1=r.ut1,hrat=r.at,
hrtr=r.tr,hran=r.an,ftp=r.ftp,
hrtr=r.tr,hran=r.an,ftp=ftp,
powerperc=powerperc,powerzones=r.powerzones)
rowdata = rdata(f1,rower=rr)
if rowdata == 0:
@@ -7065,6 +7318,7 @@ def rower_edit_view(request,message=""):
if powerform.is_valid():
cd = powerform.cleaned_data
ftp = cd['ftp']
otwslack = cd['otwslack']
try:
r = Rower.objects.get(user=request.user)
powerfrac = 100*np.array([r.pw_ut2,
@@ -7072,6 +7326,7 @@ def rower_edit_view(request,message=""):
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
r.ftp = max(min(ftp,650),50)
r.otwslack = max(min(otwslack,50),0)
ut2,ut1,at,tr,an = (r.ftp*powerfrac/100.).astype(int)
r.pw_ut2 = ut2
r.pw_ut1 = ut1
@@ -7079,7 +7334,7 @@ def rower_edit_view(request,message=""):
r.pw_tr = tr
r.pw_an = an
r.save()
message = "Functional Threshold Value Changed"
message = "FTP and/or OTW slack values changed."
messages.info(request,message)
url = reverse(rower_edit_view)
response = HttpResponseRedirect(url)
@@ -7428,9 +7683,13 @@ def strokedatajson(request,id):
r.pw_at,
r.pw_tr,r.pw_an])/r.ftp
ftp = float(r.ftp)
if row.workouttype == 'water':
ftp = ftp*(100.-r.otwslack)/100.
rr = rrower(hrmax=r.max,hrut2=r.ut2,
hrut1=r.ut1,hrat=r.at,
hrtr=r.tr,hran=r.an,ftp=r.ftp,
hrtr=r.tr,hran=r.an,ftp=ftp,
powerperc=powerperc,powerzones=r.powerzones)
rowdata = rdata(row.csvfilename,rower=rr).df