Merge branch 'release/v10.19'
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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' %}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
BIN
Binary file not shown.
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user