Private
Public Access
1
0

Merge branch 'release/team_management'

This commit is contained in:
Sander Roosendaal
2017-02-09 20:59:18 +01:00
24 changed files with 1198 additions and 363 deletions

View File

@@ -4,7 +4,7 @@ from django.contrib.auth.models import User
from .models import (
Rower, Workout,GraphImage,FavoriteChart,SiteAnnouncement,
Team,
Team,TeamInvite
)
# Register your models here so you can use them in the Admin module
@@ -29,7 +29,10 @@ class SiteAnnouncementAdmin(admin.ModelAdmin):
list_display = ('announcement','created','modified','expires','dotweet')
class TeamAdmin(admin.ModelAdmin):
list_display = ('name',)
list_display = ('name','manager')
class TeamInviteAdmin(admin.ModelAdmin):
list_display = ('issuedate','team','user','code')
admin.site.unregister(User)
admin.site.register(User,UserAdmin)
@@ -38,3 +41,4 @@ admin.site.register(GraphImage)
admin.site.register(Team,TeamAdmin)
admin.site.register(FavoriteChart,FavoriteChartAdmin)
admin.site.register(SiteAnnouncement,SiteAnnouncementAdmin)
admin.site.register(TeamInvite,TeamInviteAdmin)

View File

@@ -1,6 +1,6 @@
# All the data preparation, data cleaning and data mangling should
# be defined here
from rowers.models import Workout, User, Rower
from rowers.models import Workout, User, Rower,StrokeData
from rowingdata import rowingdata as rrdata
from rowers.tasks import handle_sendemail_unrecognized
@@ -67,6 +67,98 @@ from scipy.signal import savgol_filter
import datetime
def clean_df_stats(datadf,workstrokesonly=True):
# clean data remove zeros and negative values
datadf=datadf.clip(lower=0)
datadf.replace(to_replace=0,value=np.nan,inplace=True)
# clean data for useful ranges per column
mask = datadf['hr'] < 30
datadf.loc[mask,'hr'] = np.nan
mask = datadf['rhythm'] < 5
datadf.loc[mask,'rhythm'] = np.nan
mask = datadf['rhythm'] > 70
datadf.loc[mask,'rhythm'] = np.nan
mask = datadf['power'] < 20
datadf.loc[mask,'power'] = np.nan
mask = datadf['drivelength'] < 0.5
datadf.loc[mask,'drivelength'] = np.nan
mask = datadf['forceratio'] < 0.2
datadf.loc[mask,'forceratio'] = np.nan
mask = datadf['forceratio'] > 1.0
datadf.loc[mask,'forceratio'] = np.nan
mask = datadf['spm'] < 10
datadf.loc[mask,'spm'] = np.nan
mask = datadf['spm'] > 60
datadf.loc[mask,'spm'] = np.nan
mask = datadf['drivespeed'] < 0.5
datadf.loc[mask,'drivespeed'] = np.nan
mask = datadf['drivespeed'] > 4
datadf.loc[mask,'drivespeed'] = np.nan
mask = datadf['driveenergy'] > 2000
datadf.loc[mask,'driveenergy'] = np.nan
mask = datadf['driveenergy'] < 100
datadf.loc[mask,'driveenergy'] = np.nan
workoutstateswork = [1,4,5,8,9,6,7]
workoutstatesrest = [3]
workoutstatetransition = [0,2,10,11,12,13]
if workstrokesonly=='True' or workstrokesonly==True:
try:
datadf = datadf[~datadf['workoutstate'].isin(workoutstatesrest)]
except:
pass
return datadf
def getstatsfields():
# Get field names and remove those that are not useful in stats
fields = StrokeData._meta.get_fields()
fielddict = {field.name:field.verbose_name for field in fields}
fielddict.pop('workoutid')
fielddict.pop('ergpace')
fielddict.pop('hr_an')
fielddict.pop('hr_tr')
fielddict.pop('hr_at')
fielddict.pop('hr_ut2')
fielddict.pop('hr_ut1')
fielddict.pop('time')
fielddict.pop('distance')
fielddict.pop('nowindpace')
fielddict.pop('fnowindpace')
fielddict.pop('fergpace')
fielddict.pop('equivergpower')
# fielddict.pop('workoutstate')
fielddict.pop('fpace')
fielddict.pop('pace')
fielddict.pop('id')
fielddict.pop('ftime')
fielddict.pop('x_right')
fielddict.pop('hr_max')
fielddict.pop('hr_bottom')
fielddict.pop('cumdist')
fieldlist = [field for field,value in fielddict.iteritems()]
return fieldlist,fielddict
# A string representation for time deltas
def niceformat(values):
out = []

View File

@@ -11,6 +11,7 @@ from datetimewidget.widgets import DateTimeWidget
from django.core.validators import validate_email
import os
import twitter
import re
from django.conf import settings
from sqlalchemy import create_engine
@@ -50,7 +51,10 @@ database_url = 'mysql://{user}:{password}@{host}:{port}/{database_name}'.format(
if settings.DEBUG or user=='':
database_url = 'sqlite:///db.sqlite3'
class UserFullnameChoiceField(forms.ModelChoiceField):
def label_from_instance(self,obj):
return obj.get_full_name()
# model for Power Zone names
class PowerZonesField(models.TextField):
# __metaclass__ = models.SubfieldBase
@@ -63,6 +67,19 @@ class PowerZonesField(models.TextField):
if not value: return
if isinstance(value, list):
return value
# remove double quotes and brackets
value = re.sub(r'u\"','',value)
value = re.sub(r'u\'','',value)
value = re.sub(r'\\','',value)
value = re.sub(r'\"','',value)
value = re.sub(r'\'','',value)
value = re.sub(r'\[','',value)
value = re.sub(r'\]','',value)
value = re.sub(r'\[\[','[',value)
value = re.sub(r'\]\]',']',value)
value = re.sub(r'\ \ ',' ',value)
value = re.sub(r', ',',',value)
return value.split(self.token)
def from_db_value(self,value, expression, connection, context):
@@ -83,13 +100,40 @@ class PowerZonesField(models.TextField):
# For future Team functionality
class Team(models.Model):
name = models.CharField(max_length=150)
notes = models.CharField(blank=True,max_length=200)
choices = (
('private','private'),
('open','open'),
)
name = models.CharField(max_length=150,unique=True,verbose_name='Team Name')
notes = models.CharField(blank=True,max_length=200,verbose_name='Team Purpose')
manager = models.ForeignKey(User)
private = models.CharField(max_length=30,choices=choices,default='open',
verbose_name='Team Type')
def __unicode__(self):
return self.name
class TeamForm(ModelForm):
class Meta:
model = Team
fields = ['name','notes','private']
widgets = {
'notes': forms.Textarea,
}
class TeamInvite(models.Model):
team = models.ForeignKey(Team)
user = models.ForeignKey(User,null=True)
issuedate = models.DateField(default=timezone.now)
code = models.CharField(max_length=150,unique=True)
email = models.CharField(max_length=150,null=True,blank=True)
class TeamInviteForm(ModelForm):
user = UserFullnameChoiceField(queryset=User.objects.all(),required=False)
class Meta:
model = TeamInvite
fields = ['user','email']
# Extension of User with rowing specific data
class Rower(models.Model):
weightcategories = (
@@ -149,6 +193,8 @@ class Rower(models.Model):
choices=plans)
planexpires = models.DateField(default=timezone.now)
teamplanexpires = models.DateField(default=timezone.now)
clubsize = models.IntegerField(default=0)
# Friends/Team
friends = models.ManyToManyField("self",blank=True)
@@ -297,7 +343,7 @@ class Workout(models.Model):
)
user = models.ForeignKey(Rower)
team = models.ForeignKey(Team,blank=True,null=True)
team = models.ManyToManyField(Team,blank=True)
name = models.CharField(max_length=150)
date = models.DateField()
workouttype = models.CharField(choices=workouttypes,max_length=50)

