Merge branch 'release/v15.46'
This commit is contained in:
@@ -64,6 +64,20 @@ def process_webhook(notification):
|
|||||||
f.write(timestamp+' '+notification.kind+'\n')
|
f.write(timestamp+' '+notification.kind+'\n')
|
||||||
if notification.kind == 'subscription_charged_successfully':
|
if notification.kind == 'subscription_charged_successfully':
|
||||||
return send_invoice(notification.subscription)
|
return send_invoice(notification.subscription)
|
||||||
|
if notification.kind == 'subscription_canceled':
|
||||||
|
subscription = notification.subscription
|
||||||
|
rs = Rower.objects.filter(subscription_id=subscription.id)
|
||||||
|
if rs.count() == 0:
|
||||||
|
return 0
|
||||||
|
r = rs[0]
|
||||||
|
result,mesg,errormsg = cancel_subscription(r,subscription.id)
|
||||||
|
if result:
|
||||||
|
with open('braintreewebhooks.log','a') as f:
|
||||||
|
f.write('Subscription canceled: '+subscription.id+'\n')
|
||||||
|
return subscription.id
|
||||||
|
with open('braintreewebhooks.log','a') as f:
|
||||||
|
f.write('Could not cancel Subscription: '+subscription.id+'\n')
|
||||||
|
return 0
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def send_invoice(subscription):
|
def send_invoice(subscription):
|
||||||
|
|||||||
@@ -1061,8 +1061,8 @@ def get_workoutsummaries(userid,startdate):
|
|||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def workout_goldmedalstandard(workout):
|
def workout_goldmedalstandard(workout,reset=False):
|
||||||
if workout.goldmedalstandard > 0:
|
if workout.goldmedalstandard > 0 and not reset:
|
||||||
return workout.goldmedalstandard,workout.goldmedalseconds
|
return workout.goldmedalstandard,workout.goldmedalseconds
|
||||||
if workout.workouttype in rowtypes:
|
if workout.workouttype in rowtypes:
|
||||||
goldmedalstandard,goldmedalseconds = calculate_goldmedalstandard(workout.user,workout)
|
goldmedalstandard,goldmedalseconds = calculate_goldmedalstandard(workout.user,workout)
|
||||||
@@ -2255,6 +2255,7 @@ def new_workout_from_df(r, df,
|
|||||||
boattype = parent.boattype
|
boattype = parent.boattype
|
||||||
notes = parent.notes
|
notes = parent.notes
|
||||||
summary = parent.summary
|
summary = parent.summary
|
||||||
|
rpe = parent.rpe
|
||||||
if parent.privacy == 'hidden':
|
if parent.privacy == 'hidden':
|
||||||
makeprivate = True
|
makeprivate = True
|
||||||
else:
|
else:
|
||||||
@@ -2267,6 +2268,7 @@ def new_workout_from_df(r, df,
|
|||||||
notes = ''
|
notes = ''
|
||||||
summary = ''
|
summary = ''
|
||||||
makeprivate = False
|
makeprivate = False
|
||||||
|
rpe = 0
|
||||||
if startdatetime == '':
|
if startdatetime == '':
|
||||||
startdatetime = timezone.now()
|
startdatetime = timezone.now()
|
||||||
|
|
||||||
@@ -2305,6 +2307,7 @@ def new_workout_from_df(r, df,
|
|||||||
inboard=inboard,
|
inboard=inboard,
|
||||||
makeprivate=makeprivate,
|
makeprivate=makeprivate,
|
||||||
dosmooth=False,
|
dosmooth=False,
|
||||||
|
rpe=rpe,
|
||||||
consistencychecks=False)
|
consistencychecks=False)
|
||||||
|
|
||||||
job = myqueue(queuehigh,handle_calctrimp,id,csvfilename,r.ftp,r.sex,r.hrftp,r.max,r.rest)
|
job = myqueue(queuehigh,handle_calctrimp,id,csvfilename,r.ftp,r.sex,r.hrftp,r.max,r.rest)
|
||||||
@@ -3015,10 +3018,10 @@ def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def workout_trimp(w):
|
def workout_trimp(w,reset=False):
|
||||||
r = w.user
|
r = w.user
|
||||||
|
|
||||||
if w.trimp > 0:
|
if w.trimp > 0 and not reset:
|
||||||
return w.trimp,w.hrtss
|
return w.trimp,w.hrtss
|
||||||
|
|
||||||
r = w.user
|
r = w.user
|
||||||
@@ -3059,8 +3062,8 @@ def workout_trimp(w):
|
|||||||
|
|
||||||
return 0,0
|
return 0,0
|
||||||
|
|
||||||
def workout_rscore(w):
|
def workout_rscore(w,reset=False):
|
||||||
if w.rscore > 0:
|
if w.rscore > 0 and not reset:
|
||||||
return w.rscore,w.normp
|
return w.rscore,w.normp
|
||||||
|
|
||||||
r = w.user
|
r = w.user
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ def interactive_hr_piechart(df,rower,title,totalseconds=0):
|
|||||||
if totalseconds == 0:
|
if totalseconds == 0:
|
||||||
totalseconds = sumtimehr
|
totalseconds = sumtimehr
|
||||||
|
|
||||||
|
hrzones = rower.hrzones
|
||||||
|
|
||||||
qry = 'hr < {ut2}'.format(ut2=rower.ut2)
|
qry = 'hr < {ut2}'.format(ut2=rower.ut2)
|
||||||
qrydata = df.query(qry)
|
qrydata = df.query(qry)
|
||||||
@@ -253,12 +253,12 @@ def interactive_hr_piechart(df,rower,title,totalseconds=0):
|
|||||||
frac_an = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr
|
frac_an = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr
|
||||||
|
|
||||||
datadict = {
|
datadict = {
|
||||||
'<ut2':frac_lut2,
|
'<{ut2}'.format(ut2=hrzones[1]):frac_lut2,
|
||||||
'ut2': frac_ut2,
|
'{ut2}'.format(ut2=hrzones[1]): frac_ut2,
|
||||||
'ut1': frac_ut1,
|
'{ut1}'.format(ut1=hrzones[2]): frac_ut1,
|
||||||
'at': frac_at,
|
'{at}'.format(at=hrzones[3]): frac_at,
|
||||||
'tr': frac_tr,
|
'{tr}'.format(tr=hrzones[4]): frac_tr,
|
||||||
'an': frac_an,
|
'{an}'.format(an=hrzones[5]): frac_an,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -270,7 +270,15 @@ def interactive_hr_piechart(df,rower,title,totalseconds=0):
|
|||||||
data = pd.Series(datadict).reset_index(name='value').rename(columns={'index':'zone'})
|
data = pd.Series(datadict).reset_index(name='value').rename(columns={'index':'zone'})
|
||||||
data['angle'] = data['value']/data['value'].sum() * 2*pi
|
data['angle'] = data['value']/data['value'].sum() * 2*pi
|
||||||
data['color'] = colors
|
data['color'] = colors
|
||||||
data['zone'] = ['<ut2','ut2','ut1','at','tr','an']
|
data['zone'] = [
|
||||||
|
'<{ut2}'.format(ut2=hrzones[1]),
|
||||||
|
'{ut2}'.format(ut2=hrzones[1]),
|
||||||
|
'{ut1}'.format(ut1=hrzones[2]),
|
||||||
|
'{at}'.format(at=hrzones[3]),
|
||||||
|
'{tr}'.format(tr=hrzones[4]),
|
||||||
|
'{an}'.format(an=hrzones[5])
|
||||||
|
]
|
||||||
|
|
||||||
data['totaltime'] = pd.Series([pretty_timedelta(v) for v in data['value']])
|
data['totaltime'] = pd.Series([pretty_timedelta(v) for v in data['value']])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,11 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% if workout|previousworkout:rower.user %}
|
{% if workout|previousworkout:rower.user %}
|
||||||
<a href="/rowers/workout/{{ workout|previousworkout:rower.user }}/flexchart/"
|
<a href="/rowers/workout/{{ workout|previousworkout:rower.user }}/flexchartstacked/"
|
||||||
title="Jump to preceding workout"><em>Previous</em></a>
|
title="Jump to preceding workout"><em>Previous</em></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if workout|nextworkout:rower.user %}
|
{% if workout|nextworkout:rower.user %}
|
||||||
<a href="/rowers/workout/{{ workout|nextworkout:rower.user }}/flexchart/"
|
<a href="/rowers/workout/{{ workout|nextworkout:rower.user }}/flexchartstacked/"
|
||||||
title="Jump to following workout"><em>Next</em></a>
|
title="Jump to following workout"><em>Next</em></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<h1>Split Workout</h1>
|
<h1>Split Workout</h1>
|
||||||
|
|
||||||
{% localtime on %}
|
{% localtime on %}
|
||||||
<ul class="main-content">
|
<ul class="main-content">
|
||||||
<li class="grid_2">
|
<li class="grid_2">
|
||||||
@@ -17,19 +17,19 @@
|
|||||||
{{ form.as_table }}
|
{{ form.as_table }}
|
||||||
</table>
|
</table>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input class="button green" type="submit" value="Split">
|
<input class="button" type="submit" value="Split">
|
||||||
</form>
|
</form>
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<li class="grid_2">
|
<li class="grid_2">
|
||||||
|
|
||||||
<script src="https://cdn.pydata.org/bokeh/release/bokeh-2.2.3.min.js"></script>
|
<script src="https://cdn.pydata.org/bokeh/release/bokeh-2.2.3.min.js"></script>
|
||||||
<script async="true" type="text/javascript">
|
<script async="true" type="text/javascript">
|
||||||
Bokeh.set_log_level("info");
|
Bokeh.set_log_level("info");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{ thescript |safe }}
|
{{ thescript |safe }}
|
||||||
|
|
||||||
{{ thediv |safe }}
|
{{ thediv |safe }}
|
||||||
|
|
||||||
<table width=100%>
|
<table width=100%>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -17,8 +17,24 @@
|
|||||||
</form>
|
</form>
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<li class="grid_4">
|
<li class="maxheight grid_4">
|
||||||
{{ htmltable|safe }}
|
<table width=100% class="listtable shortpadded">
|
||||||
|
<th>
|
||||||
|
{% for i in cols %}
|
||||||
|
<td><strong>{{i}}</strong> <a href="/rowers/workout/{{ workout.id|encode }}/{{i}}/erase/">erase</a></td>
|
||||||
|
{% endfor %}
|
||||||
|
</th>
|
||||||
|
{% for row in data.values.tolist %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ forloop.counter0 }}
|
||||||
|
</td>
|
||||||
|
{% for value in row %}
|
||||||
|
<td>{{ value }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
44
rowers/templates/workout_erase_column.html
Normal file
44
rowers/templates/workout_erase_column.html
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{% extends "newbase.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
|
||||||
|
{% block title %}Workout Data{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>Erase {{ column }} for {{ workout.name }}</h1>
|
||||||
|
|
||||||
|
<ul class="main-content">
|
||||||
|
<li class="grid_4">
|
||||||
|
<p>
|
||||||
|
This will erase column {{ column }} from the following workout:
|
||||||
|
</p>
|
||||||
|
<table width=100%>
|
||||||
|
<tr>
|
||||||
|
<th>Name:</th><td>{{ workout.name }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<th>Date:</th><td>{{ workout.date }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<th>Time:</th><td>{{ workout.starttime }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<th>Distance:</th><td>{{ workout.distance }}m</td>
|
||||||
|
</tr><tr>
|
||||||
|
<th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p>
|
||||||
|
The data cannot be recovered. If you are sure, please confirm.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="submit" value="Confirm">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
{% include 'menu_workout.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -85,6 +85,10 @@ def icon(s):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return 'fa-chart-line'
|
return 'fa-chart-line'
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def datarows(data):
|
||||||
|
return range(len(data))
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def adaptive(s):
|
def adaptive(s):
|
||||||
u = s
|
u = s
|
||||||
@@ -519,11 +523,18 @@ def mayeditplan(obj,request):
|
|||||||
|
|
||||||
return mayedit
|
return mayedit
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def iterrows(df):
|
||||||
|
return df.iterrows()
|
||||||
|
|
||||||
@register.filter(name='times')
|
@register.filter(name='times')
|
||||||
def times(number):
|
def times(number):
|
||||||
return range(number)
|
return range(number)
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def get_df_iloc(data,i,j):
|
||||||
|
return data.iloc(i,j)
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def get_field_id(id,s,form):
|
def get_field_id(id,s,form):
|
||||||
field_name = s+str(id)
|
field_name = s+str(id)
|
||||||
|
|||||||
@@ -463,6 +463,8 @@ urlpatterns = [
|
|||||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/stats/$',views.workout_stats_view,name='workout_stats_view'),
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/stats/$',views.workout_stats_view,name='workout_stats_view'),
|
||||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/data/$',views.workout_data_view,
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/data/$',views.workout_data_view,
|
||||||
name='workout_data_view'),
|
name='workout_data_view'),
|
||||||
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/(?P<column>\w+)/erase/$',views.workout_erase_column_view,
|
||||||
|
name='workout_erase_column_view'),
|
||||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/zeropower-confirm/$',views.remove_power_confirm_view,
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/zeropower-confirm/$',views.remove_power_confirm_view,
|
||||||
name='remove_power_confirm_view'),
|
name='remove_power_confirm_view'),
|
||||||
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/zeropower/$',views.remove_power_view,
|
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/zeropower/$',views.remove_power_view,
|
||||||
|
|||||||
@@ -3166,6 +3166,145 @@ def instroke_chart(request,id=0,metric=''):
|
|||||||
|
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
# erase column
|
||||||
|
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
|
||||||
|
def workout_erase_column_view(request, id=0,column=''):
|
||||||
|
r = getrower(request.user)
|
||||||
|
w = get_workoutuser(id,request)
|
||||||
|
|
||||||
|
protected = ['time','pace','velo','cumdist','ftime','fpace',]
|
||||||
|
if column in protected:
|
||||||
|
messages.error(request,'You cannot erase this protected column')
|
||||||
|
url = reverse('workout_data_view',kwargs={
|
||||||
|
'id':encoder.encode_hex(w.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = dataprep.getsmallrowdata_db([column],ids=[w.id])
|
||||||
|
except:
|
||||||
|
messages.error(request,'Invalid column')
|
||||||
|
url = reverse('workout_data_view',kwargs={
|
||||||
|
'id':encoder.encode_hex(w.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cdata = data[column]
|
||||||
|
except KeyError:
|
||||||
|
url = reverse('workout_data_view',kwargs={
|
||||||
|
'id':encoder.encode_hex(w.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
|
||||||
|
if not column:
|
||||||
|
url = reverse('workout_data_view',kwargs={
|
||||||
|
'id':encoder.encode_hex(w.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
|
||||||
|
mms = {}
|
||||||
|
for m in rowingmetrics:
|
||||||
|
mms[m[0]] = m[1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
defaultvalue = mms[column]['default']
|
||||||
|
except KeyError:
|
||||||
|
if not mms[column]['null']:
|
||||||
|
messages.error(request,'You cannot erase this protected column')
|
||||||
|
url = reverse('workout_data_view',kwargs={
|
||||||
|
'id':encoder.encode_hex(w.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
defaultvalue = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
columnl = dataprep.columndict[column]
|
||||||
|
except KeyError:
|
||||||
|
messages.error(request,'You cannot erase this column')
|
||||||
|
url = reverse('workout_data_view',kwargs={
|
||||||
|
'id':encoder.encode_hex(w.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
|
||||||
|
row,workout = dataprep.getrowdata(id=w.id)
|
||||||
|
row.df[columnl] = defaultvalue
|
||||||
|
os.remove(w.csvfilename+'.gz')
|
||||||
|
|
||||||
|
|
||||||
|
row.write_csv(w.csvfilename,gzip=True)
|
||||||
|
|
||||||
|
|
||||||
|
row,workout = dataprep.getrowdata(id=w.id)
|
||||||
|
datadf = dataprep.dataprep(row.df,id=w.id)
|
||||||
|
|
||||||
|
if column == 'hr':
|
||||||
|
w.hrtss = 0
|
||||||
|
w.trimp = 0
|
||||||
|
w.save()
|
||||||
|
|
||||||
|
if column == 'power':
|
||||||
|
w.rscore = 0
|
||||||
|
w.normp = 0
|
||||||
|
w.goldmedalstandard = -1
|
||||||
|
w.goldmedalseconds = 0
|
||||||
|
w.save()
|
||||||
|
|
||||||
|
trimp,hrtss = dataprep.workout_trimp(w,reset=True)
|
||||||
|
rscore,normp = dataprep.workout_rscore(w,reset=True)
|
||||||
|
goldstandard,goldstandardduration = dataprep.workout_goldmedalstandard(w,reset=True)
|
||||||
|
|
||||||
|
|
||||||
|
messages.info(request,'Data for column '+column+' have been erased')
|
||||||
|
url = reverse('workout_data_view',kwargs={
|
||||||
|
'id':encoder.encode_hex(w.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
'url':'/rowers/list-workouts/',
|
||||||
|
'name':'Workouts'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':get_workout_default_page(request,id),
|
||||||
|
'name': w.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':reverse('workout_data_view',kwargs={'id':id}),
|
||||||
|
'name': 'Data Explorer'
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
'workout_erase_column.html',
|
||||||
|
{
|
||||||
|
'column':column,
|
||||||
|
'teams':get_my_teams(request.user),
|
||||||
|
'workout': w,
|
||||||
|
'breadcrumbs': breadcrumbs,
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# data explorer
|
# data explorer
|
||||||
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
|
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
|
||||||
@@ -3268,6 +3407,8 @@ def workout_data_view(request, id=0):
|
|||||||
'workout_data.html',
|
'workout_data.html',
|
||||||
{
|
{
|
||||||
'htmltable': htmltable,
|
'htmltable': htmltable,
|
||||||
|
'data':datadf,
|
||||||
|
'cols':datadf.columns,
|
||||||
'form':form,
|
'form':form,
|
||||||
'teams':get_my_teams(request.user),
|
'teams':get_my_teams(request.user),
|
||||||
'workout': w,
|
'workout': w,
|
||||||
|
|||||||
Reference in New Issue
Block a user