Private
Public Access
1
0

Merge branch 'release/v10.19'

This commit is contained in:
Sander Roosendaal
2019-08-29 15:48:49 +02:00
14 changed files with 275 additions and 83 deletions
+10
View File
@@ -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,
}
@@ -168,6 +170,10 @@ def alert_get_stats(alert,nperiod=0):
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),
'startdate':startdate,
@@ -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
+2
View File
@@ -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,
+36
View File
@@ -878,6 +878,19 @@ 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:
@@ -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:
+33 -2
View File
@@ -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 <info@rowsandall.com>'
d = {
'report':stats,
'report':report,
'first_name':userfirstname,
'last_name':userlastname,
'siteurl':siteurl,
+2 -2
View File
@@ -135,8 +135,8 @@ def add_member(id,rower):
t= Team.objects.get(id=id)
try:
rower.team.add(t)
except ValidationError:
return(0,"Couldn't add member")
except ValidationError as e:
return(0,"Couldn't add member: "+str(e.message))
# code to add all workouts
ws = Workout.objects.filter(user=rower)
+21 -6
View File
@@ -1,5 +1,6 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Metric Alert{% endblock %}
@@ -13,19 +14,33 @@
</p>
<ul class="main-content">
<li>
{{ stats|lookuplong:'startdate' }} - {{ stats|lookuplong:'enddate' }}
</li>
<li class="grid_4">
<h2>Alert</h2>
<h2>{{ alert.name }}</h2>
<p>{{ alert }}</p>
<p>{{ alert.description }}</p>
<p>This is a page under construction. Currently with minimal information</p>
</li>
{% for key, value in stats.items %}
<li>
<h2>{{ key }}</h2>
<p>{{ value }}</p>
<li class="rounder">
<h2>Score</h2>
<hr>
<h2>{{ stats|lookup:'percentage' }}%</h2>
</li>
<li class="rounder">
<h2>Data set</h2>
<hr>
<p>{{ stats|lookup:'workouts' }} workouts</p>
<p>{{ stats|lookup:'nr_strokes_qualifying' }} strokes out of {{ stats|lookup:'nr_strokes' }}</p>
</li>
<li class="rounder">
<h2>Statistics</h2>
<hr>
<p>Median {{ alert.metricname }}: {{ stats|lookup:'median'|sigdig }}</p>
<p>Median {{ alert.metricname }}: {{ stats|lookup:'median_q'|sigdig }} ({{ alert.shortdescription }})</p>
</li>
{% endfor %}
</ul>
+80 -46
View File
@@ -13,7 +13,7 @@
<ul class="main-content">
{% if alerts %}
{% for alert in alerts %}
<li class="rounder">
<li class="rounder" id="alert_{{ alert.id }}">
<h2>{{ alert.name }}</h2>
<a class="small" href="/rowers/alerts/{{ alert.id }}/edit/" title="Edit">
<i class="fas fa-pencil-alt fa-fw"></i>
@@ -22,10 +22,24 @@
title="Delete">
<i class="fas fa-trash-alt fa-fw"></i>
</a>
<a class="small iteratorleft"
data-nperiod="{{ stats|alertnperiod:forloop.counter }}"
data-user="{{ rower.user.id }}"
data-alertid="{{ alert.id }}"
href="/rowers/alerts/{{ alert.id }}/report/">
<i class="fas fa-arrow-alt-left fa-fw"></i>
</a>
<a class="small iteratorright"
data-nperiod="{{ stats|alertnperiod:forloop.counter }}"
data-user="{{ rower.user.id }}"
data-alertid="{{ alert.id }}"
href="/rowers/alerts/{{ alert.id }}/report/">
<i class="fas fa-arrow-alt-right fa-fw"></i>
</a>
<hr>
<a href="/rowers/alerts/{{ alert.id }}/report/">
<div id="id_alert_{{ forloop.counter }}">
<h1>{{ stats|alertstatspercentage:forloop.counter }}%</h1>
<a id="percentages" href="/rowers/alerts/{{ alert.id }}/report/">
<div>
<h1><span id="percentage">{{ stats|alertstatspercentage:forloop.counter }}</span>%</h1>
</div>
</a>
<p>
@@ -34,51 +48,16 @@
<p>
Workout type: {{ alert.workouttype }}
</p>
<p>
Next Run: {{ alert.next_run }}
<p id="dates">
<span id="startdate">
{{ stats|alertstartdate:forloop.counter }}
</span> -
<span id="enddate">
{{ stats|alertenddate:forloop.counter }}
</span>
</p>
</li>
{% endfor %}
<li class="grid_4">
<table width="100%" class="listtable shortpadded">
<thead>
<tr>
<th>Name</th>
<th>metric</th>
<th>Workout type</th>
<th>Next Run</th>
</tr>
</thead>
<tbody>
{% for alert in alerts %}
<tr>
<td>{{ alert.name }}</td>
<td>{{ alert.measured.metric }}</td>
<td>{{ alert.workouttype }}</td>
<td>{{ alert.next_run }}</td>
<td>
<a class="small" href="/rowers/alerts/{{ alert.id }}/edit/" title="Edit">
<i class="fas fa-pencil-alt fa-fw"></i>
</a>
</td>
<td>
<a class="small"
href="/rowers/alerts/{{ alert.id }}/report/"
title="Report">
<i class="fal fa-table fa-fw"></i>
</a>
</td>
<td>
<a class="small" href="/rowers/alerts/{{ alert.id }}/delete/"
title="Delete">
<i class="fas fa-trash-alt fa-fw"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</li>
{% else %}
<li class="grid_4">
<p>You have not set any alerts for {{ rower.user.first_name }}</p>
@@ -95,6 +74,61 @@
{% endblock %}
{% block scripts %}
<script type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
</script>
<script>
$(document).ready(function(){
$(".iteratorleft").click(function( event ){
event.preventDefault();
var nperiod=$(this).data().nperiod+1;
var user = $(this).data().user;
var alertid = $(this).data().alertid;
var thediv = $(this);
url = "/rowers/alerts/"+alertid+"/report/"+nperiod+"/user/"+user+"/";
$.getJSON(window.location.protocol + '//' + window.location.host + url,
function(json) {
var percentage = json['stats']['percentage'];
var startdate = json['stats']['startdate'];
var enddate = json['stats']['enddate'];
thediv.siblings("#percentages").find("#percentage").text(percentage);
thediv.siblings("#dates").find("#startdate").text(startdate);
thediv.siblings("#dates").find("#enddate").text(enddate);
thediv.data().nperiod=nperiod;
if (nextperiod<0) {nextperiod=0};
var nextperiod = nperiod
thediv.siblings(".iteratorright").data().nperiod=nextperiod;
});
});
$(".iteratorright").click(function( event ){
event.preventDefault();
var nperiod=$(this).data().nperiod-1;
if ( nperiod<0 ) {
nperiod=0
};
var user = $(this).data().user;
var alertid = $(this).data().alertid;
var thediv = $(this)
url = "/rowers/alerts/"+alertid+"/report/"+nperiod+"/user/"+user+"/";
$.getJSON(window.location.protocol + '//' + window.location.host + url,
function(json) {
var percentage = json['stats']['percentage'];
var startdate = json['stats']['startdate'];
var enddate = json['stats']['enddate'];
thediv.siblings("#percentages").find("#percentage").text(percentage);
thediv.siblings("#dates").find("#startdate").text(startdate);
thediv.siblings("#dates").find("#enddate").text(enddate);
thediv.data().nperiod=nperiod;
var nextperiod = nperiod;
thediv.siblings(".iteratorleft").data().nperiod=nextperiod;
});
});
});
</script>
{% endblock %}
{% block sidebar %}
{% include 'menu_analytics.html' %}
+1 -1
View File
@@ -86,7 +86,7 @@
</p>
</li>
<li class="rounder">
<h1>Power Progress</h1>
<h2>Power Progress</h2>
<a href="/rowers/fitness-progress/">
<div class="vignet">
<img src="/static/img/powerprogress.png" alt="Power Progress">
+8
View File
@@ -1,4 +1,12 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Rowsandall</title>
<style>
</style>
</head>
<body>
<font face="verdana, sans-serif">
+8
View File
@@ -54,6 +54,14 @@
{% if team.manager == user %}
<p>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.</p>
{% if team.manager.rower.rowerplan == 'freecoach' %}
<p>
As a Free Coach user, your team members can only be users on a paid
plan. You can also upgrade to a
<a href="/rowers/paidplans/">Paid Coach Plan</a>
which allows you to coach users on a free Rower Plan.
</p>
{% endif %}
{% if inviteform.errors %}
<p style="color: red;">
Please correct the error{{ inviteform.errors|pluralize }} below.
+46 -1
View File
@@ -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,9 +298,14 @@ def jsdict(dict,key):
s = dict.get(key)
return mark_safe(json.dumps(s))
@register.filter
def lookup(dict, key):
try:
s = dict.get(key)
except KeyError:
return None
if isinstance(s,string_types) and len(s) > 22:
s = s[:22]
@@ -271,7 +313,10 @@ def lookup(dict, key):
@register.filter
def lookuplong(dict, key):
try:
s = dict.get(key)
except KeyError:
return None
return s
Binary file not shown.
+1
View File
@@ -4329,6 +4329,7 @@ def alerts_view(request,userid=0):
for alert in alerts:
stats.append(alert_get_stats(alert))
breadcrumbs = [
{
'url':'/rowers/analysis',
+14 -12
View File
@@ -16,11 +16,16 @@ def team_view(request,id=0,userid=0):
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)