diff --git a/rowers/alerts.py b/rowers/alerts.py
index fdf00366..6e9fcd12 100644
--- a/rowers/alerts.py
+++ b/rowers/alerts.py
@@ -108,6 +108,7 @@ def alert_get_stats(alert,nperiod=0):
'nr_strokes':0,
'nr_strokes_qualifying':0,
'percentage':0,
+ 'nperiod':nperiod,
}
# check if filters are in columns list
@@ -139,6 +140,7 @@ def alert_get_stats(alert,nperiod=0):
'nr_strokes':0,
'nr_strokes_qualifying':0,
'percentage':0,
+ 'nperiod':nperiod,
}
@@ -167,6 +169,10 @@ def alert_get_stats(alert,nperiod=0):
percentage = int(100.*nr_strokes_qualifying/nr_strokes)
else:
percentage = 0
+
+ median_q = df2[alert.measured.metric].median()
+ median = df[alert.measured.metric].median()
+ std = df[alert.measured.metric].std()
return {
'workouts':len(workouts),
@@ -175,6 +181,10 @@ def alert_get_stats(alert,nperiod=0):
'nr_strokes':nr_strokes,
'nr_strokes_qualifying':nr_strokes_qualifying,
'percentage': percentage,
+ 'nperiod':nperiod,
+ 'median':median,
+ 'median_q':median_q,
+ 'standard_dev':std,
}
# run alert report
diff --git a/rowers/middleware.py b/rowers/middleware.py
index c1b968f2..7b9abb86 100644
--- a/rowers/middleware.py
+++ b/rowers/middleware.py
@@ -136,6 +136,8 @@ class RowerPlanMiddleWare(object):
paymentprocessor='braintree')
r.paidplan = basicplans[0]
r.save()
+ # remove from Free Coach groups
+
# send email
job = myqueue(queue,
handle_sendemail_expired,
diff --git a/rowers/models.py b/rowers/models.py
index ec2f772e..3028d064 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -878,7 +878,20 @@ class Rower(models.Model):
def clean_email(self):
return self.user.email.lower()
-
+ def save(self, *args, **kwargs):
+ try:
+ for group in self.coachinggroups.all():
+ try:
+ coach = Rower.objects.get(mycoachgroup=group)
+ if coach.rowerplan == 'freecoach':
+ self.coachinggroups.remove(group)
+ except Rower.DoesNotExist:
+ pass
+ except ValueError:
+ pass
+
+ super(Rower, self).save(*args, **kwargs)
+
class DeactivateUserForm(forms.ModelForm):
class Meta:
model = User
@@ -1083,6 +1096,11 @@ class Alert(models.Model):
return stri
+ def metricname(self):
+ metricdict = {key:value for (key,value) in parchoicesy1}
+
+ return metricdict[self.measured.metric]
+
def description(self):
metricdict = {key:value for (key,value) in parchoicesy1}
@@ -1105,6 +1123,24 @@ class Alert(models.Model):
return description
+ def shortdescription(self):
+ metricdict = {key:value for (key,value) in parchoicesy1}
+
+ if self.measured.condition == 'between':
+ description = '{value1} < {metric} < {value2}'.format(
+ metric = self.measured.metric,
+ value1 = self.measured.value1,
+ value2 = self.measured.value2,
+ )
+ else:
+ description = '{metric} {condition} {value1}'.format(
+ metric = self.measured.metric,
+ value1 = self.measured.value1,
+ condition = self.measured.condition
+ )
+
+ return description
+
class AlertEditForm(ModelForm):
class Meta:
diff --git a/rowers/tasks.py b/rowers/tasks.py
index 3aab4fbe..cf0bae49 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -756,6 +756,26 @@ def handle_updatedps(useremail, workoutids, debug=False,**kwargs):
return 1
+import math
+def sigdig(value, digits = 3):
+ try:
+ order = int(math.floor(math.log10(math.fabs(value))))
+ except (ValueError,TypeError):
+ return value
+
+ # return integers as is
+ if value % 1 == 0:
+ return value
+
+ places = digits - order - 1
+ if places > 0:
+ fmtstr = "%%.%df" % (places)
+ else:
+ fmtstr = "%.0f"
+ return fmtstr % (round(value, places))
+
+
+
@app.task
def handle_send_email_alert(
useremail, userfirstname, userlastname, rowerfirstname, alertname, stats, **kwargs):
@@ -770,15 +790,26 @@ def handle_send_email_alert(
else:
othertexts = None
- subject = "Your rowing performance on rowsandall.com ({startdate} to {enddate})".format(
+ report = {}
+ report['Percentage'] = int(stats['percentage'])
+ report['Number of workouts'] = int(stats['workouts'])
+ report['Data set'] = "{a} strokes out of {b}".format(
+ a = stats['nr_strokes_qualifying'],
+ b = stats['nr_strokes']
+ )
+ report['Median'] = sigdig(stats['median'])
+ report['Median of qualifying strokes'] = sigdig(stats['median_q'])
+
+ subject = "Rowsandall.com: {alertname} ({startdate} to {enddate})".format(
startdate = stats['startdate'],
enddate = stats['enddate'],
+ alertname=alertname,
)
from_email = 'Rowsandall
{{ alert }}
{{ alert.description }}
This is a page under construction. Currently with minimal information
{{ value }}
+{{ stats|lookup:'workouts' }} workouts
+{{ stats|lookup:'nr_strokes_qualifying' }} strokes out of {{ stats|lookup:'nr_strokes' }}
+Median {{ alert.metricname }}: {{ stats|lookup:'median'|sigdig }}
+Median {{ alert.metricname }}: {{ stats|lookup:'median_q'|sigdig }} ({{ alert.shortdescription }})
@@ -34,51 +48,16 @@
Workout type: {{ alert.workouttype }}
-- Next Run: {{ alert.next_run }} +
+ + {{ stats|alertstartdate:forloop.counter }} + - + + {{ stats|alertenddate:forloop.counter }} +
{% endfor %} -| Name | -metric | -Workout type | -Next Run | -|||
|---|---|---|---|---|---|---|
| {{ alert.name }} | -{{ alert.measured.metric }} | -{{ alert.workouttype }} | -{{ alert.next_run }} | -- - - - | -- - - - | -- - - - | -
You have not set any alerts for {{ rower.user.first_name }}
@@ -95,6 +74,61 @@ {% endblock %} +{% block scripts %} + + +{% endblock %} {% block sidebar %} {% include 'menu_analytics.html' %} diff --git a/rowers/templates/analysis.html b/rowers/templates/analysis.html index 42eb08c2..465143e8 100644 --- a/rowers/templates/analysis.html +++ b/rowers/templates/analysis.html @@ -86,7 +86,7 @@
diff --git a/rowers/templates/emailbase.html b/rowers/templates/emailbase.html
index cb160fb3..417b8d6f 100644
--- a/rowers/templates/emailbase.html
+++ b/rowers/templates/emailbase.html
@@ -1,14 +1,22 @@
+
+
+
+
+
-
- {% block body %}
-
- {% endblock %}
-
+
+
+ {% block body %}
+
+ {% endblock %}
+
diff --git a/rowers/templates/team.html b/rowers/templates/team.html
index 59c0fc34..81873dee 100644
--- a/rowers/templates/team.html
+++ b/rowers/templates/team.html
@@ -54,6 +54,14 @@
{% if team.manager == user %}
Use the form to add a new user. You can either select a user from the list of your existing club members who are not on this team yet, or you can type the user's email address, which also works for users who have not registered to the site yet.
+ {% if team.manager.rower.rowerplan == 'freecoach' %} ++ As a Free Coach user, your team members can only be users on a paid + plan. You can also upgrade to a + Paid Coach Plan + which allows you to coach users on a free Rower Plan. +
+ {% endif %} {% if inviteform.errors %}Please correct the error{{ inviteform.errors|pluralize }} below. diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py index 82a4afb6..d7f9dd4a 100644 --- a/rowers/templatetags/rowerfilters.py +++ b/rowers/templatetags/rowerfilters.py @@ -4,6 +4,7 @@ from time import strftime from django.utils import timezone import dateutil.parser import json +import math import datetime import re register = template.Library() @@ -37,6 +38,25 @@ from django.template.defaultfilters import stringfilter from six import string_types +@register.filter +def sigdig(value, digits = 3): + try: + order = int(math.floor(math.log10(math.fabs(value)))) + except (ValueError,TypeError): + return value + + # return integers as is + if value % 1 == 0: + return value + + places = digits - order - 1 + if places > 0: + fmtstr = "%%.%df" % (places) + else: + fmtstr = "%.0f" + return fmtstr % (round(value, places)) + + @register.filter(is_safe=True, needs_autoescape=True) @stringfilter def urlshorten(value, limit,autoescape=None): @@ -71,10 +91,27 @@ from rowers.teams import rower_get_managers @register.filter def alertstatspercentage(list,i): alertstats = list[i-1] - print(alertstats) return alertstats["percentage"] +@register.filter +def alertstartdate(list,i): + alertstats = list[i-1] + + return alertstats["startdate"] + +@register.filter +def alertnperiod(list,i): + alertstats = list[i-1] + + return alertstats["nperiod"] + +@register.filter +def alertenddate(list,i): + alertstats = list[i-1] + + return alertstats["enddate"] + @register.filter def is_coach(rower,rowers): for r in rowers: @@ -261,18 +298,26 @@ def jsdict(dict,key): s = dict.get(key) return mark_safe(json.dumps(s)) + + @register.filter def lookup(dict, key): - s = dict.get(key) - + try: + s = dict.get(key) + except KeyError: + return None + if isinstance(s,string_types) and len(s) > 22: s = s[:22] return s @register.filter def lookuplong(dict, key): - s = dict.get(key) - + try: + s = dict.get(key) + except KeyError: + return None + return s @register.filter diff --git a/rowers/tests/testdata/testdata.csv.gz b/rowers/tests/testdata/testdata.csv.gz index 66edd86d..969eae07 100644 Binary files a/rowers/tests/testdata/testdata.csv.gz and b/rowers/tests/testdata/testdata.csv.gz differ diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index c8d68209..6bb2801c 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -4328,7 +4328,8 @@ def alerts_view(request,userid=0): for alert in alerts: stats.append(alert_get_stats(alert)) - + + breadcrumbs = [ { 'url':'/rowers/analysis', diff --git a/rowers/views/teamviews.py b/rowers/views/teamviews.py index 8610beaa..36c6b85b 100644 --- a/rowers/views/teamviews.py +++ b/rowers/views/teamviews.py @@ -15,12 +15,17 @@ def team_view(request,id=0,userid=0): myteams, memberteams, otherteams = get_teams(request) teams.remove_expired_invites() + try: t = Team.objects.get(id=id) except Team.DoesNotExist: raise Http404("Team doesn't exist") + if r.rowerplan == 'basic' and t.manager.rower.rowerplan != 'coach': + raise PermissionDenied("You need to be on a Paid Plan to see or join this team") + + q = User.objects.filter(rower__isnull=False,rower__team__in=myteams).distinct().exclude(rower__team__name=t.name) mygroups = [request.user.rower.mycoachgroup] q2 = User.objects.filter(rower__isnull=False,rower__coachinggroups__in=mygroups).distinct().exclude(rower__team__name=t.name) @@ -168,19 +173,22 @@ def get_teams(request): private='open').exclude( rower=r).exclude(manager=request.user).order_by('name') + if r.rowerplan == 'basic': + otherteams = otherteams.filter(manager__rower__rowerplan='coach') + return myteams, memberteams, otherteams @login_required() -def rower_teams_view(request,message='',successmessage=''): +def rower_teams_view(request): if request.method == 'POST': form = TeamInviteCodeForm(request.POST) if form.is_valid(): code = form.cleaned_data['code'] res,text = teams.process_invite_code(request.user,code) if res: - successmessage = text + messages.info(request,text) else: - message = text + messages.error(request,text) else: form = TeamInviteCodeForm() @@ -249,8 +257,6 @@ def rower_teams_view(request,message='',successmessage=''): # clubsize = teams.count_invites(request.user)+teams.count_club_members(request.user) # max_clubsize = r.clubsize - messages.info(request,successmessage) - messages.error(request,message) breadcrumbs = [ { @@ -312,18 +318,14 @@ def manager_member_drop_view(request,teamid,userid, return HttpResponseRedirect(url) @login_required() -def manager_requests_view(request,code=None,message='',successmessage=''): +def manager_requests_view(request,code=None): if code: res,text = teams.process_request_code(request.user,code) if res: - successmessage = text - message = '' + messages.info(request,text) else: - message = text - successmessage = '' + messages.error(request,text) - messages.info(request,successmessage) - messages.error(request,message) url = reverse(rower_teams_view,kwargs={ }) return HttpResponseRedirect(url)