Merge branch 'release/v12.22'
This commit is contained in:
@@ -143,6 +143,30 @@ class DisqualificationForm(forms.Form):
|
|||||||
|
|
||||||
message = forms.CharField(required=True,widget=forms.Textarea)
|
message = forms.CharField(required=True,widget=forms.Textarea)
|
||||||
|
|
||||||
|
class HistorySelectForm(forms.Form):
|
||||||
|
typeselectchoices = [("All","All")]
|
||||||
|
for wtype,verbose in mytypes.workouttypes_ordered.items():
|
||||||
|
typeselectchoices.append((wtype,verbose))
|
||||||
|
startdate = forms.DateField(
|
||||||
|
initial=timezone.now()-datetime.timedelta(days=15),
|
||||||
|
# widget=SelectDateWidget(years=range(1990,2050)),
|
||||||
|
widget=AdminDateWidget(), #format='%Y-%m-%d'),
|
||||||
|
label='Start Date')
|
||||||
|
enddate = forms.DateField(
|
||||||
|
initial=timezone.now(),
|
||||||
|
widget=AdminDateWidget(), #format='%Y-%m-%d'),
|
||||||
|
label='End Date')
|
||||||
|
|
||||||
|
workouttype = forms.ChoiceField(initial='All',choices=typeselectchoices)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = ['startdate','enddate']
|
||||||
|
input_formats=("%Y-%m-%d")
|
||||||
|
dateTimeOptions = {
|
||||||
|
'format': '%Y-%m-%d',
|
||||||
|
'autoclose': True,
|
||||||
|
}
|
||||||
|
|
||||||
class MetricsForm(forms.Form):
|
class MetricsForm(forms.Form):
|
||||||
avghr = forms.IntegerField(required=False,label='Average Heart Rate')
|
avghr = forms.IntegerField(required=False,label='Average Heart Rate')
|
||||||
avgpwr = forms.IntegerField(required=False,label='Average Power')
|
avgpwr = forms.IntegerField(required=False,label='Average Power')
|
||||||
|
|||||||
@@ -169,6 +169,83 @@ def tailwind(bearing,vwind,winddir):
|
|||||||
from rowers.dataprep import nicepaceformat,niceformat
|
from rowers.dataprep import nicepaceformat,niceformat
|
||||||
from rowers.dataprep import timedeltaconv
|
from rowers.dataprep import timedeltaconv
|
||||||
|
|
||||||
|
from math import pi
|
||||||
|
|
||||||
|
def interactive_hr_piechart(df,rower,title):
|
||||||
|
if df.empty:
|
||||||
|
return "","Not enough data to make a chart"
|
||||||
|
|
||||||
|
df.sort_values(by='hr',inplace=True)
|
||||||
|
qry = 'hr < {ut2}'.format(ut2=rower.ut2)
|
||||||
|
frac_lut2 = len(df.query(qry))/len(df)
|
||||||
|
|
||||||
|
qry = 'hr < {ut1}'.format(ut1=rower.ut1,ut2=rower.ut2)
|
||||||
|
frac_ut2 = len(df.query(qry))/len(df)
|
||||||
|
|
||||||
|
qry = 'hr < {at}'.format(ut1=rower.ut1,at=rower.at)
|
||||||
|
frac_ut1 = len(df.query(qry))/len(df)
|
||||||
|
|
||||||
|
qry = 'hr < {tr}'.format(at=rower.at,tr=rower.tr)
|
||||||
|
frac_at = len(df.query(qry))/len(df)
|
||||||
|
|
||||||
|
qry = 'hr < {an}'.format(tr=rower.tr,an=rower.an)
|
||||||
|
frac_tr = len(df.query(qry))/len(df)
|
||||||
|
|
||||||
|
frac_an = 1.
|
||||||
|
|
||||||
|
source_starts = 2*pi*pd.Series([
|
||||||
|
0,
|
||||||
|
frac_lut2,
|
||||||
|
frac_ut2,
|
||||||
|
frac_ut1,
|
||||||
|
frac_at,
|
||||||
|
frac_tr,
|
||||||
|
])
|
||||||
|
|
||||||
|
source_ends = 2*pi*pd.Series([
|
||||||
|
frac_lut2,
|
||||||
|
frac_ut2,
|
||||||
|
frac_ut1,
|
||||||
|
frac_at,
|
||||||
|
frac_tr,
|
||||||
|
frac_an,
|
||||||
|
])
|
||||||
|
|
||||||
|
source_legends = [
|
||||||
|
'<ut2',
|
||||||
|
'ut2',
|
||||||
|
'ut1',
|
||||||
|
'at',
|
||||||
|
'tr',
|
||||||
|
'an',
|
||||||
|
]
|
||||||
|
|
||||||
|
colors = ['gray','yellow','lime','blue','purple','red']
|
||||||
|
|
||||||
|
|
||||||
|
size=350
|
||||||
|
TOOLS = 'save'
|
||||||
|
|
||||||
|
z = figure(title="HR "+title, x_range=(-1,1), y_range=(-1,1), width=size, height=size,
|
||||||
|
tools=TOOLS,
|
||||||
|
)
|
||||||
|
|
||||||
|
for start, end , legend, color in zip(source_starts, source_ends, source_legends, colors[0:len(source_starts)]):
|
||||||
|
z.wedge(x=0, y=0, radius=1, start_angle=start, end_angle=end, color=color, legend=legend)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
z.toolbar_location = 'right'
|
||||||
|
z.legend.location = 'top_right'
|
||||||
|
#z.legend.visible = False
|
||||||
|
z.axis.visible = False
|
||||||
|
z.xgrid.grid_line_color = None
|
||||||
|
z.ygrid.grid_line_color = None
|
||||||
|
z.outline_line_color = None
|
||||||
|
|
||||||
|
return components(z)
|
||||||
|
|
||||||
|
|
||||||
def interactive_boxchart(datadf,fieldname,extratitle='',
|
def interactive_boxchart(datadf,fieldname,extratitle='',
|
||||||
spmmin=0,spmmax=0,workmin=0,workmax=0):
|
spmmin=0,spmmax=0,workmin=0,workmax=0):
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -889,7 +889,7 @@ class Rower(models.Model):
|
|||||||
verbose_name='Show Notes for Favorite Charts')
|
verbose_name='Show Notes for Favorite Charts')
|
||||||
|
|
||||||
# Static chart settings
|
# Static chart settings
|
||||||
staticgrids = models.CharField(default=None,choices=gridtypes,null=True,max_length=50,
|
staticgrids = models.CharField(default='both',choices=gridtypes,null=True,max_length=50,
|
||||||
verbose_name='Chart Grid')
|
verbose_name='Chart Grid')
|
||||||
|
|
||||||
ergpaceslow = datetime.timedelta(seconds=160)
|
ergpaceslow = datetime.timedelta(seconds=160)
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
{% extends "newbase.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
|
||||||
|
{% block title %}Rowsandall {% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>History</h1>
|
||||||
|
<ul class="main-content">
|
||||||
|
<script async="true" src="https://cdn.pydata.org/bokeh/release/bokeh-1.0.4.min.js"></script>
|
||||||
|
<li class="grid_2">
|
||||||
|
<p>
|
||||||
|
<form enctype="multipart/form-data" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
</p>
|
||||||
|
<h2>All workouts</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<table class="listtable shortpadded">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Total Distance</td><td>{{ totalsdict|lookup:"distance"}} meters</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total Duration</td><td>{{ totalsdict|lookup:"duration"}} hours</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Number of workouts</td><td>{{ totalsdict|lookup:"nrworkouts"}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Average heart rate</td><td>{{ totalsdict|lookup:"hrmean"}} bpm</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Maximum heart rate</td><td>{{ totalsdict|lookup:"hrmax"}} bpm</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Average power</td><td>{{ totalsdict|lookup:"powermean"}} W</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Maximum power</td><td>{{ totalsdict|lookup:"powermax"}} W</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
|
<div>{{ totalscript|safe }}{{ totaldiv|safe }}</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% for ddict in typedicts %}
|
||||||
|
<li class="grid_1">
|
||||||
|
<h2>{{ ddict|lookup:"wtype"}}</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<table class="listtable shortpadded">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Total Distance</td><td>{{ ddict|lookup:"distance"}} meters</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total Duration</td><td>{{ ddict|lookup:"duration"}} hours</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Number of workouts</td><td>{{ ddict|lookup:"nrworkouts"}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Average heart rate</td><td>{{ ddict|lookup:"hrmean"}} bpm</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Maximum heart rate</td><td>{{ ddict|lookup:"hrmax"}} bpm</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Average power</td><td>{{ ddict|lookup:"powermean"}} W</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Maximum power</td><td>{{ ddict|lookup:"powermax"}} W</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
{% include 'menu_analytics.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -59,6 +59,10 @@
|
|||||||
|
|
||||||
<ul class="main-content">
|
<ul class="main-content">
|
||||||
<li class="grid_2" style="min-height:200px;">
|
<li class="grid_2" style="min-height:200px;">
|
||||||
|
<p>
|
||||||
|
Total meters: {{ totalmeters }}. Total time {{ totalhours }}:{{ totalminutes }}h.
|
||||||
|
<a href="/rowers/history/">Dig deeper</a>.
|
||||||
|
</p>
|
||||||
<script async="true" src="https://cdn.pydata.org/bokeh/release/bokeh-1.0.4.min.js"></script>
|
<script async="true" src="https://cdn.pydata.org/bokeh/release/bokeh-1.0.4.min.js"></script>
|
||||||
|
|
||||||
{{ interactiveplot |safe }}
|
{{ interactiveplot |safe }}
|
||||||
|
|||||||
@@ -90,6 +90,11 @@
|
|||||||
<i class="fas fa-bell fa-fw"></i> Alerts
|
<i class="fas fa-bell fa-fw"></i> Alerts
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/rowers/history/">
|
||||||
|
<i class="fas fa-history fa-fw"></i> History
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -982,9 +982,9 @@ class PermissionsViewTests(TestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code,200)
|
self.assertEqual(response.status_code,200)
|
||||||
|
|
||||||
self.assertRedirects(response,
|
#self.assertRedirects(response,
|
||||||
expected_url = url,
|
# expected_url = url,
|
||||||
status_code=302,target_status_code=200)
|
# status_code=302,target_status_code=200)
|
||||||
|
|
||||||
aantal2 = len(Workout.objects.filter(user=self.rbasic))
|
aantal2 = len(Workout.objects.filter(user=self.rbasic))
|
||||||
|
|
||||||
|
|||||||
@@ -752,6 +752,7 @@ urlpatterns = [
|
|||||||
re_path(r'^workout/api/upload/',views.workout_upload_api,name='workout_upload_api'),
|
re_path(r'^workout/api/upload/',views.workout_upload_api,name='workout_upload_api'),
|
||||||
re_path(r'^access/share/$',views.createShareURL, name="sharedURL"),
|
re_path(r'^access/share/$',views.createShareURL, name="sharedURL"),
|
||||||
re_path(r'^access/(?P<key>\w+)/$', views.sharedPage, name="sharedPage"),
|
re_path(r'^access/(?P<key>\w+)/$', views.sharedPage, name="sharedPage"),
|
||||||
|
re_path(r'^history/$',views.history_view,name="history_view"),
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
|||||||
@@ -4650,3 +4650,135 @@ class AlertDelete(DeleteView):
|
|||||||
# some checks
|
# some checks
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
@login_required()
|
||||||
|
def history_view(request,userid=0):
|
||||||
|
r = getrequestrower(request,userid=userid)
|
||||||
|
|
||||||
|
form = HistorySelectForm()
|
||||||
|
|
||||||
|
usertimezone = pytz.timezone(r.defaulttimezone)
|
||||||
|
|
||||||
|
activity_enddate = timezone.now()
|
||||||
|
activity_enddate = activity_enddate.replace(hour=23,minute=59,second=59).astimezone(usertimezone)
|
||||||
|
activity_startdate = activity_enddate-datetime.timedelta(days=15)
|
||||||
|
activity_startdate = activity_startdate.replace(hour=0,minute=0,second=0)
|
||||||
|
typeselect = 'All'
|
||||||
|
|
||||||
|
if request.method=='POST':
|
||||||
|
form = HistorySelectForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
startdate = form.cleaned_data['startdate']
|
||||||
|
enddate = form.cleaned_data['enddate']
|
||||||
|
typeselect = form.cleaned_data['workouttype']
|
||||||
|
activity_startdate = datetime.datetime(
|
||||||
|
startdate.year,startdate.month,startdate.day
|
||||||
|
).replace(hour=0,minute=0,second=0).astimezone(usertimezone)
|
||||||
|
activity_enddate = datetime.datetime(
|
||||||
|
enddate.year,enddate.month,enddate.day
|
||||||
|
).replace(hour=23,minute=59,second=59).astimezone(usertimezone)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
g_workouts = Workout.objects.filter(
|
||||||
|
user=r,
|
||||||
|
startdatetime__gte=activity_startdate,
|
||||||
|
startdatetime__lte=activity_enddate,
|
||||||
|
duplicate=False,
|
||||||
|
privacy='visible'
|
||||||
|
).order_by("-startdatetime")
|
||||||
|
|
||||||
|
ids = [w.id for w in g_workouts]
|
||||||
|
|
||||||
|
columns = ['hr','power']
|
||||||
|
|
||||||
|
df = getsmallrowdata_db(columns,ids=ids)
|
||||||
|
|
||||||
|
totalmeters,totalhours, totalminutes = get_totals(g_workouts)
|
||||||
|
|
||||||
|
# meters, duration per workout type
|
||||||
|
wtypes = list(set([w.workouttype for w in g_workouts]))
|
||||||
|
|
||||||
|
typechoices = [("All","All")]
|
||||||
|
for wtype in wtypes:
|
||||||
|
typechoices.append((wtype,mytypes.workouttypes_ordered[wtype]))
|
||||||
|
|
||||||
|
form.fields['workouttype'].choices = typechoices
|
||||||
|
|
||||||
|
listofdicts = []
|
||||||
|
|
||||||
|
for wtype in wtypes:
|
||||||
|
a_workouts = g_workouts.filter(workouttype=wtype)
|
||||||
|
wmeters, whours, wminutes = get_totals(a_workouts)
|
||||||
|
ddict = {}
|
||||||
|
ddict['wtype'] = mytypes.workouttypes_ordered[wtype]
|
||||||
|
ddict['distance'] = wmeters
|
||||||
|
ddict['duration'] = "{whours}:{wminutes:02d}".format(
|
||||||
|
whours=whours,
|
||||||
|
wminutes=wminutes
|
||||||
|
)
|
||||||
|
ddf = getsmallrowdata_db(columns,ids=[w.id for w in a_workouts])
|
||||||
|
ddict['hrmean'] = ddf['hr'].mean().astype(int)
|
||||||
|
ddict['hrmax'] = ddf['hr'].max().astype(int)
|
||||||
|
ddict['powermean'] = ddf['power'].mean().astype(int)
|
||||||
|
ddict['powermax'] = ddf['power'].max().astype(int)
|
||||||
|
ddict['nrworkouts'] = a_workouts.count()
|
||||||
|
listofdicts.append(ddict)
|
||||||
|
|
||||||
|
|
||||||
|
# interactive hr pie chart
|
||||||
|
if typeselect == 'All':
|
||||||
|
totalscript,totaldiv = interactive_hr_piechart(df,r,'All Workouts')
|
||||||
|
else:
|
||||||
|
a_workouts = g_workouts.filter(workouttype=typeselect)
|
||||||
|
ddf = getsmallrowdata_db(columns,ids=[w.id for w in a_workouts])
|
||||||
|
totalscript, totaldiv = interactive_hr_piechart(ddf,r,mytypes.workouttypes_ordered[typeselect])
|
||||||
|
|
||||||
|
# interactive power pie chart
|
||||||
|
|
||||||
|
totalsdict = {}
|
||||||
|
totalsdict['duration'] = "{totalhours}:{totalminutes}".format(
|
||||||
|
totalhours=totalhours,
|
||||||
|
totalminutes=totalminutes
|
||||||
|
)
|
||||||
|
|
||||||
|
totalsdict['distance'] = totalmeters
|
||||||
|
try:
|
||||||
|
totalsdict['powermean'] = df['power'].mean().astype(int)
|
||||||
|
totalsdict['powermax'] = df['power'].max().astype(int)
|
||||||
|
except KeyError:
|
||||||
|
totalsdict['powermean'] = 0
|
||||||
|
totalsdict['powermax'] = 0
|
||||||
|
try:
|
||||||
|
totalsdict['hrmean'] = df['hr'].mean().astype(int)
|
||||||
|
totalsdict['hrmax'] = df['hr'].max().astype(int)
|
||||||
|
except KeyError:
|
||||||
|
totalsdict['hrmean'] = 0
|
||||||
|
totalsdict['hrmax'] = 0
|
||||||
|
|
||||||
|
totalsdict['nrworkouts'] = g_workouts.count()
|
||||||
|
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
'url':'rowers/analysis',
|
||||||
|
'name':'Analysis',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':reverse('history_view'),
|
||||||
|
'name': 'History',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return render(request,'history.html',
|
||||||
|
{
|
||||||
|
'rower':r,
|
||||||
|
'breadcrumbs':breadcrumbs,
|
||||||
|
'active':'nav-analysis',
|
||||||
|
'totalsdict':totalsdict,
|
||||||
|
'typedicts':listofdicts,
|
||||||
|
'totalscript':totalscript,
|
||||||
|
'totaldiv':totaldiv,
|
||||||
|
'form':form,
|
||||||
|
})
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ from rowers.forms import (
|
|||||||
MetricsForm,DisqualificationForm,disqualificationreasons,
|
MetricsForm,DisqualificationForm,disqualificationreasons,
|
||||||
disqualifiers,SearchForm,BillingForm,PlanSelectForm,
|
disqualifiers,SearchForm,BillingForm,PlanSelectForm,
|
||||||
VideoAnalysisCreateForm,WorkoutSingleSelectForm,
|
VideoAnalysisCreateForm,WorkoutSingleSelectForm,
|
||||||
VideoAnalysisMetricsForm,SurveyForm,
|
VideoAnalysisMetricsForm,SurveyForm,HistorySelectForm,
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
@@ -259,6 +259,18 @@ from django_mailbox.models import Message,Mailbox,MessageAttachment
|
|||||||
|
|
||||||
from rules.contrib.views import permission_required, objectgetter
|
from rules.contrib.views import permission_required, objectgetter
|
||||||
|
|
||||||
|
def get_totals(workouts):
|
||||||
|
totalminutes = 0
|
||||||
|
totalmeters = 0
|
||||||
|
|
||||||
|
for w in workouts:
|
||||||
|
totalmeters += w.distance
|
||||||
|
totalminutes += w.duration.hour*60+w.duration.minute
|
||||||
|
|
||||||
|
totalhour, totalminutes = divmod(totalminutes,60)
|
||||||
|
|
||||||
|
return totalmeters,totalhour, totalminutes
|
||||||
|
|
||||||
# creating shareable views
|
# creating shareable views
|
||||||
def allow_shares(view_func):
|
def allow_shares(view_func):
|
||||||
def sharify(request, *args, **kwargs):
|
def sharify(request, *args, **kwargs):
|
||||||
@@ -506,7 +518,7 @@ def getrequestplanrower(request,rowerid=0,userid=0,notpermanent=False):
|
|||||||
|
|
||||||
def getrower(user):
|
def getrower(user):
|
||||||
try:
|
try:
|
||||||
if user.is_anonymous:
|
if user is None or user.is_anonymous:
|
||||||
return None
|
return None
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
if User.objects.get(id=user).is_anonymous:
|
if User.objects.get(id=user).is_anonymous:
|
||||||
|
|||||||
@@ -1918,7 +1918,7 @@ def workouts_view(request,message='',successmessage='',
|
|||||||
g_workouts = Workout.objects.filter(
|
g_workouts = Workout.objects.filter(
|
||||||
team=theteam,user=r,
|
team=theteam,user=r,
|
||||||
startdatetime__gte=activity_startdate,
|
startdatetime__gte=activity_startdate,
|
||||||
enddatetime__lte=activity_enddate,
|
startdatetime__lte=activity_enddate,
|
||||||
duplicate=False,
|
duplicate=False,
|
||||||
privacy='visible').order_by("-startdatetime")
|
privacy='visible').order_by("-startdatetime")
|
||||||
|
|
||||||
@@ -2009,6 +2009,8 @@ def workouts_view(request,message='',successmessage='',
|
|||||||
g_enddate,
|
g_enddate,
|
||||||
stack=stack)
|
stack=stack)
|
||||||
|
|
||||||
|
totalmeters,totalhours, totalminutes = get_totals(g_workouts)
|
||||||
|
|
||||||
|
|
||||||
messages.info(request,successmessage)
|
messages.info(request,successmessage)
|
||||||
messages.error(request,message)
|
messages.error(request,message)
|
||||||
@@ -2036,6 +2038,9 @@ def workouts_view(request,message='',successmessage='',
|
|||||||
'interactiveplot':script,
|
'interactiveplot':script,
|
||||||
'the_div':div,
|
'the_div':div,
|
||||||
'timeperiod':timeperiod,
|
'timeperiod':timeperiod,
|
||||||
|
'totalmeters':totalmeters,
|
||||||
|
'totalminutes':totalminutes,
|
||||||
|
'totalhours':totalhours,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -5085,7 +5090,8 @@ def team_workout_upload_view(request,message="",
|
|||||||
rowers = rowers.exclude(rowerplan='basic')
|
rowers = rowers.exclude(rowerplan='basic')
|
||||||
|
|
||||||
rowerform.fields['user'].queryset = User.objects.filter(rower__in=rowers).distinct()
|
rowerform.fields['user'].queryset = User.objects.filter(rower__in=rowers).distinct()
|
||||||
if form.is_valid():
|
rowerform.fields['user'].required = True
|
||||||
|
if form.is_valid() and rowerform.is_valid():
|
||||||
f = request.FILES.get('file',False)
|
f = request.FILES.get('file',False)
|
||||||
if f:
|
if f:
|
||||||
res = handle_uploaded_file(f)
|
res = handle_uploaded_file(f)
|
||||||
|
|||||||
Reference in New Issue
Block a user