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 ( from .models import (
Rower, Workout,GraphImage,FavoriteChart,SiteAnnouncement, Rower, Workout,GraphImage,FavoriteChart,SiteAnnouncement,
Team, Team,TeamInvite
) )
# Register your models here so you can use them in the Admin module # 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') list_display = ('announcement','created','modified','expires','dotweet')
class TeamAdmin(admin.ModelAdmin): 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.unregister(User)
admin.site.register(User,UserAdmin) admin.site.register(User,UserAdmin)
@@ -38,3 +41,4 @@ admin.site.register(GraphImage)
admin.site.register(Team,TeamAdmin) admin.site.register(Team,TeamAdmin)
admin.site.register(FavoriteChart,FavoriteChartAdmin) admin.site.register(FavoriteChart,FavoriteChartAdmin)
admin.site.register(SiteAnnouncement,SiteAnnouncementAdmin) 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 # All the data preparation, data cleaning and data mangling should
# be defined here # 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 rowingdata import rowingdata as rrdata
from rowers.tasks import handle_sendemail_unrecognized from rowers.tasks import handle_sendemail_unrecognized
@@ -67,6 +67,98 @@ from scipy.signal import savgol_filter
import datetime 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 # A string representation for time deltas
def niceformat(values): def niceformat(values):
out = [] out = []

View File

