Merge branch 'release/team_management'
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
208
rowers/teams.py
Normal 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')
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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> </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>
|
||||
|
||||
@@ -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> </p>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
27
rowers/templates/invitations.html
Normal file
27
rowers/templates/invitations.html
Normal 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 %}
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
{% 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> </p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
70
rowers/templates/team.html
Normal file
70
rowers/templates/team.html
Normal 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>
|
||||
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
28
rowers/templates/teamcreate.html
Normal file
28
rowers/templates/teamcreate.html
Normal 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 %}
|
||||
43
rowers/templates/teamdeleteconfirm.html
Normal file
43
rowers/templates/teamdeleteconfirm.html
Normal 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>
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
45
rowers/templates/teamleaveconfirm.html
Normal file
45
rowers/templates/teamleaveconfirm.html
Normal 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>
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
133
rowers/templates/teams.html
Normal file
133
rowers/templates/teams.html
Normal 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> </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> </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> </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> </p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% endblock %}
|
||||
@@ -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 %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
@@ -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),
|
||||
|
||||
412
rowers/views.py
412
rowers/views.py
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user