Private
Public Access
1
0

Merge branch 'release/v16.2.0'

This commit is contained in:
Sander Roosendaal
2021-05-12 09:29:09 +02:00
20 changed files with 305 additions and 283 deletions
+83
View File
@@ -0,0 +1,83 @@
from requests_oauthlib import OAuth1Session, OAuth1
import hmac
import hashlib
import requests
from urllib.parse import quote_plus as rawurlencode
import logging
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
payload = {
'workoutName': '30min at threshold',
'sport': 'GENERIC',
'description': 'Uploaded from Rowsandall.com',
'estimatedDurationInSecs': 1800,
'estimatedDistanceInMeters': 6936,
'workoutProvider': 'Rowsandall.com',
'workoutSourceId': 'Rowsandall.com',
'steps': [
{
'type': 'Step', 'stepOrder': 0,
'repeatType': 'Step',
'repeatValue': 1,
'intensity': 'Active',
'description': '0',
'durationType': 'TIME',
'durationValue': 1800,
'durationValueType': '',
'targetType': 'Power',
'targetValue': 1226,
'targetValueLow': 0, 'targetValueHigh': (0,)
}
]
}
payload = {}
oauth_consumer_key = b'ca29ba5e-6868-4468-987d-4ee60a1f04bf'
oauth_consumer_secret = b'SKRqjML9mOBV7BcPpN7LsbuDNDtvLOvRiyo'
oauth_token = b'79454eab-bf82-4329-9de2-82a6bd911498'
oauth_token_secret = b'DihdHJ2ThEdbsyoStpPTEmYh5F52L697HhD'
authheaders = OAuth1(client_key=oauth_consumer_key,
client_secret=oauth_consumer_secret,
resource_owner_key=oauth_token,
resource_owner_secret=oauth_token_secret,
signature_method='HMAC-SHA1',
#encoding='base64'
)
url = 'https://apis.garmin.com/training-api/workout/'
response = requests.post(url,payload,auth=authheaders)
# build base_string
base_string1 = b'POST&https%3A%2F%2Fapis.garmin.com%2Ftraining-api%2Fworkout%2F&description%3DUploaded%2520from%2520Rowsandall.com%26estimatedDistanceInMeters%3D6936%26estimatedDurationInSecs%3D1800%26oauth_consumer_key%3Dca29ba5e-6868-4468-987d-4ee60a1f04bf%26oauth_nonce%3D163208869057942765101620205416%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1620205416%26oauth_token%3D79454eab-bf82-4329-9de2-82a6bd911498%26oauth_version%3D1.0%26sport%3DGENERIC%26steps%3Ddescription%26steps%3DdurationType%26steps%3DdurationValue%26steps%3DdurationValueType%26steps%3Dintensity%26steps%3DrepeatType%26steps%3DrepeatValue%26steps%3DstepOrder%26steps%3DtargetType%26steps%3DtargetValue%26steps%3DtargetValueHigh%26steps%3DtargetValueLow%26steps%3Dtype%26workoutName%3D30min%2520at%2520threshold%26workoutProvider%3DRowsandall.com%26workoutSourceId%3DRowsandall.com'
base_stringa = b'POST&https%3A%2F%2Fapis.garmin.com%2Ftraining-api%2Fworkout%2F&oauth_consumer_key%3Dca29ba5e-6868-4468-987d-4ee60a1f04bf%26oauth_nonce%3D90559685229655402871620208938%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1620208938%26oauth_token%3D79454eab-bf82-4329-9de2-82a6bd911498%26oauth_version%3D1.0'
base_string2 = b'POST&https%3A%2F%2Fapis.garmin.com%2Ftraining-api%2Fworkout%2F&oauth_consumer_key%3Dca29ba5e-6868-4468-987d-4ee60a1f04bf%26oauth_nonce%3DIyk9Ambokd2%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1620138340%26oauth_token%3D673806b7-aa7b-4064-8290-2dd1b0236ae6%26oauth_version%3D1.0'
base_string3 = b'POST&https%3A%2F%2Fapis.garmin.com%2Ftraining-api%2Fworkout&oauth_consumer_key%3Dca29ba5e-6868-4468-987d-4ee60a1f04bf%26oauth_nonce%3D90559685229655402871620208938%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1620208938%26oauth_token%3D79454eab-bf82-4329-9de2-82a6bd911498%26oauth_version%3D1.0'
auth = b'&'.join([oauth_consumer_secret,oauth_token_secret])
signingKey = bytes(rawurlencode(oauth_consumer_secret) + "&" + rawurlencode(oauth_token_secret),'utf-8')
print(auth)
print(signingKey)
print(base_string3)
sig = hmac.new(signingKey, base_string3, hashlib.sha1)
print(response.status_code)
print(response.text)
print(sig.hexdigest().encode())
+21 -13
View File
@@ -238,8 +238,8 @@ def create_async_workout(alldata,user,c2id):
duration = dataprep.totaltime_sec_to_string(totaltime)
try:
timezone_str = tz(data['timezone'])
except:
timezone_str = data['timezone']
except: # pragma: no cover
timezone_str = 'UTC'
workoutdate = startdatetime.astimezone(
@@ -848,6 +848,9 @@ def get_workout(user,c2id,do_async=False):
data = s.json()['data']
splitdata = None
#with open('c2temp.json','w') as f:
# f.write(json.dumps(s.json()))
# print(s.json())
if 'workout' in data:
if 'splits' in data['workout']: # pragma: no cover
@@ -1076,20 +1079,21 @@ def add_workout_from_data(user,importid,data,strokedata,
except: # pragma: no cover
comments = ' '
try:
thetimezone = tz(data['timezone'])
except:
thetimezone = 'UTC'
thetimezone = pytz.timezone(data['timezone'])
r = Rower.objects.get(user=user)
try:
rowdatetime = iso8601.parse_date(data['date_utc'])
thetimezone = 'UTC'
except KeyError: # pragma: no cover
rowdatetime = iso8601.parse_date(data['start_date'])
rowdatetime = rowdatetime.make_aware(thetimezone)
except ParseError: # pragma: no cover
rowdatetime = iso8601.parse_date(data['date'])
rowdatetime = rowdatetime.make_aware(thetimezone)
try:
@@ -1119,7 +1123,9 @@ def add_workout_from_data(user,importid,data,strokedata,
cum_time = res[0]
lapidx = res[1]
starttimeunix = starttimeunix - cum_time.max()
totaltime = data['time']/10.
starttimeunix = starttimeunix - totaltime
unixtime = cum_time+starttimeunix
# unixtime[0] = starttimeunix
@@ -1161,7 +1167,6 @@ def add_workout_from_data(user,importid,data,strokedata,
velo = 1000./pace
pace = 500./velo
# save csv
# Create data frame with all necessary data to write to csv
df = pd.DataFrame({'TimeStamp (sec)':unixtime,
@@ -1223,15 +1228,18 @@ def add_workout_from_data(user,importid,data,strokedata,
w = Workout.objects.get(id=id)
local_tz = pytz.timezone(data['timezone'])
# local_tz = pytz.timezone(thetimezone)
w.startdatetime = w.startdatetime.astimezone(local_tz)
w.starttime = w.startdatetime.strftime('%H:%M:%S')
w.timezone = local_tz
w.duration = dataprep.totaltime_sec_to_string(totaltime)
w.distance = totaldist
w.startdatetime = rowdatetime
w.starttime = rowdatetime.time()
w.date = rowdatetime.date()
w.save()
return id,message
+15 -1
View File
@@ -1692,6 +1692,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
try:
latavg = row.df[' latitude'].mean()
lonavg = row.df[' longitude'].mean()
@@ -1717,6 +1718,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
except KeyError:
timezone_str = r.defaulttimezone
duration = totaltime_sec_to_string(totaltime)
workoutdate = workoutstartdatetime.astimezone(
@@ -1785,6 +1787,8 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
except ValidationError:
return (0,'Unable to create your workout')
if privacy == 'visible':
ts = Team.objects.filter(rower=r)
for t in ts:
@@ -2013,6 +2017,16 @@ def new_workout_from_file(r, f2,
return -1, message, f2
# Some people try to upload Concept2 logbook summaries
if fileformat == 'imageformat': # pragma: no cover
os.remove(f2)
message = "You cannot upload image files here"
return (0, message, f2)
if fileformat == 'json': # pragma: no cover
os.remove(f2)
message = "JSON format not supported in direct upload"
return (0, message, f2)
if fileformat == 'c2log':
os.remove(f2)
message = "This summary does not contain stroke data. Use the files containing stroke by stroke data."
@@ -2435,7 +2449,7 @@ def getsmallrowdata_db(columns, ids=[], doclean=True,workstrokesonly=True,comput
else:
try:
df = pd.read_parquet(csvfilenames[0],columns=columns)
except (OSError,ArrowInvalid):
except (OSError,ArrowInvalid,IndexError):
rowdata,row = getrowdata(id=ids[0])
if rowdata and len(rowdata.df): # pragma: no cover
data = dataprep(rowdata.df,id=ids[0],bands=True,otwpower=True,barchart=True)
-26
View File
@@ -994,32 +994,6 @@ class PowerIntervalUpdateForm(forms.Form):
activeminutesmin = forms.IntegerField(required=False,initial=0,widget=forms.HiddenInput())
activeminutesmax = forms.IntegerField(required=False,initial=0,widget=forms.HiddenInput())
# Form used to update interval stats
class IntervalUpdateForm(forms.Form):
def __init__(self, *args, **kwargs):
typechoices = (
(1,'single time'),
(2,'single distance'),
(3,'rest (time based)'),
(3,'rest (distance based)'),
(4,'work (time based)'),
(5,'work (distance based)'),
)
aantal = int(kwargs.pop('aantal'))
super(IntervalUpdateForm, self).__init__(*args, **kwargs)
for i in range(aantal):
self.fields['intervalt_%s' % i] = forms.DurationField(label='Time '+str(i+1))
self.fields['intervald_%s' % i] = forms.IntegerField(label='Distance '+str(i+1))
self.fields['type_%s' % i] = forms.ChoiceField(choices=typechoices,
required=True,
initial=4,
label = 'Type '+str(i+1))
self.fields['intervalt_%s' % i].widget.attrs['style'] = 'width:76px; height: 16px;'
self.fields['intervald_%s' % i].widget.attrs['style'] = 'width:76px; height: 16px;'
self.fields['type_%s' % i].widget.attrs['style'] = 'width:156px; height: 22px;'
self.fields['intervald_%s' % i].widget = forms.TimeInput(format='%H:%M:%S.%f')
boattypes = mytypes.boattypes
workouttypes = mytypes.workouttypes
+4 -13
View File
@@ -2,6 +2,7 @@ from rowers.imports import *
import datetime
import requests
from requests import Session, Request
from requests_oauthlib import OAuth1,OAuth1Session
from requests_oauthlib.oauth1_session import TokenRequestDenied
from requests import Request, Session
@@ -302,27 +303,17 @@ def ps_to_garmin(ps,r):
lijst.append(gstep)
payload['steps'] = lijst
url = 'https://apis.garmin.com/training-api/workout/'
garmin = OAuth1Session(oauth_data['client_id'],
client_secret=oauth_data['client_secret'],
resource_owner_key=r.garmintoken,
resource_owner_secret=r.garminrefreshtoken,
signature_method='HMAC-SHA1'
signature_method='HMAC-SHA1',
encoding='base64'
)
url = 'https://apis.garmin.com/training-api/workout/'
garminauth = OAuth1(
client_key=oauth_data['client_id'],
client_secret=oauth_data['client_secret'],
resource_owner_key=r.garmintoken,
resource_owner_secret=r.garminrefreshtoken,
signature_method='HMAC-SHA1'
)
response = garmin.post(url,data=payload)
#POST /training-api/workout?undefined HTTP/1.1
+4 -2
View File
@@ -571,7 +571,6 @@ def interactive_activitychart(workouts,startdate,enddate,stack='type',toolbar_lo
while d<=enddate:
dd = d.strftime('%d')
if totaldays<30:
dates.append(d.strftime('%m/%d'))
dates_sorting.append(d.strftime('%Y/%m/%d'))
@@ -582,7 +581,10 @@ def interactive_activitychart(workouts,startdate,enddate,stack='type',toolbar_lo
rscores.append(0)
trimps.append(0)
links.append('')
types.append('rower')
try:
types.append(types[0])
except IndexError:
types.append('rower')
try:
rowers.append(rowers[0])
+1 -1
View File
@@ -3158,7 +3158,7 @@ class Workout(models.Model):
timezone = models.CharField(default='UTC',
choices=timezones,
max_length=100)
distance = models.IntegerField(default=0,blank=True)
distance = models.IntegerField(default=0)
duration = models.TimeField(blank=True)
dragfactor = models.IntegerField(default=0,blank=True)
+10 -10
View File
@@ -21,7 +21,7 @@ workouttypes_ordered = collections.OrderedDict({
'c-boat':'Dutch C boat',
'churchboat':'Finnish Church boat',
'Ride':'Ride',
'Bike':'Bike',
'bike':'Bike',
'Run':'Run',
'NordicSki':'NordicSki',
'Swim':'Swim',
@@ -36,7 +36,7 @@ workouttypes_ordered = collections.OrderedDict({
'Kayaking':'Kayaking',
'Workout':'Workout',
'Yoga':'Yoga',
'bike':'Bike',
# 'bike':'Bike',
'other':'Other',
}
)
@@ -51,7 +51,7 @@ stravacollection = (
('water','Rowing'),
('rower','Rowing'),
('skierg','NordicSki'),
('Bike','Ride'),
('bike','Ride'),
('bikeerg','Ride'),
('dynamic','Rowing'),
('slides','Rowing'),
@@ -84,7 +84,7 @@ garmincollection = (
('water','ROWING'),
('rower','INDOOR_ROWING'),
('skierg','CROSS_COUNTRY_SKIING'),
('Bike','ROAD_BIKING'),
('bike','ROAD_BIKING'),
('bikeerg','INDOOR_CYCLING'),
('dynamic','INDOOR_ROWING'),
('slides','INDOOR_ROWING'),
@@ -117,7 +117,7 @@ fitcollection = (
('water','rowing'),
('rower','rowing'),
('skierg','cross_country_skiing'),
('Bike','cycling'),
('bike','cycling'),
('bikeerg','cycling'),
('dynamic','rowing'),
('slides','rowing'),
@@ -153,7 +153,7 @@ stcollection = (
('water','Rowing'),
('rower','Rowing'),
('skierg','Skiing:Nordic'),
('Bike','Cycling'),
('bike','Cycling'),
('bikeerg','Cycling'),
('dynamic','Rowing'),
('slides','Rowing'),
@@ -187,7 +187,7 @@ rkcollection = (
('water','Rowing'),
('rower','Rowing'),
('skierg','Cross-Country Skiing'),
('Bike','Cycling'),
('bike','Cycling'),
('bikeerg','Cycling'),
('dynamic','Rowing'),
('slides','Rowing'),
@@ -220,7 +220,7 @@ polarcollection = (
('water','Rowing'),
('rower','Rowing'),
('skierg','Skiing'),
('Bike','Cycling'),
('bike','Cycling'),
('bikeerg','Cycling'),
('dynamic','Rowing'),
('slides','Rowing'),
@@ -253,7 +253,7 @@ tpcollection = (
('water','rowing'),
('rower','rowing'),
('skierg','xc-ski'),
('Bike','bike'),
('bike','bike'),
('Bikeerg','bike'),
('dynamic','rowing'),
('slides','rowing'),
@@ -286,7 +286,7 @@ c2collection = (
('water','water'),
('rower','rower'),
('skierg','skierg'),
('Bike','bike'),
('bike','bike'),
('bikeerg','bike'),
('dynamic','dynamic'),
('slides','slides'),
+2 -1
View File
@@ -68,7 +68,8 @@ def validate_file_extension(value):
ext = os.path.splitext(value.name)[1]
valid_extensions = ['.tcx','.csv','.TCX','.gpx','.GPX',
'.CSV','.fit','.FIT','.zip','.ZIP',
'.gz','.GZ','.xls']
'.gz','.GZ','.xls',
'.jpg','.jpeg','.tiff','.png','.gif','.bmp']
if not ext in valid_extensions: # pragma: no cover
raise ValidationError(u'File not supported!')
+1 -1
View File
@@ -330,7 +330,7 @@ def create_async_workout(alldata,user,stravaid,debug=False):
starttime = rowdatetime.astimezone(
pytz.timezone(thetimezone)
).strftime('%H:%m:%S')
).strftime('%H:%M:%S')
totaltime = data['elapsed_time']
duration = dataprep.totaltime_sec_to_string(totaltime)
+15 -16
View File
@@ -419,7 +419,7 @@ def handle_c2_import_stroke_data(c2token,
duration = datetime.datetime.strptime(duration,'%H:%M:%S.%f').time()
try:
timezone_str = tz(workoutdata['timezone'])
timezone_str = workoutdata['timezone']
except:
timezone_str = 'UTC'
@@ -2991,7 +2991,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
c2id = data['id']
workouttype = data['type']
verified = data['verified']
startdatetime = iso8601.parse_date(data['date'])
startdatetime = iso8601.parse_date(data['date_utc'])
weightclass = data['weight_class']
try:
@@ -3013,21 +3013,18 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
totaltime = data['time']/10.
duration = totaltime_sec_to_string(totaltime)
starttimeunix = arrow.get(startdatetime).timestamp()-totaltime
startdatetime = arrow.get(starttimeunix)
try:
timezone_str = tz(data['timezone'])
except:
timezone_str = defaulttimezone
timezone = pytz.timezone(data['timezone'])
startdatetime = startdatetime.astimezone(timezone)
startdatetime = startdatetime.replace(tzinfo=None)
tz = pytz.timezone(timezone_str)
startdatetime = tz.localize(startdatetime)
workoutdate = startdatetime.astimezone(
pytz.timezone(timezone_str)
timezone
).strftime('%Y-%m-%d')
starttime = startdatetime.astimezone(
pytz.timezone(timezone_str)
timezone
).strftime('%H:%M:%S')
try:
@@ -3056,8 +3053,6 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
cum_time = res[0]
lapidx = res[1]
starttimeunix = arrow.get(startdatetime).timestamp()
starttimeunix = starttimeunix-cum_time.max()
unixtime = cum_time+starttimeunix
# unixtime[0] = starttimeunix
@@ -3134,6 +3129,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
'workouttype':workouttype,
'boattype':'1x',
'c2id':c2id,
'timezone':str(timezone)
}
session = requests.session()
@@ -3165,8 +3161,11 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
parkedids = []
with open('c2blocked.json','r') as c2blocked:
jsondata = json.load(c2blocked)
parkedids = jsondata['ids']
try:
jsondata = json.load(c2blocked)
parkedids = jsondata['ids']
except JSONDecodeError: # pragma: no cover
parkedids = []
newparkedids = [id for id in parkedids if id != newc2id]
with open('c2blocked.json','wt') as c2blocked:
@@ -3364,7 +3363,7 @@ def fetch_strava_workout(stravatoken,oauth_data,stravaid,csvfilename,userid,debu
comments = ' '
try:
thetimezone = tz(workoutsummary['timezone'])
thetimezone = workoutsummary['timezone']
except:
thetimezone = 'UTC'
-35
View File
@@ -111,9 +111,6 @@
{% for key,value in formvalues.items %}
<input type="hidden" name="{{ key }}" value="{{ value|safe }}">
{% endfor %}
{% for field in detailform %}
{{ field.as_hidden }}
{% endfor %}
<p>
<input type="submit" value="Save">
</p>
@@ -130,38 +127,6 @@
</p>
</li>
<li class="grid_2">
<h1>Detailed Summary Edit</h1>
<p>This is still experimental and there are known bugs. Use at your own risk. Nothing is stored permanently until you hit Save in the Updated Summary section. You can use the restore original button to restore the original values.</p>
<form enctype="multipart/form-data" action="/rowers/workout/{{ workout.id|encode }}/editintervals/" method="post">
<table width=100%>
<thead>
<tr>
<th>#</th><th>Time</th><th>Distance</th><th>Type</th>
</tr>
</thead>
<tbody>
{% for i in nrintervals|times %}
<tr>
<td>{{ i }}&nbsp;</td>
<td>
{% get_field_id i "intervalt_" detailform %}
</td>
<td>
{% get_field_id i "intervald_" detailform %}
</td>
<td>
{% get_field_id i "type_" detailform %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% csrf_token %}
<input type="hidden" name="nrintervals" value={{ nrintervals }}>
<input class="button" type="submit" value="Update">
</form>
</li>
<li class="grid_4">
<h1 id="howto">Interval Shorthand How-To</h1>
<p>This is a quick way to enter the intervals using a special mini-language.</p>
<p>You enter something like <em>8x500m/3min</em>, press "Update" and the site will interpret this for you and update the summary on the right. If you're happy with the result, press the green Save button to update the values. Nothing will be changed permanently until you hit Save.</p>
+2 -2
View File
@@ -559,7 +559,7 @@ def iterrows(df): # pragma: no cover
return df.iterrows()
@register.filter(name='times')
def times(number):
def times(number): # pragma: no cover
return range(number)
@register.simple_tag
@@ -567,7 +567,7 @@ def get_df_iloc(data,i,j): # pragma: no cover
return data.iloc(i,j)
@register.simple_tag
def get_field_id(id,s,form):
def get_field_id(id,s,form): # pragma: no cover
field_name = s+str(id)
return form.__getitem__(field_name)
+13 -1
View File
@@ -713,6 +713,13 @@ def mocked_requests(*args, **kwargs):
with open('rowers/tests/testdata/c2jsonworkoutdata.txt','r') as infile:
c2workoutdata = json.load(infile)
with open('rowers/tests/testdata/c2_timezone.json','r') as infile:
c2timezoneworkoutdata = json.load(infile)
with open('rowers/tests/testdata/c2_timezone2.json','r') as infile:
c2timezoneworkoutdata2 = json.load(infile)
with open('rowers/tests/testdata/c2jsonstrokedata.txt','r') as infile:
c2strokedata = json.load(infile)
@@ -1181,7 +1188,12 @@ def mocked_requests(*args, **kwargs):
if c2strokestester.match(args[0]):
return MockResponse(c2strokedata,200)
elif c2importtester.match(args[0]):
return MockResponse(c2workoutdata,200)
if '12' in args[0]:
return MockResponse(c2workoutdata,200)
elif '31' in args[0]:
return MockResponse(c2timezoneworkoutdata2,200)
else:
return MockResponse(c2timezoneworkoutdata,200)
elif c2workoutlisttester.match(args[0]):
return MockResponse(c2workoutlist,200)
elif 'access_token' in args[0]:
+1 -1
View File
@@ -427,7 +427,7 @@ workout bike
self.assertEqual(len(ws),1)
w = ws[0]
self.assertEqual(w.workouttype,'Bike')
self.assertEqual(w.workouttype,'bike')
+35
View File
@@ -158,6 +158,7 @@ class C2Objects(DjangoTestCase):
self.r.c2token = '12'
self.r.c2refreshtoken = 'ab'
self.r.tokenexpirydate = arrow.get(datetime.datetime.now()+datetime.timedelta(days=1)).datetime
self.r.defaulttimezone = 'Europe/Prague'
self.r.save()
self.c.login(username='john',password='koeinsloot')
@@ -261,6 +262,39 @@ class C2Objects(DjangoTestCase):
self.assertEqual(response.status_code, 200)
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
@patch('rowers.dataprep.create_engine')
def test_c2_import_tz(self, mock_get, mocked_sqlalchemy):
response = self.c.get('/rowers/workout/c2import/22/',follow=True)
self.assertRedirects(response,
expected_url='/rowers/workout/'+encoded2+'/edit/',
status_code=302,target_status_code=200)
self.assertEqual(response.status_code, 200)
w = Workout.objects.get(id=2)
self.assertEqual(w.timezone,'Europe/Prague')
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
@patch('rowers.dataprep.create_engine')
def test_c2_import_tz2(self, mock_get, mocked_sqlalchemy):
response = self.c.get('/rowers/workout/c2import/31/',follow=True)
self.assertRedirects(response,
expected_url='/rowers/workout/'+encoded2+'/edit/',
status_code=302,target_status_code=200)
self.assertEqual(response.status_code, 200)
w = Workout.objects.get(id=2)
self.assertEqual(w.timezone,'Europe/Amsterdam')
self.assertEqual(w.starttime.strftime("%H:%M:%S"),"20:04:56")
@patch('rowers.dataprep.create_engine')
def test_strokedata(self, mocked_sqlalchemy):
@@ -309,6 +343,7 @@ class C2Objects(DjangoTestCase):
res = tasks.handle_c2_async_workout(alldata,self.u.id,self.r.c2token,33991243,0,self.r.defaulttimezone)
self.assertEqual(res,1)
@override_settings(TESTING=True)
class C2ObjectsTokenExpired(DjangoTestCase):
def setUp(self):
+63 -16
View File
@@ -524,7 +524,6 @@ def get_strava_stream(r,metric,stravaid,series_type='time',fetchresolution='high
if metric=='power': # pragma: no cover
with open('data.txt', 'w') as outfile:
json.dump(s.json(), outfile)
print('saved to file')
for data in s.json():
y = None
@@ -589,18 +588,15 @@ def step_to_time_dist(step,avgspeed = 3.2,ftp=200,ftspm=25,ftv=3.7):
seconds = 0
distance = 0
rscore = 0
durationtype = step['durationType']
durationtype = step.get('durationType',0)
value = step.get('durationValue',0)
if step['durationValue'] == 0: # pragma: no cover
if value == 0: # pragma: no cover
return 0,0,0
try:
targettype = step['targetType']
except KeyError: # pragma: no cover
targettype = 0
targettype = step.get('targetType',0)
if durationtype == 'Time':
value = step['durationValue']
seconds = value/1000.
distance = avgspeed*seconds
rscore = 60.*seconds/3600.
@@ -616,6 +612,8 @@ def step_to_time_dist(step,avgspeed = 3.2,ftp=200,ftspm=25,ftv=3.7):
elif valuelow != 0 and valuehigh != 0: # pragma: no cover
distance = seconds*(valuelow+valuehigh)/2.
velomid = (valuelow+valuehigh)/2000.
else:
velomid = avgspeed
veloratio = (velomid/ftv)**(3.0)
rscoreperhour = 100.*veloratio
@@ -657,22 +655,21 @@ def step_to_time_dist(step,avgspeed = 3.2,ftp=200,ftspm=25,ftv=3.7):
rscore = 100*(avgpower/ftp)*seconds/3600.
return seconds,distance,rscore
elif durationtype == 'Distance': # pragma: no cover
value = step['durationValue']
elif durationtype == 'Distance':
distance = value/100.
seconds = distance/avgspeed
rscore = 60*seconds/3600.
rscore = 60.*float(seconds)/3600.
if targettype == 'Speed':
if targettype == 'Speed': # pragma: no cover
value = step.get('targetValue',0)
valuelow = step.get('targetValueLow',0)
valuehigh = step.get('targetValueHigh',0)
velomid = 0
if value != 0: # pragma: no cover
if value != 0:
seconds = distance/value
velomid = value/1000.
elif valuelow != 0 and valuehigh != 0: # pragma: no cover
elif valuelow != 0 and valuehigh != 0:
velomid = (valuelow+valuehigh)/2000.
seconds = distance/velomid
@@ -680,7 +677,7 @@ def step_to_time_dist(step,avgspeed = 3.2,ftp=200,ftspm=25,ftv=3.7):
rscoreperhour = 100.*veloratio
rscore = rscoreperhour*seconds/3600.
if targettype == 'Power':
if targettype == 'Power': # pragma: no cover
value = step.get('targetValue',0)
valuelow = step.get('targetValueLow',0)
valuehigh = step.get('targetValueHigh',0)
@@ -702,7 +699,7 @@ def step_to_time_dist(step,avgspeed = 3.2,ftp=200,ftspm=25,ftv=3.7):
rscore = 100.*(avgpower/ftp)*seconds/3600.
if targettype == 'Cadence':
if targettype == 'Cadence': # pragma: no cover
value = step.get('targetValue',0)
valuelow = step.get('targetValueLow',0)
valuehigh = step.get('targetValueHigh',0)
@@ -1167,3 +1164,53 @@ def request_is_ajax(request):
# is_ajax = True
return is_ajax
def intervals_to_string(vals, units, typ):
if vals is None or units is None or typ is None: # pragma: no cover
return ''
if len(vals) != len(units) or len(vals) != len(typ): # pragma: no cover
return ''
s = ''
previous = 'rest'
for i in range(len(vals)):
if typ[i] == 'rest' and previous == 'rest':
if units[i] == 'min': # pragma: no cover
val = int(vals[i])*60
unit = 'sec'
else:
val = int(vals[i])
if units[i] == 'meters': # pragma: no cover
unit = 'm'
if units[i] == 'seconds':
unit = 'sec'
s += '+0min/{val}{unit}'.format(val=val,unit=unit)
elif typ[i] == 'rest': # pragma: no cover
if units[i] == 'min':
val = int(vals[i])*60
unit = 'sec'
else:
val = int(vals[i])
if units[i] == 'meters':
unit = 'm'
if units[i] == 'seconds':
unit = 'sec'
s += '/{val}{unit}'.format(val=val,unit=unit)
previous = 'rest'
else: # pragma: no cover # work interval
if units[i] == 'min':
val = int(vals[i])*60
unit = 'sec'
else:
val = int(vals[i])
if units[i] == 'meters':
unit = 'm'
if units[i] == 'seconds':
unit = 'sec'
s += '+{val}{unit}'.format(val=val,unit=unit)
previous = 'work'
if s[0] == '+':
s = s[1:]
return s
+1
View File
@@ -2099,6 +2099,7 @@ def workout_getimportview(request,externalid,source = 'c2',do_async=False):
workoutdate = startdatetime.astimezone(
pytz.timezone(timezone_str)
).strftime('%Y-%m-%d')
+2 -2
View File
@@ -91,7 +91,7 @@ from django.utils.datastructures import MultiValueDictKeyError
from django.utils import timezone,translation
from django.core.mail import send_mail, BadHeaderError
from rowers.forms import (
SummaryStringForm,IntervalUpdateForm,StrokeDataForm,
SummaryStringForm,StrokeDataForm,
StatsOptionsForm,PredictedPieceForm,DateRangeForm,DeltaDaysForm,
FitnessMetricForm,PredictedPieceFormNoDistance,
EmailForm, RegistrationForm, RegistrationFormTermsOfService,
@@ -1161,7 +1161,7 @@ def get_my_teams(user):
return teams
# Used for the interval editor - translates seconds to a time object
def get_time(second):
def get_time(second): # pragma: no cover
if (second<=0) or (second>1e9):
hours = 0
minutes=0
+31 -141
View File
@@ -13,6 +13,7 @@ import numpy
from rowers.mailprocessing import send_confirm
import rowers.uploads as uploads
import rowers.utils as utils
from rowers.utils import intervals_to_string
from urllib.parse import urlparse, parse_qs
from json.decoder import JSONDecodeError
@@ -2297,7 +2298,7 @@ def workout_view(request,id=0,raceresult=0,sessionresult=0,nocourseraceresult=0)
intervaldata['itime'] = itime
intervaldata['itype'] = itype
rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger',
vals, units, typ = rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger',
debug=False,smoothwindow=15.,
activewindow = [startsecond,endsecond])
summary = rowdata.allstats()
@@ -2316,7 +2317,7 @@ def workout_view(request,id=0,raceresult=0,sessionresult=0,nocourseraceresult=0)
intervaldata['itime'] = itime
intervaldata['itype'] = itype
rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger',
vals, units, typ = rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger',
debug=False,smoothwindow=15.,
activewindow = [startsecond,endsecond])
summary = rowdata.allstats()
@@ -2336,7 +2337,7 @@ def workout_view(request,id=0,raceresult=0,sessionresult=0,nocourseraceresult=0)
intervaldata['itime'] = itime
intervaldata['itype'] = itype
rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger',
vals, units, typ = rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',0.1,mode='larger',
debug=False,smoothwindow=15.,
activewindow = [startsecond,endsecond])
summary = rowdata.allstats()
@@ -4812,7 +4813,7 @@ def workout_upload_api(request):
totalDistance = post_data.get('totalDistance',None)
elapsedTime = post_data.get('elapsedTime',None)
summary = post_data.get('summary',None)
timezone = post_data.get('timezone',None)
r = None
if form.is_valid():
@@ -4904,6 +4905,13 @@ def workout_upload_api(request):
return JSONResponse(status=200,data=message)
w = Workout.objects.get(id=id)
if timezone is not None: # pragma: no cover
w.startdatetime = w.startdatetime.astimezone(pytz.timezone(timezone))
w.workoutdate = w.startdatetime.strftime('%Y-%m-%d')
w.starttime = w.starttime.strftime('%H:%M:%S')
w.timezone = timezone
w.save()
if make_plot: # pragma: no cover
@@ -6240,7 +6248,7 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
if powerorpace == 'power' and power is not None:
try:
rowdata.updateinterval_metric(
vals, units, typ = rowdata.updateinterval_metric(
' Power (watts)',power,mode='larger',
debug=False,smoothwindow=15.,
activewindow=[activesecondsmin,activesecondsmax],
@@ -6250,7 +6258,7 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
elif powerorpace == 'pace': # pragma: no cover
try:
velo = 500./pace_secs
rowdata.updateinterval_metric(
vals, units, typ = rowdata.updateinterval_metric(
' AverageBoatSpeed (m/s)',velo,mode='larger',
debug=False,smoothwindow=15.,
activewindow=[activesecondsmin,activesecondsmax],
@@ -6259,7 +6267,7 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
messages.error(request,'Error updating pace')
elif powerorpace == 'work': # pragma: no cover
try:
rowdata.updateinterval_metric(
vals, units, typ = rowdata.updateinterval_metric(
'driveenergy',work,mode='larger',
debug=False,smoothwindow=15.,
activewindow=[activesecondsmin,activesecondsmax],
@@ -6268,13 +6276,17 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
messages.error(request,'Error updating Work per Stroke')
elif powerorpace == 'spm': # pragma: no cover
try:
rowdata.updateinterval_metric(
vals, units, typ = rowdata.updateinterval_metric(
' Cadence (stokes/min)',spm,mode='larger',
debug=False,smoothwindow=2.,
activewindow=[activesecondsmin,activesecondsmax],)
except:
messages.error(request,'Error updating SPM')
intervalString = ''
if vals is not None:
intervalString = intervals_to_string(vals, units, typ)
intervalstats = rowdata.allstats()
itime,idist,itype = rowdata.intervalstats_values()
nrintervals = len(idist)
@@ -6296,7 +6308,7 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
'activeminutesmin': activeminutesmin,
'activeminutesmax': activeminutesmax,
}
form = SummaryStringForm()
form = SummaryStringForm(initial={'intervalstring':intervalString})
powerupdateform = PowerIntervalUpdateForm(initial=data)
savebutton = 'savepowerpaceform'
formvalues = {
@@ -6368,7 +6380,7 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
pace_secs = 120.
if powerorpace == 'power' and power is not None:
rowdata.updateinterval_metric(' Power (watts)',power,mode='larger',
vals, units, typ = rowdata.updateinterval_metric(' Power (watts)',power,mode='larger',
debug=False,smoothwindow=15,
activewindow=[activesecondsmin,activesecondsmax],
)
@@ -6376,7 +6388,7 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
elif powerorpace == 'pace': # pragma: no cover
try:
velo = 500./pace_secs
rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',velo,mode='larger',
vals, units, typ = rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',velo,mode='larger',
debug=False,smoothwindow=15,
activewindow=[activesecondsmin,activesecondsmax],
)
@@ -6384,7 +6396,7 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
messages.error(request,'Error updating pace')
elif powerorpace == 'work': # pragma: no cover
try:
rowdata.updateinterval_metric(
vals, units, typ = rowdata.updateinterval_metric(
'driveenergy',work,mode='larger',
debug=False,smoothwindow=15.,
activewindow=[activesecondsmin,activesecondsmax],)
@@ -6392,7 +6404,7 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
messages.error(request,'Error updating Work per Stroke')
elif powerorpace == 'spm': # pragma: no cover
try:
rowdata.updateinterval_metric(' Cadence (stokes/min)',spm,mode='larger',
vals, units, typ = rowdata.updateinterval_metric(' Cadence (stokes/min)',spm,mode='larger',
debug=False,smoothwindow=2.,
activewindow=[activesecondsmin,activesecondsmax],
)
@@ -6400,6 +6412,11 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
messages.error(request,'Error updating SPM')
intervalString = ''
if vals is not None:
intervalString = intervals_to_string(vals, units, typ)
intervalstats = rowdata.allstats()
itime,idist,itype = rowdata.intervalstats_values()
nrintervals = len(idist)
@@ -6414,135 +6431,10 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
'activeminutesmax': activeminutesmax,
}
powerupdateform = PowerIntervalUpdateForm(initial=cd)
form = SummaryStringForm()
form = SummaryStringForm()
# we are saving the results obtained from the detailed form
elif request.method == 'POST' and "savedetailform" in request.POST: # pragma: no cover
savebutton = 'savedetailform'
form = SummaryStringForm()
nrintervals = int(request.POST['nrintervals'])
detailform = IntervalUpdateForm(request.POST,aantal=nrintervals)
itime = []
idist = []
itype = []
ivalues = []
iunits = []
itypes = []
iresults = []
for i in range(nrintervals):
try:
t = datetime.datetime.strptime(request.POST['intervalt_%s' % i],"%H:%M:%S.%f")
except ValueError:
t = datetime.datetime.strptime(request.POST['intervalt_%s' % i],"%H:%M:%S")
timesecs = 3600*t.hour+60*t.minute+t.second+t.microsecond/1.e6
itime += [timesecs]
idist += [int(request.POST['intervald_%s' % i])]
itype += [int(request.POST['type_%s' % i])]
if itype[i] == 3: # rest
itypes += ['rest']
ivalues += [timesecs]
iresults += [idist[i]]
iunits += ['seconds']
if itype[i] == 5 or itype[i] == 2: # distance based work
itypes += ['work']
ivalues += [idist[i]]
iresults += [timesecs]
iunits += ['meters']
if itype[i] == 4 or itype[i] == 1: # time based work
itypes += ['work']
ivalues += [timesecs]
iresults += [idist[i]]
iunits += ['seconds']
rowdata.updateintervaldata(ivalues,iunits,itypes,iresults=iresults)
intervalstats = rowdata.allstats()
row.summary = intervalstats
try:
row.notes += "\n"+s
except TypeError:
pass
row.save()
rowdata.write_csv(f1,gzip=True)
dataprep.update_strokedata(encoder.decode_hex(id),rowdata.df)
messages.info(request,"Updated interval data saved")
form = SummaryStringForm()
powerupdateform = PowerIntervalUpdateForm(initial={
'power': int(normp),
'pace': avpace,
'selector': 'power',
'work': int(normw),
'spm': int(normspm),
'activeminutesmin': 0,
'activeminutesmax': activeminutesmax,
})
# we are processing the details form
elif request.method == 'POST' and "nrintervals" in request.POST: # pragma: no cover
savebutton = 'savedetailform'
nrintervals = int(request.POST['nrintervals'])
detailform = IntervalUpdateForm(request.POST,aantal=nrintervals)
if detailform.is_valid():
cd = detailform.cleaned_data
itime = []
idist = []
itype = []
ivalues = []
iunits = []
itypes = []
iresults = []
for i in range(nrintervals):
t = cd['intervalt_%s' % i]
timesecs = t.total_seconds()
itime += [timesecs]
idist += [cd['intervald_%s' % i]]
itype += [cd['type_%s' % i]]
if itype[i] == '3': # rest
itypes += ['rest']
ivalues += [timesecs]
iresults += [idist[i]]
iunits += ['seconds']
if itype[i] == '5' or itype[i] == '2': # distance based work
itypes += ['work']
ivalues += [idist[i]]
iresults += [timesecs]
iunits += ['meters']
if itype[i] == '4' or itype[i] == '1': # time based work
itypes += ['work']
ivalues += [timesecs]
iresults += [idist[i]]
iunits += ['seconds']
rowdata.updateintervaldata(ivalues,iunits,
itypes,iresults=iresults)
intervalstats = rowdata.allstats()
form = SummaryStringForm()
powerupdateform = PowerIntervalUpdateForm()
form = SummaryStringForm(initial={'intervalstring':intervalString})
initial = {}
for i in range(nrintervals):
try:
initial['intervald_%s' % i] = idist[i]
initial['intervalt_%s' % i] = get_time(itime[i])
initial['type_%s' % i] = itype[i]
except IndexError: # pragma: no cover
pass
detailform = IntervalUpdateForm(aantal=nrintervals,initial=initial)
# create interactive plot
try:
@@ -6562,13 +6454,11 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
div = ''
# render page
return render(request, 'summary_edit.html',
{'form':form,
'activeminutesmax':activeminutesmax,
'activeminutesmin':activeminutesmin,
'maxminutes': maxminutes,
'detailform':detailform,
'powerupdateform':powerupdateform,
'workout':row,
'rower':r,