@@ -11,6 +11,7 @@ from datetimewidget.widgets import DateTimeWidget
from django.core.validators import validate_email from django.core.validators import validate_email
import os import os
import twitter import twitter
import re
from django.conf import settings from django.conf import settings
from sqlalchemy import create_engine from sqlalchemy import create_engine
@@ -50,6 +51,9 @@ database_url = 'mysql://{user}:{password}@{host}:{port}/{database_name}'.format(
if settings.DEBUG or user=='': if settings.DEBUG or user=='':
database_url = 'sqlite:///db.sqlite3' 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 # model for Power Zone names
class PowerZonesField(models.TextField): class PowerZonesField(models.TextField):
@@ -63,6 +67,19 @@ class PowerZonesField(models.TextField):
if not value: return if not value: return
if isinstance(value, list): if isinstance(value, list):
return value 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) return value.split(self.token)
def from_db_value(self,value, expression, connection, context): def from_db_value(self,value, expression, connection, context):
@@ -83,13 +100,40 @@ class PowerZonesField(models.TextField):
# For future Team functionality # For future Team functionality
class Team(models.Model): class Team(models.Model):
name = models.CharField(max_length=150) choices = (
notes = models.CharField(blank=True,max_length=200) ('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) manager = models.ForeignKey(User)
private = models.CharField(max_length=30,choices=choices,default='open',
verbose_name='Team Type')
def __unicode__(self): def __unicode__(self):
return self.name 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 # Extension of User with rowing specific data
class Rower(models.Model): class Rower(models.Model):
weightcategories = ( weightcategories = (
@@ -149,6 +193,8 @@ class Rower(models.Model):
choices=plans) choices=plans)
planexpires = models.DateField(default=timezone.now) planexpires = models.DateField(default=timezone.now)
teamplanexpires = models.DateField(default=timezone.now)
clubsize = models.IntegerField(default=0)
# Friends/Team # Friends/Team
friends = models.ManyToManyField("self",blank=True) friends = models.ManyToManyField("self",blank=True)
@@ -297,7 +343,7 @@ class Workout(models.Model):
) )
user = models.ForeignKey(Rower) user = models.ForeignKey(Rower)
team = models.ForeignKey(Team,blank=True,null=True) team = models.ManyToManyField(Team,blank=True)
name = models.CharField(max_length=150) name = models.CharField(max_length=150)
date = models.DateField() date = models.DateField()
workouttype = models.CharField(choices=workouttypes,max_length=50) 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 rowers.dataprepnodjango import update_strokedata
from django.core.mail import send_mail, BadHeaderError,EmailMessage from django.core.mail import send_mail, BadHeaderError,EmailMessage
# testing task # testing task
@@ -54,6 +55,7 @@ def handle_sendemail_unrecognized(unrecognizedfile,useremail):
os.remove(unrecognizedfile) os.remove(unrecognizedfile)
return 1 return 1
# Send email with TCX attachment # Send email with TCX attachment
@app.task @app.task
def handle_sendemailtcx(first_name,last_name,email,tcxfile): def handle_sendemailtcx(first_name,last_name,email,tcxfile):
@@ -116,7 +118,10 @@ def handle_otwsetpower(f1,boattype,weightvalue,
try: try:
rowdata = rdata(f1) rowdata = rdata(f1)
except IOError: except IOError:
rowdata = rdata(f1+'.csv') try:
rowdata = rdata(f1+'.csv')
except IOError:
rowdata = rdata(f1+'.gz')
weightvalue = float(weightvalue) weightvalue = float(weightvalue)
@@ -154,14 +159,12 @@ def handle_otwsetpower(f1,boattype,weightvalue,
subject = "Your Rowsandall OTW calculations are ready" subject = "Your Rowsandall OTW calculations are ready"
message = "Dear "+first_name+",\n\n" message = "Dear "+first_name+",\n\n"
message += "Your Rowsandall OTW calculations are ready.\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 += "Thank you for using rowsandall.com.\n\n"
message += "Rowsandall OTW calculations have not been fully implemented yet.\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 += "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 += "Your wind/stream corrected plot is available here: http://rowsandall.com/rowers/workout/"
message += str(workoutid) message += str(workoutid)
message +="/interactiveotwplot\n\n" 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 += "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." message += "Best Regards, The Rowsandall Physics Department."
@@ -232,6 +235,53 @@ def handle_makeplot(f1,f2,t,hrdata,plotnr,imagename):
gc.collect() gc.collect()
return imagename 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 # Another simple task for debugging purposes
def add2(x,y): 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_6 alpha">
<div class="grid_2 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> <a class="button blue small" href="/rowers/workout/compare/{{ workout.id }}">Compare Workouts</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership/">Compare Workouts</a> <a class="button blue small" href="/rowers/promembership/">Compare Workouts</a>
@@ -71,7 +71,7 @@
<div class="grid_2 omega tooltip"> <div class="grid_2 omega tooltip">
<p> <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> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/editintervals">Edit Intervals</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">Edit Intervals</a> <a class="button blue small" href="/rowers/promembership">Edit Intervals</a>
@@ -88,7 +88,7 @@
<div class="grid_2 alpha"> <div class="grid_2 alpha">
<p> <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> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/adddistanceplot2">Dist Metrics Plot</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">Dist Metrics Plot</a> <a class="button blue small" href="/rowers/promembership">Dist Metrics Plot</a>
@@ -100,7 +100,7 @@
</div> </div>
<div class="grid_2"> <div class="grid_2">
<p> <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> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/addtimeplot2">Time Metrics Plot</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">Time Metrics Plot</a> <a class="button blue small" href="/rowers/promembership">Time Metrics Plot</a>
@@ -123,7 +123,7 @@
<div class="grid_2 suffix_4 alpha"> <div class="grid_2 suffix_4 alpha">
<p> <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> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/histo">Power Histogram</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">Dist Metrics Plot</a> <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_6 alpha">
<div class="grid_2 alpha"> <div class="grid_2 alpha">
<p> <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> <a class="button blue small" href="/rowers/workout/compare/{{ workout.id }}">Compare Workouts</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">Compare Workouts</a> <a class="button blue small" href="/rowers/promembership">Compare Workouts</a>
@@ -73,7 +73,7 @@
<div class="grid_2 omega tooltip"> <div class="grid_2 omega tooltip">
<p> <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> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/editintervals">Edit Intervals</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">Edit Intervals</a> <a class="button blue small" href="/rowers/promembership">Edit Intervals</a>
@@ -90,7 +90,7 @@
<div class="grid_6 alpha"> <div class="grid_6 alpha">
<div class="grid_2 alpha"> <div class="grid_2 alpha">
<p> <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> <a class="button blue small"href="/rowers/workout/{{ workout.id }}/crewnerdsummary">CrewNerd Summary</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">CrewNerd Summary</a> <a class="button blue small" href="/rowers/promembership">CrewNerd Summary</a>
@@ -104,7 +104,7 @@
<div class="grid_2 tooltip"> <div class="grid_2 tooltip">
<p> <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> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/forcecurve">Stroke Profile (Empower)</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">Stroke Profile (Empower)</a> <a class="button blue small" href="/rowers/promembership">Stroke Profile (Empower)</a>
@@ -117,7 +117,7 @@
<div class="grid_2 omega tooltip"> <div class="grid_2 omega tooltip">
<p> <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> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/addotwpowerplot">OTW Power Plot</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">OTW Power Plot</a> <a class="button blue small" href="/rowers/promembership">OTW Power Plot</a>
@@ -137,7 +137,7 @@
<div class="grid_2 alpha"> <div class="grid_2 alpha">
<p> <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> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/geeky">Geeky Stuff</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">Geeky Stuff</a> <a class="button blue small" href="/rowers/promembership">Geeky Stuff</a>
@@ -152,7 +152,7 @@
<div class="grid_2 tooltip"> <div class="grid_2 tooltip">
<p> <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> <a class="button blue small"href="/rowers/workout/{{ workout.id }}/smoothenpace">Smooth out Pace Data</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">Smooth out Pace Data</a> <a class="button blue small" href="/rowers/promembership">Smooth out Pace Data</a>
@@ -169,7 +169,7 @@
<div class="grid_2 omega"> <div class="grid_2 omega">
<p> <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> <a class="button blue small"href="/rowers/workout/{{ workout.id }}/undosmoothenpace">Raw Data</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">Reset Smoothing</a> <a class="button blue small" href="/rowers/promembership">Reset Smoothing</a>

View File

@@ -41,7 +41,7 @@
<h2>Pro</h2> <h2>Pro</h2>
<div class="grid_2 alpha"> <div class="grid_2 alpha">
<p> <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> <a class="button blue small" href="/rowers/histo">Power Histogram</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">Power Histogram</a> <a class="button blue small" href="/rowers/promembership">Power Histogram</a>
@@ -53,7 +53,7 @@
</div> </div>
<div class="grid_2"> <div class="grid_2">
<p> <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> <a class="button blue small" href="/rowers/cumstats">Statistics</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">Statistics</a> <a class="button blue small" href="/rowers/promembership">Statistics</a>

View File

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

View File

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

View File

@@ -98,7 +98,7 @@
<div class="grid_8 omega"> <div class="grid_8 omega">
{% if cordict %} {% if cordict %}
<h2> Correlation Matrix</h2> <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> </p>
<table width="90%" class="cortable"> <table width="90%" class="cortable">
<thead> <thead>
@@ -116,13 +116,13 @@
{% for key2,value in thedict.items %} {% for key2,value in thedict.items %}
<td> <td>
{% if value > 0.5 %} {% if value > 0.5 %}
<div class="poscor">++</div> <div class="poscor">{{ value|floatformat:-1 }}</div>
{% elif value > 0.1 %} {% elif value > 0.1 %}
<div class="weakposcor">+</div> <div class="weakposcor">{{ value|floatformat:-1 }}</div>
{% elif value < -0.5 %} {% elif value < -0.5 %}
<div class="negcor">--</div> <div class="negcor">{{ value|floatformat:-1 }}</div>
{% elif value < -0.1 %} {% elif value < -0.1 %}
<div class="weaknegcor">-</div> <div class="weaknegcor">{{ value|floatformat:-1 }}</div>
{% else %} {% else %}
&nbsp; &nbsp;
{% endif %} {% endif %}

View File

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

View File

@@ -203,7 +203,7 @@
</div> </div>
{% if user.rower.rowerplan == 'pro' %} {% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %}
<div id="favorites" class="grid_12 alpha"> <div id="favorites" class="grid_12 alpha">
<div class="grid_2 suffix_4 alpha"> <div class="grid_2 suffix_4 alpha">
{% if maxfav >= 0 %} {% 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_6 alpha">
<div class="grid_2 alpha"> <div class="grid_2 alpha">
<p> <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> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/wind">Edit Wind Data</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/about">Edit Wind Data</a> <a class="button blue small" href="/rowers/about">Edit Wind Data</a>
@@ -70,7 +70,7 @@
</div> </div>
<div class="grid_2"> <div class="grid_2">
<p> <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> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/stream">Edit Stream Data</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/about">Edit Stream Data</a> <a class="button blue small" href="/rowers/about">Edit Stream Data</a>
@@ -83,7 +83,7 @@
</div> </div>
<div class="grid_2 omega"> <div class="grid_2 omega">
<p> <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> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/otwsetpower">OTW Power</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/about">OTW Power</a> <a class="button blue small" href="/rowers/about">OTW Power</a>
@@ -100,7 +100,7 @@
<div class="grid_2 alpha"> <div class="grid_2 alpha">
<p> <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> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/interactiveotwplot">Corrected Pace Plot</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/about">Corrected Pace Plot</a> <a class="button blue small" href="/rowers/about">Corrected Pace Plot</a>

View File

@@ -3,107 +3,124 @@
{% block title %}Change Rower {% endblock %} {% block title %}Change Rower {% endblock %}
{% block content %} {% block content %}
<div class="grid_6 alpha"> <div class="grid_12 alpha">
<p> <div class="grid_6 alpha">
<h2>Heart Rate Zones</h2> <p>
{% if form.errors %} <h2>Heart Rate Zones</h2>
<p style="color: red;"> {% if form.errors %}
Please correct the error{{ form.errors|pluralize }} below. <p style="color: red;">
</p> Please correct the error{{ form.errors|pluralize }} below.
{% endif %} </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>
<form enctype="multipart/form-data" action="" method="post"> <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> <table>
<thead> {{ form.as_table }}
<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> </table>
{% csrf_token %} {% csrf_token %}
<div class="grid_2 prefix_2 suffix_2"> <div class="grid_2 prefix_2 suffix_2">
<input class="button green" type="submit" value="Save"> <input class="button green" type="submit" value="Save">
</div>
</form> </form>
</p> </p>
<p> </div>
<h2>Functional Threshold Power</h2> </div>
<p>Use this form to quickly change your zones based on the power of a <div class="grid_6 omega">
recent <p>
full out 60 minutes effort. It will update all zones defined above.</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"> <form enctype="multipart/form-data" action="" method="post">
<table> <table>
{{ powerform.as_table }} {{ powerform.as_table }}
@@ -113,43 +130,61 @@
<input class="button green" type="submit" value="Save"> <input class="button green" type="submit" value="Save">
</form> </form>
</div> </div>
</p> </div>
{% if grants %} </div>
<p> <div class="grid_12 alpha">
<h2>Applications</h2> <div class="grid_6 alpha">
<table width="100%"> <p>
<thead> <h2>Teams</h2>
<tr> <div class="grid_2 suffix_4 alpha">
<th>Application</th> <a class="button gray small" href="/rowers/me/teams">Manage Teams</a>
<th>Scope</th> </div>
<th>Revoke</th> </p>
</tr> </div>
</thead>
<tbody> <div class="grid_6 omega">
{% for grant in grants %} <p>
<tr> <h2>Favorite Charts</h2>
<td>{{ grant.application }}</td> <div class="grid_2 suffix_4 alpha">
<td>{{ grant.scope }}</td> <a class="button gray small" href="/rowers/me/favoritecharts">Manage Favorite Charts</a>
<td> </div>
<a class="button red small" href="/rowers/me/revokeapp/{{ grant.application.id }}">Revoke</a> </p>
</div>
</div>
<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> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</p> </p>
{% endif %} {% else %}
<p>&nbsp;</p>
{% endif %}
</div>
</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>
{% endblock %} {% 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>
<div class="grid_8 omega"> <div class="grid_8 omega">
{% if cordict %} {% if cordict %}
<h2> Correlation table</h2> <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> </p>
<table width="90%" class="cortable"> <table width="90%" class="cortable">
<thead> <thead>
@@ -94,13 +94,13 @@
{% for key2,value in thedict.items %} {% for key2,value in thedict.items %}
<td> <td>
{% if value > 0.5 %} {% if value > 0.5 %}
<div class="poscor">++</div> <div class="poscor">{{ value|floatformat:-1 }}</div>
{% elif value > 0.1 %} {% elif value > 0.1 %}
<div class="weakposcor">+</div> <div class="weakposcor">{{ value|floatformat:-1 }}</div>
{% elif value < -0.5 %} {% elif value < -0.5 %}
<div class="negcor">--</div> <div class="negcor">{{ value|floatformat:-1 }}</div>
{% elif value < -0.1 %} {% elif value < -0.1 %}
<div class="weaknegcor">-</div> <div class="weaknegcor">{{ value|floatformat:-1 }}</div>
{% else %} {% else %}
&nbsp; &nbsp;
{% endif %} {% endif %}

View File

@@ -199,6 +199,20 @@ urlpatterns = [
url(r'^workout/(\d+)/stravauploadw/$',views.workout_strava_upload_view), url(r'^workout/(\d+)/stravauploadw/$',views.workout_strava_upload_view),
url(r'^workout/(\d+)/recalcsummary/$',views.workout_recalcsummary_view), url(r'^workout/(\d+)/recalcsummary/$',views.workout_recalcsummary_view),
url(r'^workout/(\d+)/sporttracksuploadw/$',views.workout_sporttracks_upload_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/c/(?P<message>\w+.*)$',views.rower_edit_view),
url(r'^me/edit/s/(?P<successmessage>\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), 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 ( from rowers.models import (
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm, RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm,
RowerPowerZonesForm,AccountRowerForm,UserForm,StrokeData, RowerPowerZonesForm,AccountRowerForm,UserForm,StrokeData,
Team,TeamForm,TeamInviteForm,TeamInvite
) )
from rowers.models import FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement from rowers.models import FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement
from django.forms.formsets import formset_factory 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.rows import handle_uploaded_file
from rowers.tasks import handle_makeplot,handle_otwsetpower,handle_sendemailtcx,handle_sendemailcsv from rowers.tasks import handle_makeplot,handle_otwsetpower,handle_sendemailtcx,handle_sendemailcsv
from rowers.tasks import handle_sendemail_unrecognized from rowers.tasks import handle_sendemail_unrecognized
from scipy.signal import savgol_filter from scipy.signal import savgol_filter
from django.shortcuts import render_to_response from django.shortcuts import render_to_response
from Cookie import SimpleCookie from Cookie import SimpleCookie
@@ -198,8 +200,14 @@ def splitstdata(lijst):
from utils import geo_distance,serialize_list,deserialize_list 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 # Check if a user is a Pro member
def promember(user): def ispromember(user):
r = Rower.objects.get(user=user) r = Rower.objects.get(user=user)
result = user.is_authenticated() and (r.rowerplan=='pro' or r.rowerplan=='coach') result = user.is_authenticated() and (r.rowerplan=='pro' or r.rowerplan=='coach')
return result return result
@@ -1301,7 +1309,7 @@ def histo_all(request,theuser=0):
if not request.user.is_anonymous(): 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 r.rowerplan=='pro' result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
promember=1 promember=1
@@ -1371,7 +1379,7 @@ def cum_flex(request,theuser=0,
if not request.user.is_anonymous(): 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 r.rowerplan=='pro' result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
promember=1 promember=1
@@ -1463,14 +1471,14 @@ def cum_flex(request,theuser=0,
}) })
# Show the EMpower Oarlock generated Stroke Profile # 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): def workout_forcecurve_view(request,id=0,workstrokesonly=False):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
promember=0 promember=0
mayedit=0 mayedit=0
if not request.user.is_anonymous(): 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 r.rowerplan=='pro' result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
promember=1 promember=1
if request.user == row.user.user: if request.user == row.user.user:
@@ -1509,7 +1517,7 @@ def workout_histo_view(request,id=0):
mayedit=0 mayedit=0
if not request.user.is_anonymous(): 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 r.rowerplan=='pro' result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
promember=1 promember=1
if request.user == row.user.user: if request.user == row.user.user:
@@ -1561,7 +1569,7 @@ def histo(request,theuser=0,
if not request.user.is_anonymous(): 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 r.rowerplan=='pro' result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
promember=1 promember=1
@@ -1671,7 +1679,7 @@ def rankings_view(request,theuser=0,
promember=0 promember=0
if not request.user.is_anonymous(): 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 r.rowerplan=='pro' result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
promember=1 promember=1
@@ -2040,7 +2048,7 @@ def workouts_view(request,message='',successmessage='',
return HttpResponse("User has no rower instance") return HttpResponse("User has no rower instance")
# List of workouts to compare a selected workout to # 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='', def workout_comparison_list(request,id=0,message='',successmessage='',
startdatestring="",enddatestring="", startdatestring="",enddatestring="",
startdate=timezone.now()-datetime.timedelta(days=365), startdate=timezone.now()-datetime.timedelta(days=365),
@@ -2155,7 +2163,7 @@ def workout_view(request,id=0):
except Workout.DoesNotExist: except Workout.DoesNotExist:
return HttpResponseNotFound("Workout doesn't exist") 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) @user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_undo_smoothenpace_view(request,id=0,message="",successmessage=""): def workout_undo_smoothenpace_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
@@ -2182,7 +2190,7 @@ def workout_undo_smoothenpace_view(request,id=0,message="",successmessage=""):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
# Data smoothing of pace data # Data smoothing of pace data
@user_passes_test(ispromember,login_url="/",redirect_field_name=None) @user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_smoothenpace_view(request,id=0,message="",successmessage=""): def workout_smoothenpace_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
@@ -2218,7 +2226,7 @@ def workout_smoothenpace_view(request,id=0,message="",successmessage=""):
return HttpResponseRedirect(url) 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) @user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_crewnerd_summary_view(request,id=0,message="",successmessage=""): def workout_crewnerd_summary_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
@@ -2263,7 +2271,7 @@ def workout_crewnerd_summary_view(request,id=0,message="",successmessage=""):
{'form':form, {'form':form,
'id':row.id}) '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) @user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_downloadwind_view(request,id=0,message="",successmessage=""): def workout_downloadwind_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
@@ -2320,7 +2328,7 @@ def workout_downloadwind_view(request,id=0,message="",successmessage=""):
return response return response
# Show form to update wind data # Show form to update wind data
@user_passes_test(ispromember,login_url="/",redirect_field_name=None) @user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_wind_view(request,id=0,message="",successmessage=""): def workout_wind_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
@@ -2420,7 +2428,7 @@ def workout_wind_view(request,id=0,message="",successmessage=""):
'gmapdiv':gmdiv}) '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) @user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_stream_view(request,id=0,message="",successmessage=""): def workout_stream_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
@@ -2482,7 +2490,7 @@ def workout_stream_view(request,id=0,message="",successmessage=""):
'form':form, 'form':form,
'the_div':div}) '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) @user_passes_test(ispromember, login_url="/",redirect_field_name=None)
def workout_otwsetpower_view(request,id=0,message="",successmessage=""): def workout_otwsetpower_view(request,id=0,message="",successmessage=""):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
@@ -2654,7 +2662,7 @@ def cumstats(request,theuser=0,
theuser = request.user.id theuser = request.user.id
if not request.user.is_anonymous(): 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) result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
promember=1 promember=1
@@ -2738,97 +2746,17 @@ def cumstats(request,theuser=0,
u = '' u = ''
ids = [int(workout.id) for workout in allergworkouts] 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() fieldlist,fielddict = dataprep.getstatsfields()
# prepare data frame # prepare data frame
datadf = dataprep.read_cols_df_sql(ids,fieldlist) datadf = dataprep.read_cols_df_sql(ids,fieldlist)
# clean data remove zeros and negative values
datadf=datadf.clip(lower=0)
datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly) datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
# 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: if datadf.empty:
return HttpResponse("No data found") 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 # 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" message = "You are not allowed to see the stats of this workout"
url = reverse(workouts_view,args=[str(message)]) url = reverse(workouts_view,args=[str(message)])
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly) 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: if datadf.empty:
@@ -2961,44 +2846,12 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
workoutstateswork = [1,4,5,8,9,6,7] workoutstateswork = [1,4,5,8,9,6,7]
workoutstatesrest = [3] workoutstatesrest = [3]
workoutstatetransition = [0,2,10,11,12,13] workoutstatetransition = [0,2,10,11,12,13]
if workstrokesonly=='True' or workstrokesonly==True:
try:
datadf = datadf[~datadf['workoutstate'].isin(workoutstatesrest)]
except:
pass
# Create stats # Create stats
stats = {} stats = {}
# Get field names and remove those that are not useful in stats
fieldlist,fielddict = dataprep.getstatsfields() fieldlist,fielddict = dataprep.getstatsfields()
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')
for field,verbosename in fielddict.iteritems(): 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'): def workout_comparison_view(request,id1=0,id2=0,xparam='distance',yparam='spm'):
promember=0 promember=0
if not request.user.is_anonymous(): 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) result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
promember=1 promember=1
@@ -3113,7 +2966,7 @@ def workout_comparison_view2(request,id1=0,id2=0,xparam='distance',
yparam='spm',plottype='line'): yparam='spm',plottype='line'):
promember=0 promember=0
if not request.user.is_anonymous(): 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) result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
promember=1 promember=1
@@ -3161,7 +3014,7 @@ def workout_flexchart3_view(request,*args,**kwargs):
promember=0 promember=0
mayedit=0 mayedit=0
if not request.user.is_anonymous(): 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) result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
promember=1 promember=1
@@ -3316,7 +3169,7 @@ def workout_biginteractive_view(request,id=0,message="",successmessage=""):
promember=0 promember=0
mayedit=0 mayedit=0
if not request.user.is_anonymous(): 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) result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
promember=1 promember=1
@@ -3353,7 +3206,7 @@ def workout_otwpowerplot_view(request,id=0,message="",successmessage=""):
promember=0 promember=0
mayedit=0 mayedit=0
if not request.user.is_anonymous(): 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) result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
promember=1 promember=1
@@ -3555,7 +3408,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""):
}) })
return HttpResponseRedirect(url) 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) @user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_add_otw_powerplot_view(request,id): def workout_add_otw_powerplot_view(request,id):
w = Workout.objects.get(id=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" url = "/rowers/workout/"+str(w.id)+"/edit"
return HttpResponseRedirect(url) 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) @user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_add_distanceplot2_view(request,id): def workout_add_distanceplot2_view(request,id):
w = Workout.objects.get(id=id) w = Workout.objects.get(id=id)
@@ -3871,7 +3724,7 @@ def workout_add_distanceplot2_view(request,id):
return HttpResponseRedirect(url) 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) @user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_add_timeplot2_view(request,id): def workout_add_timeplot2_view(request,id):
w = Workout.objects.get(id=id) w = Workout.objects.get(id=id)
@@ -4934,7 +4787,7 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
'savebutton':savebutton, '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) @user_passes_test(ispromember,login_url="/rowers/me/edit",redirect_field_name=None)
def rower_favoritecharts_view(request): def rower_favoritecharts_view(request):
message = '' message = ''
@@ -5430,3 +5283,190 @@ def strokedatajson(request,id):
return HttpResponse(row.id,status=201) return HttpResponse(row.id,status=201)
#Method not supported #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)