View File

@@ -22,6 +22,7 @@ from utils import serialize_list,deserialize_list
from rowers.dataprepnodjango import update_strokedata
from django.core.mail import send_mail, BadHeaderError,EmailMessage
# testing task
@@ -54,6 +55,7 @@ def handle_sendemail_unrecognized(unrecognizedfile,useremail):
os.remove(unrecognizedfile)
return 1
# Send email with TCX attachment
@app.task
def handle_sendemailtcx(first_name,last_name,email,tcxfile):
@@ -116,8 +118,11 @@ def handle_otwsetpower(f1,boattype,weightvalue,
try:
rowdata = rdata(f1)
except IOError:
rowdata = rdata(f1+'.csv')
try:
rowdata = rdata(f1+'.csv')
except IOError:
rowdata = rdata(f1+'.gz')
weightvalue = float(weightvalue)
# do something with boat type
@@ -154,14 +159,12 @@ def handle_otwsetpower(f1,boattype,weightvalue,
subject = "Your Rowsandall OTW calculations are ready"
message = "Dear "+first_name+",\n\n"
message += "Your Rowsandall OTW calculations are ready.\n"
# message += "You can now create OTW plots with power information and wind corrections.\n\n"
message += "Thank you for using rowsandall.com.\n\n"
message += "Rowsandall OTW calculations have not been fully implemented yet.\n"
message += "We are now running an experimental version for debugging purposes. \n"
message += "Your wind/stream corrected plot is available here: http://rowsandall.com/rowers/workout/"
message += str(workoutid)
message +="/interactiveotwplot\n\n"
# message += "This functionality will be available soon, though.\n\n"
message += "Please report any bugs/inconsistencies/unexpected results at rowsandall.slack.com or by reply to this email.\n\n"
message += "Best Regards, The Rowsandall Physics Department."
@@ -232,6 +235,53 @@ def handle_makeplot(f1,f2,t,hrdata,plotnr,imagename):
gc.collect()
return imagename
# Team related remote tasks
@app.task
def handle_sendemail_invite(email,name,code,teamname,manager):
fullemail = name+' <'+email+'>'
subject = 'Invitation to join team '+teamname
message = 'Dear '+name+',\n\n'
message += manager+' is inviting you to join his team '+teamname
message += ' on rowsandall.com\n\n'
message += 'If you already have an account on rowsandall.com, you can login to the site and you will find the invitation here on the Teams page:\n'
message += ' https://rowsandall.com/rowers/me/teams \n\n'
message += 'You can also click the direct link: \n'
message += 'https://rowsandall.com/rowers/me/invitation/'+code+' \n\n'
message += 'If you are not yet registered to rowsandall.com, '
message += 'you can register for free at https://rowsandall.com/rowers/register\n'
message += 'After you set up your account, you can use the direct link: '
message += 'https://rowsandall.com/rowers/me/invitation/'+code+' \n\n'
message += 'You can also manually accept your team membership with the code.\n'
message += 'You will need to do this if you registered under a differnt email address than this one.\n'
message += 'Code: '+code+'\n'
message += 'Link to manually accept your team membership: '
message += 'https://rowsandall.com/rowers/me/invitation\n\n'
message += "Best Regards, the Rowsandall Team"
email = EmailMessage(subject, message,
'Rowsandall <info@rowsandall.com>',
[fullemail])
res = email.send()
return 1
@app.task
def handle_remove_workouts_team(ws,t):
for w in ws:
w.team.remove(t)
return 1
@app.task
def handle_add_workouts_team(ws,t):
for w in ws:
w.team.add(t)
return 1
# Another simple task for debugging purposes
def add2(x,y):

208
rowers/teams.py Normal file
View File

@@ -0,0 +1,208 @@
# All the Team related methods
# Python
from django.utils import timezone
from datetime import datetime
from datetime import timedelta
import time
from django.db import IntegrityError
import uuid
from django.conf import settings
from rowers.models import (
Rower, Workout, Team, TeamInvite,User
)
from rowers.tasks import (
handle_remove_workouts_team,handle_sendemail_invite,
handle_add_workouts_team
)
# Low level functions - to be called by higher level methods
inviteduration = 14 # days
def create_team(name,manager,private='open',notes=''):
# needs some error testing
try:
t = Team(name=name,manager=manager,notes=notes,
private=private)
t.save()
r = Rower.objects.get(user=manager)
r.team.add(t)
except IntegrityError:
return (0,'Team name duplication')
return (t.id,'Team created')
def remove_team(id):
t = Team.objects.get(id=id)
return t.delete()
def set_teamplanexpires(rower):
ts = Team.objects.filter(rower=rower)
texp = datetime.date(timezone.now())
for t in ts:
mr = Rower.objects.get(user=t.manager)
if mr.teamplanexpires > texp:
rower.teamplanexpires = mr.teamplanexpires
t.save()
return (1,'Updated rower team expiry')
def add_member(id,rower):
t= Team.objects.get(id=id)
rower.team.add(t)
# code to add all workouts
ws = Workout.objects.filter(user=rower)
if settings.DEBUG:
res = handle_add_workouts_team(ws,t)
else:
res = queuehigh.enqueue(handle_add_workouts_team,ws,t)
set_teamplanexpires(rower)
return (id,'Member added')
def remove_member(id,rower):
t = Team.objects.get(id=id)
rower.team.remove(t)
# remove the team from rower's workouts:
ws = Workout.objects.filter(user=rower,team=t)
if settings.DEBUG:
res = handle_remove_workouts_team(ws,t)
else:
res = queuehigh.enqueue(handle_remove_workouts_team,ws,t)
set_teamplanexpires(rower)
return (id,'Member removed')
def mgr_remove_member(id,manager,rower):
t = Team.objects.get(id=id)
if t.manager == manager:
remove_member(id,rower)
return (id,'Member removed')
else:
return (0,'You are not the team manager')
return (0,'')
def count_invites(manager):
ts = Team.objects.filter(manager=manager)
count = 0
for t in ts:
count += TeamInvite.objects.filter(team=t).count()
return count
def count_members(id):
t = Team.objects.get(id=id)
return Rower.objects.filter(team=t).count()
def count_club_members(manager):
ts = Team.objects.filter(manager=manager)
return Rower.objects.filter(team__in=ts).distinct().count()
def get_club_members(manager):
ts = Team.objects.filter(manager=manager)
return Rower.objects.filter(team__in=ts).distinct()
def get_team_members(id):
t = Team.objects.get(id=id)
return Rower.objects.filter(team=t)
def get_team_workouts(id):
t = Team.objects.get(id=id)
return Workout.objects.filter(team=t).order_by("-date", "-starttime")
# Medium level functionality
def create_invite(team,manager,user=None,email=''):
r = Rower.objects.get(user=manager)
if team.manager != manager:
return (0,'Not the team manager')
if user:
try:
r2 = Rower.objects.get(user=user)
except Rower.DoesNotExist:
return (0,'Rower does not exist')
if r2 in Rower.objects.filter(team=team):
return (0,'Already member of that team')
elif email==None or email=='':
return (0,'Invalid request - missing email or user')
if count_club_members(team.manager)+count_invites(team.manager) < r.clubsize:
codes = [i.code for i in TeamInvite.objects.all()]
code = uuid.uuid4().hex[:10].upper()
# prevent duplicates
while code in codes:
code = uuid.uuid4().hex[:10].upper()
invite = TeamInvite(team=team,code=code,user=user,email=email)
invite.save()
return (invite.id,'Invitation created')
else:
return (0,'You are at your club size limit')
return (0,'Nothing done')
def revoke_invite(id):
invitation = TeamInvite.objects.get(id=id)
invitation.delete()
return (1,'Invitation revoked')
def send_invite_email(id):
invitation = TeamInvite.objects.get(id=id)
if invitation.user:
email = invitation.user.email
name = invitation.user.first_name + " " + invitation.user.last_name
else:
email = invitation.email
name = ''
code = invitation.code
teamname = invitation.team.name
manager = invitation.team.manager.first_name+' '+invitation.team.manager.last_name
if settings.DEBUG:
res = handle_sendemail_invite.delay(email,name,code,teamname,manager)
else:
queue.enqueue(handle_sendemail_invite,email,name,code,teamname,manager)
return (1,'Invitation email sent')
def process_invite_code(user,code):
code = code.upper()
try:
invitation = TeamInvite.objects.get(code=code)
except TeamInvite.DoesNotExist:
return (0,'The invitation has expired or the code is invalid')
r = Rower.objects.get(user=user)
nu = datetime.date(timezone.now())
if nu > invitation.issuedate+timedelta(days=inviteduration):
revoke_invite(invitation.id)
return (0,'The invitation has expired')
t = invitation.team
result = add_member(t.id,r)
invitation.delete()
return result
def remove_expired_invites():
issuedate = timezone.now()-timedelta(days=inviteduration)
issuedate = datetime.date(issuedate)
invitations = TeamInvite.objects.filter(issuedate__lt=issuedate)
for i in invitations:
revoke_invite(i.id)
return (1,'Expired invitations deleted')

View File

@@ -51,7 +51,7 @@
<div class="grid_6 alpha">
<div class="grid_2 alpha">
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/workout/compare/{{ workout.id }}">Compare Workouts</a>
{% else %}
<a class="button blue small" href="/rowers/promembership/">Compare Workouts</a>
@@ -71,7 +71,7 @@
<div class="grid_2 omega tooltip">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/editintervals">Edit Intervals</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">Edit Intervals</a>
@@ -88,7 +88,7 @@
<div class="grid_2 alpha">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/adddistanceplot2">Dist Metrics Plot</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">Dist Metrics Plot</a>
@@ -100,7 +100,7 @@
</div>
<div class="grid_2">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/addtimeplot2">Time Metrics Plot</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">Time Metrics Plot</a>
@@ -123,7 +123,7 @@
<div class="grid_2 suffix_4 alpha">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/histo">Power Histogram</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">Dist Metrics Plot</a>

View File

@@ -51,7 +51,7 @@
<div class="grid_6 alpha">
<div class="grid_2 alpha">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/workout/compare/{{ workout.id }}">Compare Workouts</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">Compare Workouts</a>
@@ -73,7 +73,7 @@
<div class="grid_2 omega tooltip">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/editintervals">Edit Intervals</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">Edit Intervals</a>
@@ -90,7 +90,7 @@
<div class="grid_6 alpha">
<div class="grid_2 alpha">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small"href="/rowers/workout/{{ workout.id }}/crewnerdsummary">CrewNerd Summary</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">CrewNerd Summary</a>
@@ -104,7 +104,7 @@
<div class="grid_2 tooltip">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/forcecurve">Stroke Profile (Empower)</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">Stroke Profile (Empower)</a>
@@ -117,7 +117,7 @@
<div class="grid_2 omega tooltip">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/addotwpowerplot">OTW Power Plot</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">OTW Power Plot</a>
@@ -137,7 +137,7 @@
<div class="grid_2 alpha">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/geeky">Geeky Stuff</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">Geeky Stuff</a>
@@ -152,7 +152,7 @@
<div class="grid_2 tooltip">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small"href="/rowers/workout/{{ workout.id }}/smoothenpace">Smooth out Pace Data</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">Smooth out Pace Data</a>
@@ -169,7 +169,7 @@
<div class="grid_2 omega">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small"href="/rowers/workout/{{ workout.id }}/undosmoothenpace">Raw Data</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">Reset Smoothing</a>

View File

@@ -41,7 +41,7 @@
<h2>Pro</h2>
<div class="grid_2 alpha">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/histo">Power Histogram</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">Power Histogram</a>
@@ -53,7 +53,7 @@
</div>
<div class="grid_2">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/cumstats">Statistics</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">Statistics</a>

View File

@@ -24,7 +24,7 @@
{% analytical_body_top %}
<div class="container_12">
<div id="logo" class="grid_2">
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<p><a href="/"><img src="/static/img/logocroppedpro.gif"
alt="Rowsandall logo" width="110" heigt="110"></a></p>
{% else %}
@@ -49,7 +49,7 @@
<p>Free Data and Analysis. For Rowers. By Rowers.</p>
</div>
<div class="grid_3">
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<h6>Pro Member</h6>
{% else %}
<p>&nbsp;</p>
@@ -120,7 +120,7 @@
<p>
<a class="button gray small" href="/rowers/me/edit">{{ user.first_name }}</a>
</p>
<span class="tooltiptext">Edit user data, e.g. heart rate zones</span>
<span class="tooltiptext">Edit user account, e.g. heart rate zones, power zones, email, teams</span>
{% else %}
<p><a class="button gray small" href="{% url 'login' %}">login</a> </p>

View File

@@ -19,7 +19,7 @@
<body>
<div class="container_12">
<div id="logo" class="grid_2">
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<p><a href="/"><img src="/static/img/logocroppedpro.gif"
alt="Rowsandall logo" width="110" heigt="110"></a></p>
{% else %}
@@ -44,7 +44,7 @@
<p>Free Data and Analysis. For Rowers. By Rowers.</p>
</div>
<div class="grid_3">
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<h6>Pro Member</h6>
{% else %}
<p>&nbsp;</p>

View File

@@ -98,7 +98,7 @@
<div class="grid_8 omega">
{% if cordict %}
<h2> Correlation Matrix</h2>
<p>This table indicates a positive (+) or negative (-) correlation between two parameters. The strong correlations are indicated with ++ and --.
<p>This matrix indicates a positive (+) or negative (-) correlation between two parameters. The Spearman correlation coefficient has values between +1 and -1. Positive correlation between two metrics means that if one metric increases, the other value is also likely to increase. Negative is the opposite. The further from zero, the higher the likelyhood.
</p>
<table width="90%" class="cortable">
<thead>
@@ -116,13 +116,13 @@
{% for key2,value in thedict.items %}
<td>
{% if value > 0.5 %}
<div class="poscor">++</div>
<div class="poscor">{{ value|floatformat:-1 }}</div>
{% elif value > 0.1 %}
<div class="weakposcor">+</div>
<div class="weakposcor">{{ value|floatformat:-1 }}</div>
{% elif value < -0.5 %}
<div class="negcor">--</div>
<div class="negcor">{{ value|floatformat:-1 }}</div>
{% elif value < -0.1 %}
<div class="weaknegcor">-</div>
<div class="weaknegcor">{{ value|floatformat:-1 }}</div>
{% else %}
&nbsp;
{% endif %}

View File

@@ -168,7 +168,7 @@
</div>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<div id="favorites" class="grid_12 alpha">
<div class="grid_2 suffix_4 alpha">
{% if maxfav >= 0 %}

View File

@@ -203,7 +203,7 @@
</div>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<div id="favorites" class="grid_12 alpha">
<div class="grid_2 suffix_4 alpha">
{% if maxfav >= 0 %}

View File

@@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block title %}Teams {% endblock %}
{% block content %}
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<p>
<h2>Invitations</h2>
Future invitations page
</p>
</div>
<div class="grid_6 omega">
<p>
<h2>Manual with Code</h2>
</p>
</div>
</div>
{% endblock %}

View File

@@ -57,7 +57,7 @@
<div class="grid_6 alpha">
<div class="grid_2 alpha">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/wind">Edit Wind Data</a>
{% else %}
<a class="button blue small" href="/rowers/about">Edit Wind Data</a>
@@ -70,7 +70,7 @@
</div>
<div class="grid_2">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/stream">Edit Stream Data</a>
{% else %}
<a class="button blue small" href="/rowers/about">Edit Stream Data</a>
@@ -83,7 +83,7 @@
</div>
<div class="grid_2 omega">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/otwsetpower">OTW Power</a>
{% else %}
<a class="button blue small" href="/rowers/about">OTW Power</a>
@@ -100,7 +100,7 @@
<div class="grid_2 alpha">
<p>
{% if user.rower.rowerplan == 'pro' %}
{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<a class="button blue small" href="/rowers/workout/{{ workout.id }}/interactiveotwplot">Corrected Pace Plot</a>
{% else %}
<a class="button blue small" href="/rowers/about">Corrected Pace Plot</a>
@@ -159,4 +159,4 @@
</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -3,107 +3,124 @@
{% block title %}Change Rower {% endblock %}
{% block content %}
<div class="grid_6 alpha">
<p>
<h2>Heart Rate Zones</h2>
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<form enctype="multipart/form-data" action="" method="post">
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<div class="grid_2 prefix_2 suffix_2">
<input class="button green" type="submit" value="Save">
</form>
</p>
</div>
<p>
<h2>Account Information</h2>
{% if userform.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
{% if accountform.errors %}
<p style="color: red;">
</p>
{% endif %}
<form enctype="multipart/form-data" action="" method="post">
<table>
{{ userform.as_table }}
{{ accountform.as_table }}
<tr>
<td>Plan</td><td>{{ rower.rowerplan }}</td>
</tr>
</table>
{% csrf_token %}
<div class="grid_2 prefix_2 suffix_2">
<input class="button green" type="submit" value="Save">
</form>
</div>
</p>
</div>
<div class="grid_6 omega">
<p>
<h2>Power Zones</h2>
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<p>
<h2>Heart Rate Zones</h2>
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<form enctype="multipart/form-data" action="" method="post">
{% if powerzonesform.errors %}
<p style="color: red;">
Please correct the error{{ powerzonesform.errors|pluralize }} below.
{{ powerzonesform.non_field_errors }}
</p>
{% endif %}
<table>
<thead>
<tr>
<th>ID</th><th>Zone Name</th><th>Lower Boundary (Watt)</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td><td>{{ powerzonesform.ut3name }}</td>
<td></td>
</tr>
<tr>
<td>2</td><td>{{ powerzonesform.ut2name }}</td>
<td>{{ powerzonesform.pw_ut2 }}</td>
</tr>
<tr>
<td>3</td><td>{{ powerzonesform.ut1name }}</td>
<td>{{ powerzonesform.pw_ut1 }}</td>
</tr>
<tr>
<td>4</td><td>{{ powerzonesform.atname }}</td>
<td>{{ powerzonesform.pw_at }}</td>
</tr>
<tr>
<td>5</td><td>{{ powerzonesform.trname }}</td>
<td>{{ powerzonesform.pw_tr }}</td>
</tr>
<tr>
<td>6</td><td>{{ powerzonesform.anname }}</td>
<td>{{ powerzonesform.pw_an }}</td>
</tr>
</tbody>
{{ form.as_table }}
</table>
{% csrf_token %}
<div class="grid_2 prefix_2 suffix_2">
<input class="button green" type="submit" value="Save">
</div>
</form>
</p>
<p>
<h2>Functional Threshold Power</h2>
<p>Use this form to quickly change your zones based on the power of a
recent
full out 60 minutes effort. It will update all zones defined above.</p>
</p>
</div>
</div>
<div class="grid_6 omega">
<p>
<h2>Power Zones</h2>
<form enctype="multipart/form-data" action="" method="post">
{% if powerzonesform.errors %}
<p style="color: red;">
Please correct the error{{ powerzonesform.errors|pluralize }} below.
{{ powerzonesform.non_field_errors }}
</p>
{% endif %}
<table>
<thead>
<tr>
<th>ID</th><th>Zone Name</th><th>Lower Boundary (Watt)</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td><td>{{ powerzonesform.ut3name }}</td>
<td></td>
</tr>
<tr>
<td>2</td><td>{{ powerzonesform.ut2name }}</td>
<td>{{ powerzonesform.pw_ut2 }}</td>
</tr>
<tr>
<td>3</td><td>{{ powerzonesform.ut1name }}</td>
<td>{{ powerzonesform.pw_ut1 }}</td>
</tr>
<tr>
<td>4</td><td>{{ powerzonesform.atname }}</td>
<td>{{ powerzonesform.pw_at }}</td>
</tr>
<tr>
<td>5</td><td>{{ powerzonesform.trname }}</td>
<td>{{ powerzonesform.pw_tr }}</td>
</tr>
<tr>
<td>6</td><td>{{ powerzonesform.anname }}</td>
<td>{{ powerzonesform.pw_an }}</td>
</tr>
</tbody>
</table>
{% csrf_token %}
<div class="grid_2 prefix_2 suffix_2">
<input class="button green" type="submit" value="Save">
</div>
</form>
</p>
</div>
</div>
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<p>
<h2>Account Information</h2>
{% if userform.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
{% if accountform.errors %}
<p style="color: red;">
</p>
{% endif %}
<form enctype="multipart/form-data" action="" method="post">
<table>
{{ userform.as_table }}
{{ accountform.as_table }}
<tr>
<th>Plan</th><td>{{ rower.rowerplan }}</td>
</tr>
<tr>
<th>Plan Expiry</th><td>{{ rower.planexpires }}</td>
</tr>
</table>
{% csrf_token %}
<div class="grid_2 alpha">
{% if rower.rowerplan == 'basic' %}
<a class="button blue" href="/rowers/promembership">Upgrade</a>
{% else %}
&nbsp;
{% endif %}
</div>
<div class="grid_2 suffix_2 omega">
<input class="button green" type="submit" value="Save">
</form>
</div>
</p>
</div>
<div class="grid_6 omega">
<p>
<h2>Functional Threshold Power</h2>
<p>Use this form to quickly change your zones based on the power of a
recent
full out 60 minutes effort. It will update all zones defined above.</p>
<form enctype="multipart/form-data" action="" method="post">
<table>
{{ powerform.as_table }}
@@ -113,43 +130,61 @@
<input class="button green" type="submit" value="Save">
</form>
</div>
</p>
{% if grants %}
<p>
<h2>Applications</h2>
<table width="100%">
<thead>
<tr>
<th>Application</th>
<th>Scope</th>
<th>Revoke</th>
</tr>
</thead>
<tbody>
{% for grant in grants %}
<tr>
<td>{{ grant.application }}</td>
<td>{{ grant.scope }}</td>
<td>
<a class="button red small" href="/rowers/me/revokeapp/{{ grant.application.id }}">Revoke</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</p>
{% endif %}
</div>
</div>
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<p>
<h2>Teams</h2>
<div class="grid_2 suffix_4 alpha">
<a class="button gray small" href="/rowers/me/teams">Manage Teams</a>
</div>
</p>
</div>
<div class="grid_6 omega">
<p>
<h2>Favorite Charts</h2>
<div class="grid_2 suffix_4 alpha">
<a class="button gray small" href="/rowers/me/favoritecharts">Manage Favorite Charts</a>
</div>
</p>
</div>
</div>
<div class="grid_6 prefix_6 alpha">
<p>
<h2>Favorite Charts</h2>
<div class="grid_2 suffix_4 alpha">
<a class="button gray small" href="/rowers/me/favoritecharts">Manage Favorite Charts</a>
</div>
</p>
<div class="grid_12 alpha">
<div class="grid_6 suffix_6 alpha">
{% if grants %}
<p>
<h2>Applications</h2>
<table width="100%">
<thead>
<tr>
<th>Application</th>
<th>Scope</th>
<th>Revoke</th>
</tr>
</thead>
<tbody>
{% for grant in grants %}
<tr>
<td>{{ grant.application }}</td>
<td>{{ grant.scope }}</td>
<td>
<a class="button red small" href="/rowers/me/revokeapp/{{ grant.application.id }}">Revoke</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</p>
{% else %}
<p>&nbsp;</p>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,70 @@
{% extends "base.html" %}
{% block title %}Team {% endblock %}
{% block content %}
<div class="grid_12 alpha">
<h1>{{ team.name }}</h1>
<p>{{ team.notes }}</p>
<p><b>Manager:</b> {{ team.manager.first_name }} {{ team.manager.last_name }}</p>
{% if ismember %}
<div class="grid_2 suffix_10 alpha">
<a class="button red small" href="/rowers/team/{{ team.id }}/leaveconfirm">Leave this team</a>
</div>
{% else %}
<div class="grid_2 suffix_10 alpha tooltip">
<a class="button green small" href="/rowers/team/{{ team.id }}/requestmembership/{{ user.id }}">Join</a>
<span class="tooltiptext">A request will be sent to the team manager</span>
</div>
{% endif %}
<div class="grid_6 alpha">
<p>
<h2>Members</h2>
<table width="100%" class="listtable">
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
{% for member in members %}
<tr>
<td> {{ member.user.first_name }} {{ member.user.last_name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</p>
</div>
<div class="grid_6 omega">
{% if team.manager == user %}
<p>Use the form to add a new user. You can either select a user from the slist, or you can type his email address, which also works for users who have not registered to the site yet.</p>
{% if inviteform.errors %}
<p style="color: red;">
Please correct the error{{ inviteform.errors|pluralize }} below.
</p>
{% endif %}
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
<table>
{{ inviteform.as_table }}
</table>
{% csrf_token %}
<div id="formbutton" class="grid_1 prefix_4 suffix_1">
<input class="button green" type="submit" value="Submit">
</div>
</form>
{% else %}
<p>
&nbsp;
</p>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,28 @@
{% extends "base.html" %}
{% load staticfiles %}
{% block title %}New Team{% endblock %}
{% block content %}
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
<div id="left" class="grid_6 alpha">
<h1>Create a new Team</h1>
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<div id="formbutton" class="grid_1 prefix_4 suffix_1">
<input class="button green" type="submit" value="Submit">
</div>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,43 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Leave Team {% endblock %}
{% block content %}
<div id="workouts" class="grid_6 alpha">
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<h1>Confirm Deleting the team {{ team.name }}</h1>
<p>This will remove the team. Your team members and their workouts
will not be deleted.
</p>
<div class="grid_2 alpha">
<p>
<a class="button green small" href="/rowers/team/{{ team.id }}">Cancel</a>
</div>
<div class="grid_2">
<p>
<a class="button red small" href="/rowers/team/{{ team.id }}/delete">Delete</a>
</p>
</div>
</div>
<div id="images" class="grid_6 omega">
<p>
&nbsp;
</p>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Leave Team {% endblock %}
{% block content %}
<div id="workouts" class="grid_6 alpha">
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<h1>Confirm Leaving the team {{ team.name }}</h1>
<p>This will remove you and all your workouts from the team. If this
is a closed team, you can only return when the team manager
reinvites you.
If this is an open team, you can return by applying for team membership.
</p>
<div class="grid_2 alpha">
<p>
<a class="button green small" href="/rowers/team/{{ team.id }}">Cancel</a>
</div>
<div class="grid_2">
<p>
<a class="button red small" href="/rowers/team/{{ team.id }}/leave">Leave</a>
</p>
</div>
</div>
<div id="images" class="grid_6 omega">
<p>
&nbsp;
</p>
</div>
{% endblock %}

133
rowers/templates/teams.html Normal file
View File

@@ -0,0 +1,133 @@
{% extends "base.html" %}
{% block title %}Teams {% endblock %}
{% block content %}
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<p>
<h2>My Teams</h2>
{% if teams %}
<table width="70%" class="listtable">
<thead>
<tr>
<th>Name</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for team in teams %}
<tr>
<td>
<a href="/rowers/team/{{ team.id }}/">{{ team.name }}</a>
</td>
<td>
<div class="grid_1">
<a class="button small red" href="/rowers/team/{{ team.id }}/leaveconfirm">Leave</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>You are not a member of any team.</p>
{% endif %}
</p>
</div>
<div class="grid_6 omega">
{% if invites %}
<p>
<h2>Invitations</h2>
<table width="70%" class="listtable">
<thead>
<tr>
<th>Name</th>
<th>Manager</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for i in invites %}
<tr>
<td><a href="/rowers/team/{{ i.team.id }}">{{ i.team.name }}</a></td>
<td>{{ i.team.manager.first_name }} {{ i.team.manager.last_name }}</td>
<td><a class="button small green" href="/rowers/me/invitation/{{ i.code }}">Accept</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</p>
{% else %}
<p>&nbsp;</p>
{% endif %}
</div>
</div>
{% if user.rower.rowerplan == 'coach' %}
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<h2>Teams I manage</h2>
{% if myteams %}
<table width="70%" class="listtable">
<thead>
<tr>
<th>Name</th>
<th>Manager</th>
</tr>
</thead>
<tbody>
{% for team in myteams %}
<tr>
<td>
<a href="/rowers/team/{{ team.id }}/">{{ team.name }}</a>
</td>
<td>
<div class="grid_1">
<a class="button small red" href="/rowers/team/{{ team.id }}/deleteconfirm">Delete</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<div class="grid_2 suffix_4 alpha">
<a class="button green" href="/rowers/team/create">New Team</a>
</div>
</div>
<div class="grid_6 omega">
{% if otherteams %}
<h2>Other Teams</h2>
<table width="70%" class="listtable">
<thead>
<tr>
<th>Name</th>
<th>Manager</th>
</tr>
</thead>
<tbody>
{% for team in otherteams %}
<tr>
<td>
<a href="/rowers/team/{{ team.id }}/">{{ team.name }}</a>
</td>
<td>
{{ team.manager.first_name }} {{ team.manager.last_name }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>&nbsp;</p>
{% endif %}
</div>
</div>
{% endif %}
{% endblock %}

View File

@@ -75,8 +75,8 @@
</div>
<div class="grid_8 omega">
{% if cordict %}
<h2> Correlation table</h2>
<p>This table indicates a positive (+) or negative (-) correlation between two parameters. The strong correlations are indicated with ++ and --.
<h2> Correlation matrix</h2>
<p>This matrix indicates a positive (+) or negative (-) correlation between two parameters. The Spearman correlation coefficient has values between +1 and -1. Positive correlation between two metrics means that if one metric increases, the other value is also likely to increase. Negative is the opposite. The further from zero, the higher the likelyhood.
</p>
<table width="90%" class="cortable">
<thead>
@@ -94,13 +94,13 @@
{% for key2,value in thedict.items %}
<td>
{% if value > 0.5 %}
<div class="poscor">++</div>
<div class="poscor">{{ value|floatformat:-1 }}</div>
{% elif value > 0.1 %}
<div class="weakposcor">+</div>
<div class="weakposcor">{{ value|floatformat:-1 }}</div>
{% elif value < -0.5 %}
<div class="negcor">--</div>
<div class="negcor">{{ value|floatformat:-1 }}</div>
{% elif value < -0.1 %}
<div class="weaknegcor">-</div>
<div class="weaknegcor">{{ value|floatformat:-1 }}</div>
{% else %}
&nbsp;
{% endif %}

View File

@@ -199,6 +199,20 @@ urlpatterns = [
url(r'^workout/(\d+)/stravauploadw/$',views.workout_strava_upload_view),
url(r'^workout/(\d+)/recalcsummary/$',views.workout_recalcsummary_view),
url(r'^workout/(\d+)/sporttracksuploadw/$',views.workout_sporttracks_upload_view),
url(r'^me/teams/$',views.rower_teams_view),
url(r'^team/(?P<id>\d+)/s/(?P<successmessage>\w+.*)/c/(?P<message>\w+.*)$',views.team_view),
url(r'^team/(?P<id>\d+)/c/(?P<message>\w+.*)$',views.team_view),
url(r'^team/(?P<id>\d+)/s/(?P<successmessage>\w+.*)$',views.team_view),
url(r'^team/(\d+)/$',views.team_view),
url(r'^team/(\d+)/leaveconfirm/$',views.team_leaveconfirm_view),
url(r'^team/(\d+)/leave/$',views.team_leave_view),
url(r'^team/(\d+)/deleteconfirm/$',views.team_deleteconfirm_view),
url(r'^team/(\d+)/requestmembership/(\d+)$',views.team_requestmembership_view),
url(r'^team/(\d+)/delete/$',views.team_delete_view),
url(r'^team/create/$',views.team_create_view),
url(r'^me/invitation/$',views.rower_invitations_view),
url(r'^me/invitation/c/(?P<message>\w+.*)/$',views.rower_invitations_view),
url(r'^me/invitation/(\w+.*)/$',views.rower_invitations_view),
url(r'^me/edit/c/(?P<message>\w+.*)$',views.rower_edit_view),
url(r'^me/edit/s/(?P<successmessage>\w+.*)$',views.rower_edit_view),
url(r'^me/edit/$',views.rower_edit_view),

View File

@@ -31,6 +31,7 @@ from rowers.models import Workout, User, Rower, WorkoutForm,FavoriteChart
from rowers.models import (
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm,
RowerPowerZonesForm,AccountRowerForm,UserForm,StrokeData,
Team,TeamForm,TeamInviteForm,TeamInvite
)
from rowers.models import FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement
from django.forms.formsets import formset_factory
@@ -57,6 +58,7 @@ from rest_framework.parsers import JSONParser
from rowers.rows import handle_uploaded_file
from rowers.tasks import handle_makeplot,handle_otwsetpower,handle_sendemailtcx,handle_sendemailcsv
from rowers.tasks import handle_sendemail_unrecognized
from scipy.signal import savgol_filter
from django.shortcuts import render_to_response
from Cookie import SimpleCookie
@@ -198,8 +200,14 @@ def splitstdata(lijst):
from utils import geo_distance,serialize_list,deserialize_list
# Check if a user is a Coach member
def iscoachmember(user):
r = Rower.objects.get(user=user)
result = user.is_authenticated() and (r.rowerplan=='coach')
return result
# Check if a user is a Pro member
def promember(user):
def ispromember(user):
r = Rower.objects.get(user=user)
result = user.is_authenticated() and (r.rowerplan=='pro' or r.rowerplan=='coach')
return result
@@ -1301,7 +1309,7 @@ def histo_all(request,theuser=0):
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -1371,7 +1379,7 @@ def cum_flex(request,theuser=0,
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -1463,14 +1471,14 @@ def cum_flex(request,theuser=0,
})
# Show the EMpower Oarlock generated Stroke Profile
@user_passes_test(promember,login_url="/",redirect_field_name=None)
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_forcecurve_view(request,id=0,workstrokesonly=False):
row = Workout.objects.get(id=id)
promember=0
mayedit=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
if request.user == row.user.user:
@@ -1509,7 +1517,7 @@ def workout_histo_view(request,id=0):
mayedit=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
if request.user == row.user.user:
@@ -1561,7 +1569,7 @@ def histo(request,theuser=0,
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -1671,7 +1679,7 @@ def rankings_view(request,theuser=0,
promember=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -2040,7 +2048,7 @@ def workouts_view(request,message='',successmessage='',
return HttpResponse("User has no rower instance")
# List of workouts to compare a selected workout to
@user_passes_test(promember,login_url="/",redirect_field_name=None)
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_comparison_list(request,id=0,message='',successmessage='',
startdatestring="",enddatestring="",
startdate=timezone.now()-datetime.timedelta(days=365),
@@ -2155,7 +2163,7 @@ def workout_view(request,id=0):
except Workout.DoesNotExist:
return HttpResponseNotFound("Workout doesn't exist")
# Resets stroke data to raw data (pace)
# Resets stroke data to raw data (pace)
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_undo_smoothenpace_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
@@ -2182,7 +2190,7 @@ def workout_undo_smoothenpace_view(request,id=0,message="",successmessage=""):
return HttpResponseRedirect(url)
# Data smoothing of pace data
# Data smoothing of pace data
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_smoothenpace_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
@@ -2218,7 +2226,7 @@ def workout_smoothenpace_view(request,id=0,message="",successmessage=""):
return HttpResponseRedirect(url)
# Process CrewNerd Summary CSV and update summary
# Process CrewNerd Summary CSV and update summary
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_crewnerd_summary_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
@@ -2263,7 +2271,7 @@ def workout_crewnerd_summary_view(request,id=0,message="",successmessage=""):
{'form':form,
'id':row.id})
# Get weather for given location and date/time
# Get weather for given location and date/time
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_downloadwind_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
@@ -2320,7 +2328,7 @@ def workout_downloadwind_view(request,id=0,message="",successmessage=""):
return response
# Show form to update wind data
# Show form to update wind data
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_wind_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
@@ -2420,7 +2428,7 @@ def workout_wind_view(request,id=0,message="",successmessage=""):
'gmapdiv':gmdiv})
# Show form to update River stream data (for river dwellers)
# Show form to update River stream data (for river dwellers)
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_stream_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
@@ -2482,7 +2490,7 @@ def workout_stream_view(request,id=0,message="",successmessage=""):
'form':form,
'the_div':div})
# Form to set average crew weight and boat type, then run power calcs
# Form to set average crew weight and boat type, then run power calcs
@user_passes_test(ispromember, login_url="/",redirect_field_name=None)
def workout_otwsetpower_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id)
@@ -2654,7 +2662,7 @@ def cumstats(request,theuser=0,
theuser = request.user.id
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -2738,97 +2746,17 @@ def cumstats(request,theuser=0,
u = ''
ids = [int(workout.id) for workout in allergworkouts]
# Get field names and remove those that are not useful in stats
fields = StrokeData._meta.get_fields()
fielddict = {field.name:field.verbose_name for field in fields}
fielddict.pop('workoutid')
fielddict.pop('ergpace')
fielddict.pop('hr_an')
fielddict.pop('hr_tr')
fielddict.pop('hr_at')
fielddict.pop('hr_ut2')
fielddict.pop('hr_ut1')
fielddict.pop('time')
fielddict.pop('distance')
fielddict.pop('nowindpace')
fielddict.pop('fnowindpace')
fielddict.pop('fergpace')
fielddict.pop('equivergpower')
# fielddict.pop('workoutstate')
fielddict.pop('fpace')
fielddict.pop('pace')
fielddict.pop('id')
fielddict.pop('ftime')
fielddict.pop('x_right')
fielddict.pop('hr_max')
fielddict.pop('hr_bottom')
fielddict.pop('cumdist')
fieldlist,fielddict = dataprep.getstatsfields()
# prepare data frame
datadf = dataprep.read_cols_df_sql(ids,fieldlist)
# clean data remove zeros and negative values
datadf=datadf.clip(lower=0)
datadf.replace(to_replace=0,value=np.nan,inplace=True)
# clean data for useful ranges per column
mask = datadf['hr'] < 30
datadf.loc[mask,'hr'] = np.nan
mask = datadf['rhythm'] < 5
datadf.loc[mask,'rhythm'] = np.nan
mask = datadf['rhythm'] > 70
datadf.loc[mask,'rhythm'] = np.nan
mask = datadf['power'] < 20
datadf.loc[mask,'power'] = np.nan
mask = datadf['drivelength'] < 0.5
datadf.loc[mask,'drivelength'] = np.nan
mask = datadf['forceratio'] < 0.2
datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
mask = datadf['forceratio'] > 1.0
datadf.loc[mask,'forceratio'] = np.nan
mask = datadf['spm'] < 10
datadf.loc[mask,'spm'] = np.nan
mask = datadf['spm'] > 60
datadf.loc[mask,'spm'] = np.nan
mask = datadf['drivespeed'] < 0.5
datadf.loc[mask,'drivespeed'] = np.nan
mask = datadf['drivespeed'] > 4
datadf.loc[mask,'drivespeed'] = np.nan
mask = datadf['driveenergy'] > 2000
datadf.loc[mask,'driveenergy'] = np.nan
mask = datadf['driveenergy'] < 100
if datadf.empty:
return HttpResponse("No data found")
workoutstateswork = [1,4,5,8,9,6,7]
workoutstatesrest = [3]
workoutstatetransition = [0,2,10,11,12,13]
if workstrokesonly=='True' or workstrokesonly==True:
try:
datadf = datadf[~datadf['workoutstate'].isin(workoutstatesrest)]
except:
# Create stats
@@ -2908,51 +2836,8 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
message = "You are not allowed to see the stats of this workout"
url = reverse(workouts_view,args=[str(message)])
return HttpResponseRedirect(url)
datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
# clean data remove zeros and negative values
datadf=datadf.clip(lower=0)
datadf.replace(to_replace=0,value=np.nan,inplace=True)
# clean data for useful ranges per column
mask = datadf['hr'] < 30
datadf.loc[mask,'hr'] = np.nan
mask = datadf['rhythm'] < 5
datadf.loc[mask,'rhythm'] = np.nan
mask = datadf['rhythm'] > 70
datadf.loc[mask,'rhythm'] = np.nan
mask = datadf['power'] < 20
datadf.loc[mask,'power'] = np.nan
mask = datadf['drivelength'] < 0.5
datadf.loc[mask,'drivelength'] = np.nan
mask = datadf['forceratio'] < 0.2
datadf.loc[mask,'forceratio'] = np.nan
mask = datadf['forceratio'] > 1.0
datadf.loc[mask,'forceratio'] = np.nan
mask = datadf['spm'] < 10
datadf.loc[mask,'spm'] = np.nan
mask = datadf['spm'] > 60
datadf.loc[mask,'spm'] = np.nan
mask = datadf['drivespeed'] < 0.5
datadf.loc[mask,'drivespeed'] = np.nan
mask = datadf['drivespeed'] > 4
datadf.loc[mask,'drivespeed'] = np.nan
mask = datadf['driveenergy'] > 2000
datadf.loc[mask,'driveenergy'] = np.nan
mask = datadf['driveenergy'] < 100
if datadf.empty:
@@ -2961,44 +2846,12 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
workoutstateswork = [1,4,5,8,9,6,7]
workoutstatesrest = [3]
workoutstatetransition = [0,2,10,11,12,13]
if workstrokesonly=='True' or workstrokesonly==True:
try:
datadf = datadf[~datadf['workoutstate'].isin(workoutstatesrest)]
except:
pass
# Create stats
stats = {}
# Get field names and remove those that are not useful in stats
fields = StrokeData._meta.get_fields()
fielddict = {field.name:field.verbose_name for field in fields}
fielddict.pop('workoutid')
fielddict.pop('ergpace')
fielddict.pop('hr_an')
fielddict.pop('hr_tr')
fielddict.pop('hr_at')
fielddict.pop('hr_ut2')
fielddict.pop('hr_ut1')
fielddict.pop('time')
fielddict.pop('distance')
fielddict.pop('nowindpace')
fielddict.pop('fnowindpace')
fielddict.pop('fergpace')
fielddict.pop('equivergpower')
fielddict.pop('workoutstate')
fielddict.pop('fpace')
fielddict.pop('pace')
fielddict.pop('id')
fielddict.pop('ftime')
fielddict.pop('x_right')
fielddict.pop('hr_max')
fielddict.pop('hr_bottom')
fieldlist,fielddict = dataprep.getstatsfields()
for field,verbosename in fielddict.iteritems():
@@ -3086,7 +2939,7 @@ def workout_comparison_view(request,id1=0,id2=0,xparam='distance',yparam='spm'):
def workout_comparison_view(request,id1=0,id2=0,xparam='distance',yparam='spm'):
promember=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -3113,7 +2966,7 @@ def workout_comparison_view2(request,id1=0,id2=0,xparam='distance',
yparam='spm',plottype='line'):
promember=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -3161,7 +3014,7 @@ def workout_flexchart3_view(request,*args,**kwargs):
promember=0
mayedit=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -3316,7 +3169,7 @@ def workout_biginteractive_view(request,id=0,message="",successmessage=""):
promember=0
mayedit=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -3353,7 +3206,7 @@ def workout_otwpowerplot_view(request,id=0,message="",successmessage=""):
promember=0
mayedit=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
@@ -3555,7 +3408,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
})
return HttpResponseRedirect(url)
# Create the chart image with wind corrected pace (OTW)
# Create the chart image with wind corrected pace (OTW)
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_add_otw_powerplot_view(request,id):
w = Workout.objects.get(id=id)
@@ -3818,7 +3671,7 @@ def workout_add_distanceplot_view(request,id):
url = "/rowers/workout/"+str(w.id)+"/edit"
return HttpResponseRedirect(url)
# Create the advanced parameters distance overview chart
# Create the advanced parameters distance overview chart
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_add_distanceplot2_view(request,id):
w = Workout.objects.get(id=id)
@@ -3871,7 +3724,7 @@ def workout_add_distanceplot2_view(request,id):
return HttpResponseRedirect(url)
# Create the advanced parameters time based overview chart
# Create the advanced parameters time based overview chart
@user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_add_timeplot2_view(request,id):
w = Workout.objects.get(id=id)
@@ -4934,7 +4787,7 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
'savebutton':savebutton,
})
# Page where user can manage his favorite charts
# Page where user can manage his favorite charts
@user_passes_test(ispromember,login_url="/rowers/me/edit",redirect_field_name=None)
def rower_favoritecharts_view(request):
message = ''
@@ -5430,3 +5283,190 @@ def strokedatajson(request,id):
return HttpResponse(row.id,status=201)
#Method not supported
return HttpResponseNotAllowed("Method not supported")
# Teams related views
import teams
@login_required()
def team_view(request,id=0,message='',successmessage=''):
ismember = 0
r = Rower.objects.get(user=request.user)
try:
t = Team.objects.get(id=id)
except Team.DoesNotExist:
return HttpResponse("Team doesn't exist")
if request.method == 'POST' and request.user == t.manager:
inviteform = TeamInviteForm(request.POST)
if inviteform.is_valid():
cd = inviteform.cleaned_data
newmember = cd['user']
email = cd['email']
inviteid,text = teams.create_invite(t,t.manager,
user=newmember,
email=email)
if inviteid:
teams.send_invite_email(inviteid)
successmessage = text
else:
message = text
elif request.user == t.manager:
inviteform = TeamInviteForm()
inviteform.fields['user'].queryset = User.objects.filter(rower__isnull=False)
else:
inviteform = ''
members = Rower.objects.filter(team=t).order_by('user__last_name','user__first_name')
if r in members:
ismember = 1
return render(request, 'team.html',
{
'team':t,
'members':members,
'inviteform':inviteform,
'message':message,
'successmessage':successmessage,
'ismember':ismember,
})
@login_required()
def team_leaveconfirm_view(request,id=0):
try:
t = Team.objects.get(id=id)
except Team.DoesNotExist:
return HttpResponse("Team doesn't exist")
return render(request,'teamleaveconfirm.html',
{
'team':t
})
@login_required()
def team_leave_view(request,id=0):
r = Rower.objects.get(user=request.user)
teams.remove_member(id,r)
url = reverse(rower_teams_view)
response = HttpResponseRedirect(url)
return response
@login_required()
def rower_teams_view(request):
r = Rower.objects.get(user=request.user)
ts = Team.objects.filter(rower=r)
myteams = Team.objects.filter(manager=request.user)
otherteams = Team.objects.filter(private='open').exclude(rower=r).exclude(manager=request.user).order_by('name')
teams.remove_expired_invites()
invites = TeamInvite.objects.filter(user=request.user)
return render(request, 'teams.html',
{
'teams':ts,
'myteams':myteams,
'invites':invites,
'otherteams':otherteams,
})
@login_required()
def rower_invitations_view(request,code=None,message='',successmessage=''):
if code:
teams.remove_expired_invites()
res,text = teams.process_invite_code(request.user,code)
if res:
successmessage = text
teamid=res
url = reverse(team_view,kwargs={
'id':teamid,
'successmessage': successmessage,
})
else:
message = text
url = reverse(rower_invitations_view,kwargs={
'message':message
})
return HttpResponseRedirect(url)
r = Rower.objects.get(user=request.user)
ts = Team.objects.filter(rower=r)
myteams = Team.objects.filter(manager=request.user)
invites = TeamInvite.objects.filter(user=request.user)
otherteams = Team.objects.filter(private='open').drop(rower=r)
return render(request, 'teams.html',
{
'teams':ts,
'myteams':myteams,
'invites':invites,
'otherteams':otherteams,
})
@login_required()
def team_requestmembership_view(request,teamid,userid):
return HttpResponse("Not yet implemented")
@user_passes_test(iscoachmember,login_url="/",redirect_field_name=None)
def team_create_view(request):
if request.method == 'POST':
teamcreateform = TeamForm(request.POST)
if teamcreateform.is_valid():
cd = teamcreateform.cleaned_data
name = cd['name']
notes = cd['notes']
manager = request.user
private = cd['private']
res,message=teams.create_team(name,manager,private,notes)
url = reverse(rower_teams_view)
response = HttpResponseRedirect(url)
return response
else:
teamcreateform = TeamForm()
return render(request,'teamcreate.html',
{
'form':teamcreateform,
})
@user_passes_test(iscoachmember,login_url="/",redirect_field_name=None)
def team_deleteconfirm_view(request,id):
r = Rower.objects.get(user=request.user)
try:
t = Team.objects.get(id=id)
except Team.DoesNotExist:
return HttpResponse("This team doesn't exist")
if t.manager != request.user:
return HttpResponse("You are not allowed to delete this team")
return render(request,'teamdeleteconfirm.html',
{
'team':t
})
@user_passes_test(iscoachmember,login_url="/",redirect_field_name=None)
def team_delete_view(request,id):
r = Rower.objects.get(user=request.user)
try:
t = Team.objects.get(id=id)
except Team.DoesNotExist:
return HttpResponse("This team doesn't exist")
if t.manager != request.user:
return HttpResponse("You are not allowed to delete this team")
teams.remove_team(t.id)
url = reverse(rower_teams_view)
response = HttpResponseRedirect(url)