Private
Public Access
1
0

Merge branch 'release/v8.49'

This commit is contained in:
Sander Roosendaal
2018-11-15 19:05:28 +01:00
19 changed files with 660 additions and 187 deletions

View File

@@ -44,7 +44,8 @@ class RowerInline(admin.StackedInline):
'polartoken','polartokenexpirydate',
'polarrefreshtoken','polaruserid',
'polar_auto_import',
'stravatoken','stravaexportas','strava_auto_export',
'stravatoken','stravatokenexpirydate','stravarefreshtoken',
'stravaexportas','strava_auto_export',
'strava_auto_import',
'runkeepertoken','runkeeper_auto_export',)}),
('Team',

View File

@@ -6,7 +6,7 @@
from rowers.imports import *
import datetime
from requests import Request, Session
import mytypes
from rowers.mytypes import otwtypes
from iso8601 import ParseError
@@ -428,7 +428,7 @@ def createc2workoutdata(w):
startdate = datetime.datetime.combine(w.date,datetime.time())
data = {
"type": workouttype,
"type": mytypes.c2mapping[workouttype],
"date": w.startdatetime.isoformat(),
"timezone": w.timezone,
"distance": int(w.distance),
@@ -684,6 +684,12 @@ def process_callback(request):
# Uploading workout
def workout_c2_upload(user,w):
message = 'trying C2 upload'
try:
if mytypes.c2mapping[w.workouttype] is None:
return "This workout type cannot be uploaded to Concept2",0
except KeyError:
return "This workout type cannot be uploaded to Concept2",0
thetoken = c2_open(user)
r = Rower.objects.get(user=user)
@@ -755,7 +761,7 @@ def add_workout_from_data(user,importid,data,strokedata,
source='c2',splitdata=None,
workoutsource='concept2'):
try:
workouttype = data['type']
workouttype = mytypes.c2mappinginv[data['type']]
except KeyError:
workouttype = 'rower'

View File

@@ -741,6 +741,7 @@ def fetchcp(rower,theworkouts,table='cpdata'):
def create_row_df(r,distance,duration,startdatetime,workouttype='rower',
avghr=None,avgpwr=None,avgspm=None,
rankingpiece = False,
duplicate=False,
title='Manual entry',notes='',weightcategory='hwt'):
@@ -813,6 +814,7 @@ def create_row_df(r,distance,duration,startdatetime,workouttype='rower',
title=title,
notes=notes,
rankingpiece=rankingpiece,
duplicate=duplicate,
dosmooth=False,
workouttype=workouttype,
consistencychecks=False,
@@ -829,6 +831,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
workoutsource='unknown',
notes='', totaldist=0, totaltime=0,
rankingpiece=False,
duplicate=False,
summary='',
makeprivate=False,
oarlength=2.89, inboard=0.88,
@@ -999,13 +1002,30 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
maxhr = np.nan_to_num(maxhr)
averagehr = np.nan_to_num(averagehr)
t = datetime.datetime.strptime(duration,"%H:%M:%S.%f")
delta = datetime.timedelta(hours=t.hour, minutes=t.minute, seconds=t.second)
workoutenddatetime = workoutstartdatetime+delta
# check for duplicate start times and duration
ws = Workout.objects.filter(startdatetime=workoutstartdatetime,
distance=totaldist,
user=r)
if (len(ws) != 0):
message = "Warning: This workout probably already exists in the database"
privacy = 'hidden'
ws = Workout.objects.filter(user=r,date=workoutdate,duplicate=False).exclude(
startdatetime__gt=workoutenddatetime
)
ws2 = []
for ww in ws:
t = ww.duration
delta = datetime.timedelta(hours=t.hour, minutes=t.minute, seconds=t.second)
enddatetime = ww.startdatetime+delta
if enddatetime > workoutstartdatetime:
ws2.append(ww)
if (len(ws2) != 0):
message = "Warning: This workout overlaps with an existing one and was marked as a duplicate"
duplicate = True
w = Workout(user=r, name=title, date=workoutdate,
@@ -1014,6 +1034,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
duration=duration, distance=totaldist,
weightcategory=r.weightcategory,
starttime=workoutstarttime,
duplicate=duplicate,
workoutsource=workoutsource,
rankingpiece=rankingpiece,
forceunit=forceunit,

View File

@@ -866,6 +866,8 @@ def update_agegroup_db(age,sex,weightcategory,wcdurations,wcpower,
df['sex'] = sex
df['age'] = age
df['weightcategory'] = weightcategory
df.replace([np.inf,-np.inf],np.nan,inplace=True)
df.dropna(axis=0,inplace=True)
if debug:
engine = create_engine(database_url_debug, echo=False)

View File

@@ -94,7 +94,7 @@ def imports_open(user,oauth_data):
tokenname = oauth_data['tokenname']
refreshtokenname = oauth_data['refreshtokenname']
expirydatename = oauth_data['expirydatename']
if tokenexpirydate and timezone.now()>tokenexpirydate:
if tokenexpirydate and timezone.now()+timedelta(seconds=3599)>tokenexpirydate:
token = imports_token_refresh(
user,
tokenname,
@@ -102,6 +102,15 @@ def imports_open(user,oauth_data):
expirydatename,
oauth_data,
)
elif tokenexpirydate is None and expirydatename is not None and 'strava' in expirydatename:
token = imports_token_refresh(
user,
tokenname,
refreshtokenname,
expirydatename,
oauth_data,
)
return token
@@ -156,7 +165,11 @@ def imports_do_refresh_token(refreshtoken,oauth_data,access_token=''):
try:
expires_in = token_json['expires_in']
except KeyError:
expires_in = 0
try:
expires_at = arrow.get(token_json['expires_at']).timestamp
expires_in = expires_at - arrow.now().timestamp
except KeyError:
expires_in = 0
try:
refresh_token = token_json['refresh_token']
except KeyError:
@@ -267,6 +280,11 @@ def imports_token_refresh(user,tokenname,refreshtokenname,expirydatename,oauth_d
refreshtoken = getattr(r,refreshtokenname)
# for Strava transition
if not refreshtoken:
refreshtoken = getattr(r,tokenname)
res = imports_do_refresh_token(refreshtoken,oauth_data)
access_token = res[0]
expires_in = res[1]

View File

@@ -237,8 +237,6 @@ def interactive_boxchart(datadf,fieldname,extratitle=''):
def interactive_activitychart(workouts,startdate,enddate,stack='type'):
if len(workouts) == 0:
return "",""
dates = []
dates_sorting = []
@@ -316,7 +314,10 @@ def interactive_activitychart(workouts,startdate,enddate,stack='type'):
label = CatAttr(columns=['date'], sort=False),
xlabel='Date',
ylabel='Time',
title='Activity',
title='Activity {d1} to {d2}'.format(
d1 = startdate.strftime("%Y-%m-%d"),
d2 = enddate.strftime("%Y-%m-%d"),
),
stack=stack,
plot_width=350,
plot_height=250,

View File

@@ -653,6 +653,9 @@ class Rower(models.Model):
polar_auto_import = models.BooleanField(default=False)
stravatoken = models.CharField(default='',max_length=200,blank=True,null=True)
stravatokenexpirydate = models.DateTimeField(blank=True,null=True)
stravarefreshtoken = models.CharField(default='',max_length=1000,
blank=True,null=True)
stravaexportas = models.CharField(default="Rowing",
max_length=30,
choices=stravatypes,
@@ -2131,6 +2134,7 @@ class Workout(models.Model):
privacy = models.CharField(default='visible',max_length=30,
choices=privacychoices)
rankingpiece = models.BooleanField(default=False,verbose_name='Ranking Piece')
duplicate = models.BooleanField(default=False,verbose_name='Duplicate Workout')
def __unicode__(self):
@@ -2179,6 +2183,41 @@ def auto_delete_file_on_delete(sender, instance, **kwargs):
if os.path.isfile(instance.csvfilename+'.gz'):
os.remove(instance.csvfilename+'.gz')
@receiver(models.signals.post_delete,sender=Workout)
def update_duplicates_on_delete(sender, instance, **kwargs):
if instance.id:
duplicates = Workout.objects.filter(
user=instance.user,date=instance.date,
duplicate=True)
for d in duplicates:
t = d.duration
delta = datetime.timedelta(hours=t.hour, minutes=t.minute, seconds=t.second)
workoutenddatetime = d.startdatetime+delta
ws = Workout.objects.filter(
user=d.user,date=d.date,
).exclude(
pk__in=[instance.pk,d.pk]
).exclude(
startdatetime__gt=workoutenddatetime
)
ws2 = []
for ww in ws:
t = ww.duration
delta = datetime.timedelta(hours=t.hour, minutes=t.minute, seconds=t.second)
enddatetime = ww.startdatetime+delta
if enddatetime > d.startdatetime:
ws2.append(ww)
if len(ws2) == 0:
d.duplicate=False
d.save()
# Delete stroke data from the database when a workout is deleted
@receiver(models.signals.post_delete,sender=Workout)
def auto_delete_strokedata_on_delete(sender, instance, **kwargs):
@@ -2401,7 +2440,7 @@ class WorkoutForm(ModelForm):
# duration = forms.TimeInput(format='%H:%M:%S.%f')
class Meta:
model = Workout
fields = ['name','date','starttime','timezone','duration','distance','workouttype','boattype','weightcategory','notes','rankingpiece']
fields = ['name','date','starttime','timezone','duration','distance','workouttype','boattype','weightcategory','notes','rankingpiece','duplicate']
widgets = {
'date': AdminDateWidget(),
'notes': forms.Textarea,

View File

@@ -10,9 +10,214 @@ workouttypes = (
('coastal','Coastal'),
('c-boat','Dutch C boat'),
('churchboat','Finnish Church boat'),
('Ride','Ride'),
('Run','Run'),
('NordicSki','NordicSki'),
('Swim','Swim'),
('Hike','Hike'),
('Walk','Walk'),
('Canoeing','Canoeing'),
('Crossfit','Crossfit'),
('StandUpPaddling','StandUpPaddling'),
('IceSkate','IceSkate'),
('WeightTraining','WeightTraining'),
('InlineSkate','InlineSkate'),
('Kayaking','Kayaking'),
('Workout','Workout'),
('other','Other'),
)
stravamapping = {
'water':'Rowing',
'rower':'Rowing',
'skierg':'NordicSki',
'bike':'Ride',
'dynamic':'Rowing',
'slides':'Rowing',
'paddle':'StandUpPaddling',
'snow':'NordicSki',
'coastal':'Rowing',
'c-boat':'Rowing',
'churchboat':'Rowing',
'Ride':'Ride',
'Run':'Run',
'NordicSki':'NordicSki',
'Swim':'Swim',
'Hike':'Hike',
'Walk':'Walk',
'Canoeing':'Canoeing',
'Crossfit':'Crossfit',
'StandUpPaddling':'StandUpPaddling',
'IceSkate':'IceSkate',
'WeightTraining':'WeightTraining',
'InlineSkate':'InlineSkate',
'Kayaking':'Kayaking',
'Workout':'Workout',
'other':'Workout',
}
stmapping = {
'water':'Rowing',
'rower':'Rowing',
'skierg':'Skiing:Nordic',
'bike':'Cycling',
'dynamic':'Rowing',
'slides':'Rowing',
'paddle':'Other:Paddling',
'snow':'Skiing:Nordic',
'coastal':'Rowing',
'c-boat':'Rowing',
'churchboat':'Rowing',
'Ride':'Cycling',
'Run':'Running',
'NordicSki':'Skiing:Nordic',
'Swim':'Swimming',
'Hike':'Hiking',
'RollerSki':'Other:RollerSki',
'Walk':'Other:Walk',
'Canoeing':'Other:Canoeing',
'Crossfit':'Other:Crossfit',
'StandUpPaddling':'Other:StandUpPaddling',
'IceSkate':'Skating',
'WeightTraining':'Other:WeightTraining',
'InlineSkate':'Skating:InlineSkate',
'Kayaking':'Other:Kayaking',
'Workout':'Other:Workout',
'other':'Other',
}
rkmapping = {
'water':'Rowing',
'rower':'Rowing',
'skierg':'Cross-Country Skiing',
'bike':'Cycling',
'dynamic':'Rowing',
'slides':'Rowing',
'paddle':'Other:Paddling',
'snow':'Cross-Country Skiing',
'coastal':'Rowing',
'c-boat':'Rowing',
'churchboat':'Rowing',
'Ride':'Cycling',
'Run':'Running',
'NordicSki':'Cross-Country Skiing',
'Swim':'Swimming',
'Hike':'Hiking',
'Walk':'Walking',
'Canoeing':'Other',
'Crossfit':'CrossFit',
'StandUpPaddling':'Other',
'IceSkate':'Skating',
'WeightTraining':'Other',
'InlineSkate':'Skating',
'Kayaking':'Other',
'Workout':'Other',
'other':'Other',
}
polarmapping = {
'water':'Rowing',
'rower':'Rowing',
'skierg':'Skiing',
'bike':'Cycling',
'dynamic':'Rowing',
'slides':'Rowing',
'paddle':'Other Outdoor',
'snow':'Skiing',
'coastal':'Rowing',
'c-boat':'Rowing',
'churchboat':'Rowing',
'Ride':'Cycling',
'Run':'Running',
'NordicSki':'Skiing',
'Swim':'Swimming',
'Hike':'Hiking',
'Walk':'Walking',
'Canoeing':'Canoeing',
'Crossfit':'Crossfit',
'StandUpPaddling':'Other Outdoor',
'IceSkate':'Skating',
'WeightTraining':'Strength training',
'InlineSkate':'Skating',
'Kayaking':'Kayaking',
'Workout':'Other Indoor',
'other':'Other Indoor',
}
tpmapping = {
'water':'rowing',
'rower':'rowing',
'skierg':'xc-ski',
'bike':'bike',
'dynamic':'rowing',
'slides':'rowing',
'paddle':'other',
'snow':'xc-ski',
'coastal':'rowing',
'c-boat':'rowing',
'churchboat':'rowing',
'Ride':'cycling',
'Run':'run',
'NordicSki':'xc-ski',
'Swim':'swim',
'Hike':'other',
'Walk':'walk',
'Canoeing':'other',
'Crossfit':'other',
'StandUpPaddling':'other',
'IceSkate':'other',
'WeightTraining':'strength',
'InlineSkate':'other',
'Kayaking':'other',
'Workout':'other',
'other':'other',
}
c2mapping = {
'water':'water',
'rower':'rower',
'skierg':'skierg',
'bike':'bike',
'dynamic':'dynamic',
'slides':'slides',
'paddle':'paddle',
'snow':'snow',
'coastal':'water',
'c-boat':'water',
'churchboat':'water',
'Ride':'bike',
'Run':None,
'NordicSki':'snow',
'Swim':None,
'Hike':None,
'Walk':None,
'Canoeing':'paddle',
'Crossfit':None,
'StandUpPaddling':None,
'IceSkate':None,
'WeightTraining':None,
'InlineSkate':None,
'Kayaking':None,
'Workout':None,
'other':None,
}
c2mappinginv = {value:key for key,value in c2mapping.iteritems() if value is not None}
stravamappinginv = {value:key for key,value in stravamapping.iteritems() if value is not None}
stmappinginv = {value:key for key,value in stmapping.iteritems() if value is not None}
rkmappinginv = {value:key for key,value in rkmapping.iteritems() if value is not None}
polarmappinginv = {value:key for key,value in polarmapping.iteritems() if value is not None}
otwtypes = (
'water',
'coastal',
@@ -20,6 +225,17 @@ otwtypes = (
'churchboat'
)
rowtypes = (
'water',
'rower',
'dynamic',
'slides',
'coastal',
'c-boat',
'churchboat'
)
checktypes = [i[0] for i in workouttypes]
workoutsources = (

View File

@@ -52,7 +52,7 @@ baseurl = 'https://polaraccesslink.com/v3'
from utils import NoTokenError, custom_exception_handler
import mytypes
# Exchange access code for long-lived access token
def get_token(code):

View File

@@ -18,7 +18,7 @@ from stravalib.exc import ActivityUploadFailed,TimeoutExceeded
from iso8601 import ParseError
from utils import myqueue
import mytypes
import gzip
from rowsandall_app.settings import (
@@ -40,11 +40,11 @@ oauth_data = {
'autorization_uri': "https://www.strava.com/oauth/authorize",
'content_type': 'application/json',
'tokenname': 'stravatoken',
'refreshtokenname': '',
'expirydatename': '',
'refreshtokenname': 'stravarefreshtoken',
'expirydatename': 'stravatokenexpirydate',
'bearer_auth': True,
'base_url': "https://www.strava.com/oauth/token",
'grant_type': None,
'grant_type': 'refresh_token',
}
@@ -52,6 +52,27 @@ oauth_data = {
def get_token(code):
return imports_get_token(code, oauth_data)
def strava_open(user):
return imports_open(user, oauth_data)
def do_refresh_token(refreshtoken):
return imports_do_refresh_token(refreshtoken, oauth_data)
def rower_strava_token_refresh(user):
r = Rower.objects.get(user=user)
res = do_refresh_token(r.stravarefreshtoken)
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+timedelta(seconds=expires_in)
r.stravatoken = access_token
r.stravatokenexpirydate = expirydatetime
r.stravarefreshtoken = refresh_token
r.save()
return r.stravatoken
# Make authorization URL including random string
def make_authorization_url(request):
return imports_make_authorization_url(oauth_data)
@@ -62,6 +83,9 @@ def get_strava_workout_list(user,limit_n=0):
if (r.stravatoken == '') or (r.stravatoken is None):
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401,s)
elif (r.stravatokenexpirydate is None or timezone.now()+timedelta(seconds=3599)>r.stravatokenexpirydate):
s = "Token expired. Needs to refresh."
return custom_exception_handler(401,s)
else:
# ready to fetch. Hurray
authorizationstring = str('Bearer ' + r.stravatoken)
@@ -78,6 +102,7 @@ def get_strava_workout_list(user,limit_n=0):
s = requests.get(url,headers=headers,params=params)
return s
@@ -87,9 +112,12 @@ def get_strava_workouts(rower):
if not isprorower(rower):
return 0
res = get_strava_workout_list(rower.user,limit_n=10)
try:
thetoken = strava_open(rower.user)
except NoTokenError:
return 0
print res.status_code
res = get_strava_workout_list(rower.user,limit_n=10)
if (res.status_code != 200):
return 0
@@ -135,9 +163,9 @@ def create_async_workout(alldata,user,stravaid,debug=False):
distance = data['distance']
stravaid = data['id']
try:
workouttype = data['type']
workouttype = mytypes.stravamappinginv[data['type']]
except:
workouttype = 'rower'
workouttype = 'other'
if workouttype.lower() == 'rowing':
workouttype = 'rower'
@@ -227,6 +255,9 @@ def get_workout(user,stravaid):
if (r.stravatoken == '') or (r.stravatoken is None):
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401,s)
elif (r.stravatokenexpirydate is not None and timezone.now()>r.stravatokenexpirydate):
s = "Token expired. Needs to refresh."
return custom_exception_handler(401,s)
else:
# ready to fetch. Hurray
fetchresolution = 'high'
@@ -413,14 +444,15 @@ def add_workout_from_data(user,importid,data,strokedata,
source='strava',splitdata=None,
workoutsource='strava'):
try:
workouttype = data['type']
workouttype = mytypes.stravamappinginv[data['type']]
except KeyError:
workouttype = 'rower'
workouttype = 'other'
if workouttype.lower() == 'rowing':
workouttype = 'rower'
if 'summary_polyline' in data['map']:
workouttype = 'water'
if 'summary_polyline' in data['map'] and workouttype=='rower':
workouttype = 'water'
if workouttype not in [x[0] for x in Workout.workouttypes]:
workouttype = 'other'

View File

@@ -83,18 +83,15 @@
$( document ).ready(function() {
$('#id_workouttype').on('change', function(){
if (
$(this).val() == 'rower'
|| $(this).val() == 'skierg'
|| $(this).val() == 'dynamic'
|| $(this).val() == 'slides'
|| $(this).val() == 'paddle'
|| $(this).val() == 'bike'
|| $(this).val() == 'snow'
$(this).val() == 'water'
|| $(this).val() == 'coastal'
|| $(this).val() == 'c-boat'
|| $(this).val() == 'churchboat'
) {
$('#id_boattype').toggle(true);
} else {
$('#id_boattype').toggle(false);
$('#id_boattype').val('1x');
} else {
$('#id_boattype').toggle(true);
}
});
$('#id_workouttype').change();

View File

@@ -16,18 +16,15 @@
$( document ).ready(function() {
$('#id_workouttype').on('change', function(){
if (
$(this).val() == 'rower'
|| $(this).val() == 'skierg'
|| $(this).val() == 'dynamic'
|| $(this).val() == 'slides'
|| $(this).val() == 'paddle'
|| $(this).val() == 'bike'
|| $(this).val() == 'snow'
$(this).val() == 'water'
|| $(this).val() == 'coastal'
|| $(this).val() == 'c-boat'
|| $(this).val() == 'churchboat'
) {
$('#id_boattype').toggle(true);
} else {
$('#id_boattype').toggle(false);
$('#id_boattype').val('1x');
} else {
$('#id_boattype').toggle(true);
}
});
$('#id_workouttype').change();

View File

@@ -89,6 +89,9 @@
</td>
<td> {{ workout.distance }}m</td>
<td> {{ workout.duration |durationprint:"%H:%M:%S.%f" }} </td>
<td>
<a href="/rowers/sessions/{{ psdict.id.1 }}/detach/{{ workout.id }}/">Detach</a>
</td>
</tr>
{% endfor %}
</tbody>
@@ -127,6 +130,10 @@
{% endfor %}
</tbody>
</table>
<p>
<a href="/rowers/sessions/{{ psdict.id.1 }}/compare"
title="Compare the workouts of all athletes who did this session">Compare Workouts</a>
</p>
</li>
{% if coursescript %}
<li class="grid_2">

View File

@@ -112,7 +112,7 @@
<li class="grid_2 maxheight">
{% if workouts %}
<form enctype="multipart/form-data"
action="/rowers/multi-compare"
action="/rowers/multi-compare/"
method="post">
<input type="checkbox" onClick="toggle(this)" /> Toggle All<br/>

View File

@@ -76,25 +76,22 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
$( document ).ready(function() {
$('#id_workouttype').on('change', function(){
if (
$(this).val() == 'rower'
|| $(this).val() == 'skierg'
|| $(this).val() == 'dynamic'
|| $(this).val() == 'slides'
|| $(this).val() == 'paddle'
|| $(this).val() == 'bike'
|| $(this).val() == 'snow'
) {
$('#id_boattype').toggle(false);
$('#id_boattype').val('1x');
} else {
$('#id_boattype').toggle(true);
}
});
$('#id_workouttype').change();
});
$( document ).ready(function() {
$('#id_workouttype').on('change', function(){
if (
$(this).val() == 'water'
|| $(this).val() == 'coastal'
|| $(this).val() == 'c-boat'
|| $(this).val() == 'churchboat'
) {
$('#id_boattype').toggle(true);
} else {
$('#id_boattype').toggle(false);
$('#id_boattype').val('1x');
}
});
$('#id_workouttype').change();
});
</script>
{% endblock %}

View File

@@ -23,18 +23,15 @@
$( document ).ready(function() {
$('#id_workouttype').on('change', function(){
if (
$(this).val() == 'rower'
|| $(this).val() == 'skierg'
|| $(this).val() == 'dynamic'
|| $(this).val() == 'slides'
|| $(this).val() == 'paddle'
|| $(this).val() == 'snow'
|| $(this).val() == 'bike'
$(this).val() == 'water'
|| $(this).val() == 'coastal'
|| $(this).val() == 'c-boat'
|| $(this).val() == 'churchboat'
) {
$('#id_boattype').toggle(true);
} else {
$('#id_boattype').toggle(false);
$('#id_boattype').val('1x');
} else {
$('#id_boattype').toggle(true);
}
});
$('#id_workouttype').change();

View File

@@ -321,6 +321,15 @@ def mocked_requests(*args, **kwargs):
return MockResponse(json_data,200)
elif stravasummarytester.match(args[0]):
return MockResponse(stravasummaryjson,200)
elif 'token' in args[0]:
json_data = {
"token_type": "Bearer",
"access_token": "987654321234567898765432123456789",
"refresh_token": "1234567898765432112345678987654321",
"expires_at": 1531385304
}
return MockResponse(json_data,200)
if c2tester.match(args[0]):
if c2uploadtester.match(args[0]):
@@ -403,7 +412,7 @@ class C2Objects(DjangoTestCase):
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
self.w = Workout.objects.create(
name='testworkout',workouttype='On-water',
name='testworkout',workouttype='water',
user=self.r,date=self.nu.strftime('%Y-%m-%d'),
starttime=workoutstarttime,
startdatetime=row.rowdatetime,
@@ -574,7 +583,7 @@ class STObjects(DjangoTestCase):
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
self.w = Workout.objects.create(
name='testworkout',workouttype='On-water',
name='testworkout',workouttype='water',
user=self.r,date=self.nu.strftime('%Y-%m-%d'),
starttime=workoutstarttime,
startdatetime=row.rowdatetime,
@@ -690,7 +699,7 @@ class RunKeeperObjects(DjangoTestCase):
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
self.w = Workout.objects.create(
name='testworkout',workouttype='On-water',
name='testworkout',workouttype='water',
user=self.r,date=self.nu.strftime('%Y-%m-%d'),
starttime=workoutstarttime,
startdatetime=row.rowdatetime,
@@ -782,7 +791,7 @@ class UAObjects(DjangoTestCase):
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
self.w = Workout.objects.create(
name='testworkout',workouttype='On-water',
name='testworkout',workouttype='water',
user=self.r,date=self.nu.strftime('%Y-%m-%d'),
starttime=workoutstarttime,
startdatetime=row.rowdatetime,
@@ -882,7 +891,7 @@ class TPObjects(DjangoTestCase):
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
self.w = Workout.objects.create(
name='testworkout',workouttype='On-water',
name='testworkout',workouttype='water',
user=self.r,date=self.nu.strftime('%Y-%m-%d'),
starttime=workoutstarttime,
startdatetime=row.rowdatetime,
@@ -1074,7 +1083,7 @@ class WorkoutTests(TestCase):
)
nu = datetime.datetime.now()
self.w = Workout.objects.create(name='testworkout',
workouttype='On-water',
workouttype='water',
user=self.r,date=nu.strftime('%Y-%m-%d'),
starttime=nu.strftime('%H:%M:%S'),
duration="0:55:00",distance=8000)
@@ -1092,7 +1101,7 @@ class C2Tests(TestCase):
gdproptindate=timezone.now()
)
self.nu = datetime.datetime.now()
self.w = Workout.objects.create(name='testworkout',workouttype='On-water',
self.w = Workout.objects.create(name='testworkout',workouttype='water',
user=r,date=nu.strftime('%Y-%m-%d'),
starttime=nu.strftime('%H:%M:%S'),
duration="0:55:00",distance=8000)
@@ -1255,7 +1264,7 @@ class DataTest(TestCase):
workoutdate = row.rowdatetime.strftime('%Y-%m-%d')
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
w = Workout.objects.create(name='testworkout',workouttype='On-water',
w = Workout.objects.create(name='testworkout',workouttype='water',
user=r,date=self.nu.strftime('%Y-%m-%d'),
starttime=workoutstarttime,
duration=duration,distance=totaldist,
@@ -1999,7 +2008,7 @@ class URLTests(TestCase):
self.nu = datetime.datetime.now()
filename = 'rowers/testdata/testdata.csv'
self.wotw = Workout.objects.create(name='testworkout',
workouttype='On-water',
workouttype='water',
user=r,date=self.nu.strftime('%Y-%m-%d'),
starttime=self.nu.strftime('%H:%M:%S'),
duration="0:55:00",distance=8000,
@@ -2241,7 +2250,7 @@ class subroutinetests(TestCase):
nu = datetime.datetime.now()
filename = 'rowers/testdata/testdata.csv'
self.w = Workout.objects.create(name='testworkout',
workouttype='On-water',
workouttype='water',
user=r,date=nu.strftime('%Y-%m-%d'),
starttime=nu.strftime('%H:%M:%S'),
duration="0:55:00",distance=8000,
@@ -2266,7 +2275,7 @@ class PlotTests(TestCase):
self.nu = datetime.datetime.now()
filename = 'rowers/testdata/testdata.csv'
self.wotw = Workout.objects.create(name='testworkout',
workouttype='On-water',
workouttype='water',
user=r,date=self.nu.strftime('%Y-%m-%d'),
starttime=self.nu.strftime('%H:%M:%S'),
duration="0:55:00",distance=8000,

View File

@@ -342,8 +342,9 @@ urlpatterns = [
url(r'^workout/(?P<id>\d+)/runkeeperuploadw/$',views.workout_runkeeper_upload_view),
url(r'^workout/(?P<id>\d+)/underarmouruploadw/$',views.workout_underarmour_upload_view),
url(r'^workout/(?P<id>\d+)/tpuploadw/$',views.workout_tp_upload_view),
url(r'^multi-compare/workout/(?P<id>\d+)$',views.multi_compare_view),
url(r'^multi-compare$',views.multi_compare_view),
url(r'^multi-compare/workout/(?P<id>\d+)/user/(?P<userid>\d+)/$',views.multi_compare_view),
url(r'^multi-compare/workout/(?P<id>\d+)/$',views.multi_compare_view),
url(r'^multi-compare/$',views.multi_compare_view),
url(r'^user-boxplot/user/(?P<userid>\d+)$',views.boxplot_view),
url(r'^user-boxplot$',views.boxplot_view),
url(r'^user-boxplot-data$',views.boxplot_view_data),
@@ -486,12 +487,16 @@ urlpatterns = [
url(r'^sessions/multicreate/user/(?P<userid>\d+)/$',
views.plannedsession_multicreate_view),
url(r'^sessions/(?P<id>\d+)/edit/$',views.plannedsession_edit_view),
url(r'^sessions/(?P<id>\d+)/compare/$',views.plannedsession_compare_view),
url(r'^sessions/(?P<id>\d+)/compare/user/(?P<userid>\d+)/$',views.plannedsession_compare_view),
url(r'^sessions/(?P<id>\d+)/edit/user/(?P<userid>\d+)/$',views.plannedsession_edit_view),
url(r'^sessions/(?P<id>\d+)/clone/user/(?P<userid>\d+)/$',views.plannedsession_clone_view),
url(r'^sessions/(?P<id>\d+)/clone/$',views.plannedsession_clone_view),
url(r'^sessions/(?P<id>\d+)$',views.plannedsession_view,
url(r'^sessions/(?P<psid>\d+)/detach/(?P<id>\d+)/user/(?P<userid>\d+)/$',views.plannedsession_detach_view),
url(r'^sessions/(?P<psid>\d+)/detach/(?P<id>\d+)/$',views.plannedsession_detach_view),
url(r'^sessions/(?P<id>\d+)/$',views.plannedsession_view,
name='plannedsession_view'),
url(r'^sessions/(?P<id>\d+)/user/(?P<userid>\d+)$',views.plannedsession_view,
url(r'^sessions/(?P<id>\d+)/user/(?P<userid>\d+)/$',views.plannedsession_view,
name='plannedsession_view'),
url(r'^sessions/(?P<pk>\d+)/deleteconfirm$',views.PlannedSessionDelete.as_view()),
url(r'^sessions/(?P<pk>\d+)/delete$',views.PlannedSessionDelete.as_view(),

View File

@@ -116,6 +116,7 @@ from sporttracksstuff import sporttracks_open
from tpstuff import tp_open
from iso8601 import ParseError
import stravastuff
from stravastuff import strava_open
import polarstuff
import sporttracksstuff
import underarmourstuff
@@ -1732,6 +1733,12 @@ def workout_strava_upload_view(request,id=0):
message = ""
r = getrower(request.user)
res = -1
try:
thetoken = strava_open(request.user)
except NoTokenError:
return HttpResponseRedirect("/rowers/me/stravaauthorize")
if (r.stravatoken == '') or (r.stravatoken is None):
s = "Token doesn't exist. Need to authorize"
return HttpResponseRedirect("/rowers/me/stravaauthorize/")
@@ -1748,11 +1755,16 @@ def workout_strava_upload_view(request,id=0):
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
except TypeError:
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
activity_type = r.stravaexportas
res,mes = stravastuff.handle_stravaexport(f,w.name,
r.stravatoken,
description=newnotes,
activity_type=activity_type)
if w.workouttype in mytypes.rowtypes:
activity_type = r.stravaexportas
else:
activity_type = mytypes.stravamapping[w.workouttype]
res,mes = stravastuff.handle_stravaexport(
f,w.name,
r.stravatoken,
description=newnotes,
activity_type=activity_type)
if res==0:
messages.error(request,mes)
w.uploadedtostrava = -1
@@ -2421,15 +2433,22 @@ def rower_process_stravacallback(request):
if res[0]:
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
r = getrower(request.user)
r.stravatoken = access_token
r.stravatokenexpirydate = expirydatetime
r.stravarefreshtoken = refresh_token
r.save()
successmessage = "Tokens stored. Good to go"
messages.info(request,successmessage)
return imports_view(request)
url = reverse(workouts_view)
return HttpResponseRedirect(url)
else:
message = "Something went wrong with the Strava authorization"
messages.error(request,message)
@@ -3373,6 +3392,11 @@ def addmanual_view(request):
except KeyError:
rankingpiece = False
try:
duplicate = form.cleaned_data['duplicate']
except KeyError:
duplicate = False
if private:
privacy = 'private'
else:
@@ -3395,6 +3419,7 @@ def addmanual_view(request):
avghr=avghr,
rankingpiece=rankingpiece,
avgpwr=avgpwr,
duplicate=duplicate,
avgspm=avgspm,
title = name,
notes=notes,
@@ -5325,6 +5350,8 @@ def team_comparison_select(request,
r = getrequestrower(request,userid=userid)
requestrower = getrower(request.user)
request.session.pop('ps',None)
if 'waterboattype' in request.session:
waterboattype = request.session['waterboattype']
else:
@@ -5521,10 +5548,53 @@ def team_comparison_select(request,
'teams':get_my_teams(request.user),
})
@login_required()
def plannedsession_compare_view(request,id=0,userid=0):
r = getrequestrower(request,userid=userid)
try:
ps = PlannedSession.objects.get(id=id)
except PlannedSession.DoesNotExist:
raise Http404("Planned session does not exist")
m = ps.manager
mm = m.rower
if ps.manager != request.user:
if r.rowerplan == 'coach':
teams = Team.objects.filter(manager=request.user)
members = Rower.objects.filter(team__in=teams).distinct()
teamusers = [m.user for m in members]
if ps.manager not in teamusers:
raise PermissionDenied("You do not have access to this session")
elif r not in ps.rower.all():
raise PermissionDenied("You do not have access to this session")
workouts = Workout.objects.filter(plannedsession=ps)
ids = [int(w.id) for w in workouts]
labeldict = {
int(w.id): w.__unicode__() for w in workouts
}
xparam = 'time'
yparam = 'hr'
plottype = 'line'
request.session['ids'] = ids
request.session['xparam'] = xparam
request.session['yparam'] = yparam
request.session['plottype'] = plottype
request.session['ps'] = ps.id
url = reverse(multi_compare_view,kwargs={'userid':userid,'id':ids[0]})
return HttpResponseRedirect(url)
# Team comparison
@login_required()
def multi_compare_view(request,id=0):
def multi_compare_view(request,id=0,userid=0):
promember=0
if not request.user.is_anonymous():
r = getrower(request.user)
@@ -5549,45 +5619,9 @@ def multi_compare_view(request,id=0):
int(w.id): w.__unicode__() for w in workouts
}
res = interactive_multiple_compare_chart(ids,xparam,yparam,
promember=promember,
plottype=plottype,
labeldict=labeldict)
script = res[0]
div = res[1]
errormessage = res[3]
if errormessage != '':
messages.error(request,errormessage)
r = getrower(request.user)
breadcrumbs = [
{
'url':'/rowers/list-workouts',
'name':'Workouts'
},
{
'url':reverse(team_comparison_select,kwargs={'teamid':teamid}),
'name': 'Compare Select'
},
{
'url':reverse(multi_compare_view),
'name': 'Comparison Chart'
}
]
return render(request,'multicompare.html',
{'interactiveplot':script,
'the_div':div,
'breadcrumbs':breadcrumbs,
'rower':r,
'active':'nav-workouts',
'promember':promember,
'teamid':teamid,
'chartform':chartform,
'teams':get_my_teams(request.user),
})
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 = ChartParamChoiceForm(request.POST)
if chartform.is_valid():
xparam = chartform.cleaned_data['xparam']
@@ -5601,42 +5635,24 @@ def multi_compare_view(request,id=0):
labeldict = {
int(w.id): w.__unicode__() for w in workouts
}
res = interactive_multiple_compare_chart(ids,xparam,yparam,
promember=promember,
plottype=plottype,
labeldict=labeldict)
script = res[0]
div = res[1]
r = getrower(request.user)
breadcrumbs = [
{
'url':'/rowers/list-workouts',
'name':'Workouts'
},
{
'url':reverse(team_comparison_select,kwargs={'teamid':teamid}),
'name': 'Compare Select'
},
{
'url':reverse(multi_compare_view),
'name': 'Comparison Chart'
elif 'ids' in request.session and 'plottype' in request.session:
xparam = request.session['xparam']
yparam = request.session['yparam']
plottype = request.session['plottype']
teamid = 0
ids = request.session['ids']
workouts = [Workout.objects.get(id=id) for id in ids]
labeldict = {
int(w.id): w.__unicode__() for w in workouts
}
chartform = ChartParamChoiceForm(
initial = {
'xparam':xparam,
'yparam':yparam,
'plottype':plottype,
'teamid':teamid
}
]
return render(request,'multicompare.html',
{'interactiveplot':script,
'the_div':div,
'breadcrumbs':breadcrumbs,
'rower':r,
'active':'nav-workouts',
'promember':promember,
'teamid':teamid,
'chartform':chartform,
'teams':get_my_teams(request.user),
})
)
else:
url = reverse(team_comparison_select,
@@ -5645,6 +5661,76 @@ def multi_compare_view(request,id=0):
'teamid':0})
return HttpResponseRedirect(url)
res = interactive_multiple_compare_chart(ids,xparam,yparam,
promember=promember,
plottype=plottype,
labeldict=labeldict)
script = res[0]
div = res[1]
errormessage = res[3]
if errormessage != '':
messages.error(request,errormessage)
r = getrower(request.user)
breadcrumbs = [
{
'url':'/rowers/list-workouts',
'name':'Workouts'
},
{
'url':reverse(team_comparison_select,kwargs={'teamid':teamid}),
'name': 'Compare Select'
},
{
'url':reverse(multi_compare_view),
'name': 'Comparison Chart'
}
]
if 'ps' in request.session:
ps = PlannedSession.objects.get(id=int(request.session['ps']))
breadcrumbs = [
{
'url': reverse(plannedsessions_view,
kwargs={'userid':userid}),
'name': 'Sessions'
},
{
'url':reverse(plannedsession_view,
kwargs={
'userid':userid,
'id':ps.id,
}
),
'name': ps.id
},
{
'url':reverse(plannedsession_compare_view,
kwargs={
'userid':userid,
'id':ps.id,
}
),
'name': 'Compare'
}
]
return render(request,'multicompare.html',
{'interactiveplot':script,
'the_div':div,
'breadcrumbs':breadcrumbs,
'rower':r,
'active':'nav-workouts',
'promember':promember,
'teamid':teamid,
'chartform':chartform,
'teams':get_my_teams(request.user),
})
# Multi Flex Chart with Grouping
@user_passes_test(ispromember,login_url="/rowers/promembership",
message="This functionality requires a Pro plan or higher",
@@ -6740,6 +6826,7 @@ def workouts_view(request,message='',successmessage='',
enddate = startdate
startdate = s
startdatestring = startdate.strftime('%Y-%m-%d')
enddatestring = enddate.strftime('%Y-%m-%d')
@@ -6755,6 +6842,10 @@ def workouts_view(request,message='',successmessage='',
except ValueError:
activity_enddate = enddate
g_startdate = activity_startdate
g_enddate = activity_enddate
if teamid:
try:
theteam = Team.objects.get(id=teamid)
@@ -6771,6 +6862,7 @@ def workouts_view(request,message='',successmessage='',
team=theteam,
startdatetime__gte=activity_startdate,
startdatetime__lte=activity_enddate,
duplicate=False,
privacy='visible').order_by("-date", "-starttime")
elif theteam.viewing == 'coachonly':
workouts = Workout.objects.filter(
@@ -6782,6 +6874,7 @@ def workouts_view(request,message='',successmessage='',
team=theteam,user=r,
startdatetime__gte=activity_startdate,
enddatetime__lte=activity_enddate,
duplicate=False,
privacy='visible').order_by("-startdatetime")
@@ -6797,6 +6890,7 @@ def workouts_view(request,message='',successmessage='',
user=r,
startdatetime__gte=activity_startdate,
startdatetime__lte=activity_enddate,
duplicate=False,
privacy='visible').order_by("-startdatetime")
else:
theteam = None
@@ -6806,9 +6900,18 @@ def workouts_view(request,message='',successmessage='',
startdatetime__lte=enddate).order_by("-date", "-starttime")
g_workouts = Workout.objects.filter(
user=r,
duplicate=False,
startdatetime__gte=activity_startdate,
startdatetime__lte=activity_enddate).order_by("-startdatetime")
if len(g_workouts) == 0:
g_workouts = Workout.objects.filter(
user=r,
startdatetime__gte=timezone.now()-timedelta(days=15)).order_by("-startdatetime")
g_enddate = timezone.now()
g_startdate = (timezone.now()-timedelta(days=15))
if rankingonly:
workouts = workouts.exclude(rankingpiece=False)
@@ -6846,9 +6949,10 @@ def workouts_view(request,message='',successmessage='',
else:
stack='type'
script,div = interactive_activitychart(g_workouts,
activity_startdate,
activity_enddate,
g_startdate,
g_enddate,
stack=stack)
messages.info(request,successmessage)
@@ -9845,6 +9949,11 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
except KeyError:
rankingpiece =- Workout.objects.get(id=id).rankingpiece
try:
duplicate = form.cleaned_data['duplicate']
except KeyError:
duplicate = Workout.objects.get(id=id).duplicate
if private:
privacy = 'private'
else:
@@ -9884,6 +9993,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
row.duration = duration
row.distance = distance
row.boattype = boattype
row.duplicate = duplicate
row.privacy = privacy
row.rankingpiece = rankingpiece
row.timezone = thetimezone
@@ -10268,6 +10378,11 @@ def workout_add_chart_view(request,id,plotnr=1):
# The page where you select which Strava workout to import
@login_required()
def workout_stravaimport_view(request,message="",userid=0):
try:
thetoken = strava_open(request.user)
except NoTokenError:
return HttpResponseRedirect("/rowers/me/stravaauthorize/")
res = stravastuff.get_strava_workout_list(request.user)
r = getrequestrower(request,userid=userid)
@@ -10277,6 +10392,7 @@ def workout_stravaimport_view(request,message="",userid=0):
r = getrower(request.user)
if (res.status_code != 200):
if (res.status_code == 401):
r = getrower(request.user)
@@ -10420,15 +10536,7 @@ def workout_runkeeperimport_view(request,message="",userid=0):
def workout_underarmourimport_view(request,message="",userid=0):
res = underarmourstuff.get_underarmour_workout_list(request.user)
if (res.status_code != 200):
if (res.status_code == 401):
r = getrower(request.user)
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
s = "Token doesn't exist. Need to authorize"
return HttpResponseRedirect("/rowers/me/underarmourauthorize/")
message = "Something went wrong in workout_underarmourimport_view"
messages.error(request,message)
url = reverse(workouts_view)
return HttpResponseRedirect(url)
return HttpResponseRedirect("/rowers/me/underarmourauthorize/")
workouts = []
items = res.json()['_embedded']['workouts']
@@ -10778,7 +10886,7 @@ def workout_getimportview(request,externalid,source = 'c2'):
if strokedata.empty:
distance = data['distance']
c2id = data['id']
workouttype = data['type']
workouttype = mytypes.c2mappinginv[data['type']]
verified = data['verified']
startdatetime = iso8601.parse_date(data['date'])
weightclass = data['weight_class']
@@ -10827,7 +10935,7 @@ def workout_getimportview(request,externalid,source = 'c2'):
return HttpResponseRedirect(url)
# strokdata not empty - continue
# strokedata not empty - continue
id,message = importsources[source].add_workout_from_data(
request.user,
externalid,data,
@@ -15302,6 +15410,26 @@ def plannedsession_edit_view(request,id=0,userid=0):
})
@login_required()
def plannedsession_detach_view(request,id=0,psid=0):
r = getrequestrower(request)
try:
ps = PlannedSession.objects.get(id=psid)
except PlannedSession.DoesNotExist:
raise Http404("Planned Session does not exist")
w = get_workout(id)
if (checkworkoutuser(request.user,w)==False):
return HttpResponseForbidden("Permission Error")
remove_workout_plannedsession(w,ps)
url = reverse(plannedsession_view,kwargs={'id':psid})
return HttpResponseRedirect(url)
@login_required()
def plannedsession_view(request,id=0,userid=0):