Merge branch 'release/v5.78'
This commit is contained in:
@@ -5,7 +5,7 @@ from django.contrib.auth.models import User
|
||||
from .models import (
|
||||
Rower, Workout,GraphImage,FavoriteChart,SiteAnnouncement,
|
||||
Team,TeamInvite,TeamRequest,
|
||||
WorkoutComment,C2WorldClassAgePerformance,
|
||||
WorkoutComment,C2WorldClassAgePerformance,PlannedSession,
|
||||
)
|
||||
|
||||
# Register your models here so you can use them in the Admin module
|
||||
@@ -43,11 +43,17 @@ class TeamRequestAdmin(admin.ModelAdmin):
|
||||
|
||||
class WorkoutCommentAdmin(admin.ModelAdmin):
|
||||
list_display = ('created','user','workout')
|
||||
|
||||
|
||||
class PlannedSessionAdmin(admin.ModelAdmin):
|
||||
list_display = ('name','startdate','enddate','manager','sessionvalue','sessionunit')
|
||||
|
||||
class GraphImageAdmin(admin.ModelAdmin):
|
||||
list_display = ('creationdatetime','workout','filename')
|
||||
|
||||
admin.site.unregister(User)
|
||||
admin.site.register(User,UserAdmin)
|
||||
admin.site.register(Workout,WorkoutAdmin)
|
||||
admin.site.register(GraphImage)
|
||||
admin.site.register(GraphImage,GraphImageAdmin)
|
||||
admin.site.register(Team,TeamAdmin)
|
||||
admin.site.register(FavoriteChart,FavoriteChartAdmin)
|
||||
admin.site.register(SiteAnnouncement,SiteAnnouncementAdmin)
|
||||
@@ -56,3 +62,5 @@ admin.site.register(TeamRequest,TeamRequestAdmin)
|
||||
admin.site.register(WorkoutComment,WorkoutCommentAdmin)
|
||||
admin.site.register(C2WorldClassAgePerformance,
|
||||
C2WorldClassAgePerformanceAdmin)
|
||||
admin.site.register(PlannedSession,PlannedSessionAdmin)
|
||||
|
||||
|
||||
73
rowers/courses.py
Normal file
73
rowers/courses.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# All the Courses 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 utils import myqueue
|
||||
|
||||
from matplotlib import path
|
||||
|
||||
import django_rq
|
||||
queue = django_rq.get_queue('default')
|
||||
queuelow = django_rq.get_queue('low')
|
||||
queuehigh = django_rq.get_queue('low')
|
||||
|
||||
from rowers.models import (
|
||||
Rower, Workout,
|
||||
GeoPoint,GeoPolygon, GeoCourse,
|
||||
)
|
||||
|
||||
# low level methods
|
||||
class InvalidTrajectoryError(Exception):
|
||||
def __init__(self,value):
|
||||
self.value=value
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.value)
|
||||
|
||||
def polygon_to_path(polygon):
|
||||
points = GeoPoint.objects.filter(polygon==polygon).order_by(order_in_polygon)
|
||||
s = []
|
||||
for point in points:
|
||||
s.append([point.latitude,point.longitude])
|
||||
|
||||
p = path.Path(np.array(s))
|
||||
|
||||
return p
|
||||
|
||||
def coordinate_in_polygon(latitude,longitude, polygon):
|
||||
p = polygon_to_path(polygon)
|
||||
|
||||
retun p.contains_points([(latitude,longitude)])[0]
|
||||
|
||||
|
||||
|
||||
def time_in_polygon(df,polygon,maxmin='max'):
|
||||
# df has timestamp, latitude, longitude
|
||||
p = polygon_to_path(polygon)
|
||||
|
||||
latitude = df.latitude
|
||||
longitude = df.longitude
|
||||
|
||||
f = lambda x: coordinate_in_polygon(x['latitude'],x['longitude'],polygon)
|
||||
|
||||
df['inpolygon'] = df.apply(f,axis=1)
|
||||
|
||||
mask = df['inpolygon'] == True
|
||||
|
||||
if df[mask].empty():
|
||||
raise InvalidTrajectoryError
|
||||
|
||||
if maxmin == 'max':
|
||||
time = df[mask]['time'].max()
|
||||
else:
|
||||
time = df[mask]['time'].min()
|
||||
|
||||
|
||||
return time
|
||||
@@ -36,7 +36,7 @@ from rowingdata import (
|
||||
summarydata, get_file_type,
|
||||
)
|
||||
|
||||
from rowers.metrics import axes
|
||||
from rowers.metrics import axes,calc_trimp
|
||||
from async_messages import messages as a_messages
|
||||
import os
|
||||
import zipfile
|
||||
@@ -2212,3 +2212,40 @@ def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True,
|
||||
conn.close()
|
||||
engine.dispose()
|
||||
return data
|
||||
|
||||
def workout_trimp(workout):
|
||||
r = workout.user
|
||||
df,row = getrowdata_db(id=workout.id)
|
||||
df = clean_df_stats(df)
|
||||
if df.empty:
|
||||
df,row = getrowdata_db(id=workout.id)
|
||||
df = clean_df_stats(df,workstrokesonly=False)
|
||||
trimp = calc_trimp(df,r.sex,r.max,r.rest)
|
||||
trimp = int(trimp)
|
||||
|
||||
return trimp
|
||||
|
||||
def workout_rscore(w):
|
||||
r = workout.user
|
||||
df,row = getrowdata_db(id=workout.id)
|
||||
df = clean_df_stats(df)
|
||||
if df.empty:
|
||||
df,row = getrowdata_db(id=workout.id)
|
||||
df = clean_df_stats(df,workstrokesonly=False)
|
||||
|
||||
duration = df['time'].max()-df['time'].min()
|
||||
duration /= 1.0e3
|
||||
pwr4 = df['power']**(4.0)
|
||||
normp = (pwr4.mean())**(0.25)
|
||||
if not np.isnan(normp):
|
||||
ftp = float(r.ftp)
|
||||
if w.workouttype in ('water','coastal'):
|
||||
ftp = ftp*(100.-r.otwslack)/100.
|
||||
|
||||
intensityfactor = df['power'].mean()/float(ftp)
|
||||
intensityfactor = normp/float(ftp)
|
||||
tss = 100.*((duration*normp*intensityfactor)/(3600.*ftp))
|
||||
else:
|
||||
tss = 0
|
||||
|
||||
return tss
|
||||
|
||||
@@ -602,3 +602,30 @@ class FusionMetricChoiceForm(ModelForm):
|
||||
metricchoices = list(sorted(formaxlabels2.items(), key = lambda x:x[1]))
|
||||
self.fields['columns'].choices = metricchoices
|
||||
|
||||
class PlannedSessionSelectForm(forms.Form):
|
||||
|
||||
def __init__(self, sessionchoices, *args, **kwargs):
|
||||
initialsession = kwargs.pop('initialsession',None)
|
||||
super(PlannedSessionSelectForm, self).__init__(*args,**kwargs)
|
||||
|
||||
self.fields['plannedsession'] = forms.ChoiceField(
|
||||
label='Sessions',
|
||||
choices = sessionchoices,
|
||||
widget = forms.RadioSelect,
|
||||
initial=initialsession
|
||||
)
|
||||
|
||||
|
||||
class WorkoutSessionSelectForm(forms.Form):
|
||||
|
||||
def __init__(self, workoutdata, *args, **kwargs):
|
||||
|
||||
super(WorkoutSessionSelectForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields['workouts'] = forms.MultipleChoiceField(
|
||||
label='Workouts',
|
||||
choices = workoutdata['choices'],
|
||||
initial=workoutdata['initial'],
|
||||
widget = forms.CheckboxSelectMultiple,
|
||||
)
|
||||
|
||||
|
||||
@@ -307,6 +307,7 @@ This value should be fairly constant across all stroke rates.""",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def calc_trimp(df,sex,hrmax,hrmin):
|
||||
if sex == 'male':
|
||||
f = 1.92
|
||||
@@ -321,6 +322,7 @@ def calc_trimp(df,sex,hrmax,hrmin):
|
||||
|
||||
return trimp
|
||||
|
||||
|
||||
def getagegrouprecord(age,sex='male',weightcategory='hwt',
|
||||
distance=2000,duration=None,indf=pd.DataFrame()):
|
||||
|
||||
|
||||
283
rowers/models.py
283
rowers/models.py
@@ -483,6 +483,7 @@ class Rower(models.Model):
|
||||
plans = (
|
||||
('basic','basic'),
|
||||
('pro','pro'),
|
||||
('plan','plan'),
|
||||
('coach','coach')
|
||||
)
|
||||
|
||||
@@ -623,10 +624,284 @@ def checkworkoutuser(user,workout):
|
||||
except Rower.DoesNotExist:
|
||||
return False
|
||||
|
||||
# Check if user is coach or rower
|
||||
def checkaccessuser(user,rower):
|
||||
try:
|
||||
r = Rower.objects.get(user=user)
|
||||
teams = Team.objects.filter(manager=user)
|
||||
if rower == r:
|
||||
return True
|
||||
elif teams:
|
||||
for team in teams:
|
||||
if team in rower.team.all():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except Rower.DoesNotExist:
|
||||
return False
|
||||
|
||||
timezones = (
|
||||
(x,x) for x in pytz.common_timezones
|
||||
)
|
||||
|
||||
# models related to geo data (points, polygon, courses)
|
||||
|
||||
class GeoCourse(models.Model):
|
||||
manager = models.ForeignKey(Rower)
|
||||
name = models.CharField(max_length=150,blank=True)
|
||||
|
||||
|
||||
class GeoPolygon(models.Model):
|
||||
course = models.ForeignKey(GeoCourse, blank=True)
|
||||
order_in_course = models.IntegerField(default=0)
|
||||
|
||||
# Need error checking to insert new polygons into existing course (all later polygons
|
||||
# increase there order_in_course number
|
||||
|
||||
class GeoPoint(models.Model):
|
||||
latitude = models.FloatField(default=0)
|
||||
longitude = models.FloatField(default=0)
|
||||
polygon = models.ForeignKey(GeoPolygon,blank=True)
|
||||
order_in_poly = models.IntegerField(default=0)
|
||||
|
||||
# need error checking to "insert" new point into existing polygon? This affects order_in_poly
|
||||
# of multiple GeoPoint instances
|
||||
|
||||
|
||||
def half_year_from_now():
|
||||
return timezone.now()+timezone.timedelta(days=182)
|
||||
|
||||
|
||||
# models related to training planning - draft
|
||||
# Do we need a separate class TestTarget?
|
||||
class TrainingTarget(models.Model):
|
||||
rower = models.ForeignKey(Rower)
|
||||
name = models.CharField(max_length=150,blank=True)
|
||||
date = models.DateField(
|
||||
default=half_year_from_now)
|
||||
notes = models.TextField(max_length=300,blank=True)
|
||||
|
||||
class TrainingTargetForm(ModelForm):
|
||||
class Meta:
|
||||
model = TrainingTarget
|
||||
fields = ['name','date','notes']
|
||||
|
||||
widgets = {
|
||||
'date': SelectDateWidget(
|
||||
years=range(
|
||||
timezone.now().year-1,timezone.now().year+1)),
|
||||
}
|
||||
|
||||
# SportTracks has a TrainingGoal like this
|
||||
#class TrainingGoal(models.Model):
|
||||
# rower = models.ForeignKey(Rower)
|
||||
# name = models.CharField(max_length=150,blank=True)
|
||||
# startdate = models.DateField(default=timezone.now)
|
||||
# enddate = models.DateField(
|
||||
# default=timezone.now()+datetime.timedelta(days=28))
|
||||
# goalmetric = models.CharField(max_length=150,default='rower',
|
||||
# choices = modechoices)
|
||||
# value = models.IntegerValue(default=1)
|
||||
|
||||
# I think we can use PlannedSession for that (in challenge mode)
|
||||
# although such a TrainingGoal could have automatically calculated
|
||||
# values without needing the user to assign
|
||||
|
||||
|
||||
class TrainingPlan(models.Model):
|
||||
rower = models.ForeignKey(Rower)
|
||||
name = models.CharField(max_length=150,blank=True)
|
||||
target = models.ForeignKey(TrainingTarget,blank=True)
|
||||
startdate = models.DateField(default=timezone.now)
|
||||
enddate = models.DateField(
|
||||
default=half_year_from_now)
|
||||
|
||||
class TrainingPlanForm(ModelForm):
|
||||
class Meta:
|
||||
model = TrainingPlan
|
||||
fields = ['name','target','startdate','enddate']
|
||||
|
||||
widgets = {
|
||||
'startdate': SelectDateWidget(
|
||||
years=range(
|
||||
timezone.now().year-1,timezone.now().year+1)),
|
||||
'enddate': SelectDateWidget(
|
||||
years=range(
|
||||
timezone.now().year-1,timezone.now().year+1)),
|
||||
}
|
||||
|
||||
|
||||
cycletypechoices = (
|
||||
('filler','System Defined'),
|
||||
('userdefined','User Defined')
|
||||
)
|
||||
|
||||
class TrainingMacroCycle(models.Model):
|
||||
plan = models.ForeignKey(TrainingPlan)
|
||||
name = models.CharField(max_length=150,blank=True)
|
||||
startdate = models.DateField(default=timezone.now)
|
||||
enddate = models.DateField(
|
||||
default=half_year_from_now)
|
||||
notes = models.TextField(max_length=300,blank=True)
|
||||
type = models.CharField(default='filler',
|
||||
choices=cycletypechoices,
|
||||
max_length=150)
|
||||
|
||||
class TrainingMesoCycle(models.Model):
|
||||
plan = models.ForeignKey(TrainingMacroCycle)
|
||||
name = models.CharField(max_length=150,blank=True)
|
||||
startdate = models.DateField(default=timezone.now)
|
||||
enddate = models.DateField(
|
||||
default=half_year_from_now)
|
||||
notes = models.TextField(max_length=300,blank=True)
|
||||
type = models.CharField(default='filler',
|
||||
choices=cycletypechoices,
|
||||
max_length=150)
|
||||
|
||||
|
||||
class TrainingMicroCycle(models.Model):
|
||||
plan = models.ForeignKey(TrainingMesoCycle)
|
||||
name = models.CharField(max_length=150,blank=True)
|
||||
startdate = models.DateField(default=timezone.now)
|
||||
enddate = models.DateField(
|
||||
default=half_year_from_now)
|
||||
notes = models.TextField(max_length=300,blank=True)
|
||||
type = models.CharField(default='filler',
|
||||
choices=cycletypechoices,
|
||||
max_length=150)
|
||||
|
||||
|
||||
# Needs some error checking
|
||||
# - Microcycles should not overlap with other microcycles, same for MesoCycles, MacroCycles
|
||||
# - When a TrainingPlan is created, it should create 1 "collector" Macro, Meso & MicroCycle - this is invisible for users who choose to not use cycles
|
||||
# - When a new Microcycle is inserted, the "collector" cycle is automatically adjusted to "go out of the way" of the new MicroCycle - and similar for Macro & Meso
|
||||
# - If the entire MesoCycle is filled with user defined MicroCycles - there are no "filler" MicroCycles
|
||||
# - Sessions are automatically linked to the correct Cycles based on their start/end date - no need for a hard link
|
||||
|
||||
# Cycle error checking goes in forms
|
||||
|
||||
# model for Planned Session (Workout, Challenge, Test)
|
||||
class PlannedSession(models.Model):
|
||||
|
||||
sessiontypechoices = (
|
||||
('session','Training Session'),
|
||||
('challenge','Challenge'),
|
||||
('test','Mandatory Test'),
|
||||
)
|
||||
|
||||
sessionmodechoices = (
|
||||
('distance','Distance'),
|
||||
('time','Time'),
|
||||
('rScore','rScore'),
|
||||
('TRIMP','TRIMP'),
|
||||
)
|
||||
|
||||
criteriumchoices = (
|
||||
('none','Approximately'),
|
||||
('minimum','At Least'),
|
||||
('exact','Exactly'),
|
||||
)
|
||||
|
||||
verificationchoices = (
|
||||
('none','None'),
|
||||
('automatic','Automatic'),
|
||||
('manual','Manual')
|
||||
)
|
||||
|
||||
sessionunitchoices = (
|
||||
('min','minutes'),
|
||||
('km','km'),
|
||||
('m','meters'),
|
||||
('None',None),
|
||||
)
|
||||
|
||||
manager = models.ForeignKey(User)
|
||||
|
||||
name = models.CharField(max_length=150,blank=True)
|
||||
|
||||
comment = models.TextField(max_length=300,blank=True,
|
||||
)
|
||||
|
||||
startdate = models.DateField(default=timezone.now,
|
||||
verbose_name='Start Date')
|
||||
|
||||
enddate = models.DateField(default=timezone.now,
|
||||
verbose_name='End Date')
|
||||
|
||||
sessiontype = models.CharField(default='session',
|
||||
choices=sessiontypechoices,
|
||||
max_length=150,
|
||||
verbose_name='Session Type')
|
||||
|
||||
sessionvalue = models.IntegerField(default=60,verbose_name='Value')
|
||||
|
||||
max_nr_of_workouts = models.IntegerField(
|
||||
default=0,verbose_name='Maximum number of workouts'
|
||||
)
|
||||
|
||||
sessionunit = models.CharField(
|
||||
default='min',choices=sessionunitchoices,
|
||||
max_length=150,
|
||||
verbose_name='Unit')
|
||||
|
||||
criterium = models.CharField(
|
||||
default='none',
|
||||
choices=criteriumchoices,
|
||||
max_length=150)
|
||||
|
||||
verification = models.CharField(
|
||||
default='none',
|
||||
max_length=150,
|
||||
choices=verificationchoices
|
||||
)
|
||||
|
||||
team = models.ManyToManyField(Team,blank=True)
|
||||
rower = models.ManyToManyField(Rower,blank=True)
|
||||
|
||||
sessionmode = models.CharField(default='distance',
|
||||
choices=sessionmodechoices,
|
||||
max_length=150,
|
||||
verbose_name='Session Mode')
|
||||
|
||||
hasranking = models.BooleanField(default=False)
|
||||
|
||||
def __unicode__(self):
|
||||
|
||||
name = self.name
|
||||
startdate = self.startdate
|
||||
enddate = self.enddate
|
||||
|
||||
stri = u'{n} {s} - {e}'.format(
|
||||
s = startdate.strftime('%Y-%m-%d'),
|
||||
e = enddate.strftime('%Y-%m-%d'),
|
||||
n = name,
|
||||
)
|
||||
|
||||
return stri
|
||||
|
||||
# Date input utility
|
||||
class DateInput(forms.DateInput):
|
||||
input_type = 'date'
|
||||
|
||||
class PlannedSessionForm(ModelForm):
|
||||
class Meta:
|
||||
model = PlannedSession
|
||||
fields = ['startdate',
|
||||
'enddate',
|
||||
'name',
|
||||
'sessiontype',
|
||||
'sessionmode',
|
||||
'criterium',
|
||||
'sessionvalue',
|
||||
'sessionunit',
|
||||
'comment',
|
||||
]
|
||||
widgets = {
|
||||
'comment': forms.Textarea,
|
||||
'startdate': DateInput(),
|
||||
'enddate': DateInput(),
|
||||
}
|
||||
|
||||
|
||||
# Workout
|
||||
class Workout(models.Model):
|
||||
@@ -637,6 +912,7 @@ class Workout(models.Model):
|
||||
|
||||
user = models.ForeignKey(Rower)
|
||||
team = models.ManyToManyField(Team,blank=True)
|
||||
plannedsession = models.ForeignKey(PlannedSession, blank=True,null=True)
|
||||
name = models.CharField(max_length=150)
|
||||
date = models.DateField()
|
||||
workouttype = models.CharField(choices=workouttypes,max_length=50)
|
||||
@@ -864,9 +1140,6 @@ def auto_delete_image_on_delete(sender,instance, **kwargs):
|
||||
else:
|
||||
print "couldn't find the file "+instance.filename
|
||||
|
||||
# Date input utility
|
||||
class DateInput(forms.DateInput):
|
||||
input_type = 'date'
|
||||
|
||||
# Form to update Workout data
|
||||
class WorkoutForm(ModelForm):
|
||||
@@ -1327,7 +1600,7 @@ class RowerForm(ModelForm):
|
||||
# optionally sends a tweet to our twitter account
|
||||
class SiteAnnouncement(models.Model):
|
||||
created = models.DateField(default=timezone.now)
|
||||
announcement = models.TextField(max_length=140)
|
||||
announcement = models.TextField(max_length=280)
|
||||
expires = models.DateField(default=timezone.now)
|
||||
modified = models.DateField(default=timezone.now)
|
||||
dotweet = models.BooleanField(default=False)
|
||||
@@ -1371,4 +1644,4 @@ class WorkoutCommentForm(ModelForm):
|
||||
widgets = {
|
||||
'comment': forms.Textarea,
|
||||
}
|
||||
|
||||
|
||||
|
||||
233
rowers/plannedsessions.py
Normal file
233
rowers/plannedsessions.py
Normal file
@@ -0,0 +1,233 @@
|
||||
# Python
|
||||
from django.utils import timezone
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from datetime import date
|
||||
import time
|
||||
from django.db import IntegrityError
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
|
||||
from utils import myqueue
|
||||
|
||||
import django_rq
|
||||
queue = django_rq.get_queue('default')
|
||||
queuelow = django_rq.get_queue('low')
|
||||
queuehigh = django_rq.get_queue('low')
|
||||
|
||||
from rowers.models import (
|
||||
Rower, Workout,
|
||||
GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle,
|
||||
TrainingPlan,PlannedSession,
|
||||
)
|
||||
|
||||
import metrics
|
||||
import numpy as np
|
||||
import dataprep
|
||||
|
||||
# Low Level functions - to be called by higher level methods
|
||||
def add_workouts_plannedsession(ws,ps):
|
||||
result = 0
|
||||
comments = []
|
||||
errors = []
|
||||
|
||||
# check if all sessions have same date
|
||||
dates = [w.date for w in ws]
|
||||
if (not all(d == dates[0] for d in dates)) and ps.sessiontype != 'challenge':
|
||||
errors.append('For tests and training sessions, selected workouts must all be done on the same date')
|
||||
return result,comments,errors
|
||||
|
||||
# start adding sessions
|
||||
for w in ws:
|
||||
if w.date>=ps.startdate and w.date<=ps.enddate:
|
||||
w.plannedsession = ps
|
||||
w.save()
|
||||
result += 1
|
||||
comments.append('Attached workout %i to session' % w.id)
|
||||
else:
|
||||
errors.append('Workout %i did not match session dates' % w.id)
|
||||
|
||||
return result,comments,errors
|
||||
|
||||
|
||||
def remove_workout_plannedsession(w,ps):
|
||||
if w.plannedsession == ps:
|
||||
w.plannedsession = None
|
||||
w.save()
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
def clone_planned_session(ps):
|
||||
ps.save()
|
||||
ps.pk = None # creates new instance
|
||||
ps.save()
|
||||
|
||||
def timefield_to_seconds_duration(t):
|
||||
duration = t.hour*3600.
|
||||
duration += t.minute * 60.
|
||||
duration += t.second
|
||||
duration += t.microsecond/1.e6
|
||||
|
||||
return duration
|
||||
|
||||
def is_session_complete(r,ps):
|
||||
status = 'not done'
|
||||
|
||||
ws = Workout.objects.filter(user=r,plannedsession=ps)
|
||||
|
||||
if len(ws)==0:
|
||||
today = date.today()
|
||||
if today > ps.enddate:
|
||||
status = 'missed'
|
||||
ratio = 0
|
||||
return ratio,status
|
||||
else:
|
||||
return 0,'not done'
|
||||
|
||||
score = 0
|
||||
for w in ws:
|
||||
if ps.sessionmode == 'distance':
|
||||
score += w.distance
|
||||
elif ps.sessionmode == 'time':
|
||||
durationseconds = timefield_to_seconds_duration(w.duration)
|
||||
score += durationseconds
|
||||
elif ps.sessionmode == 'TRIMP':
|
||||
trimp = dataprep.workout_trimp(w)
|
||||
score += trimp
|
||||
elif ps.sessionmode == 'rScore':
|
||||
rscore = dataprep.workout_rscore(w)
|
||||
score += rscore
|
||||
|
||||
value = ps.sessionvalue
|
||||
if ps.sessionunit == 'min':
|
||||
value *= 60.
|
||||
elif ps.sessionunit == 'km':
|
||||
value *= 1000.
|
||||
|
||||
ratio = score/float(value)
|
||||
|
||||
status = 'partial'
|
||||
|
||||
if ps.sessiontype == 'session':
|
||||
if ps.criterium == 'exact':
|
||||
if ratio == 1.0:
|
||||
return ratio,'completed'
|
||||
else:
|
||||
return ratio,'partial'
|
||||
elif ps.criterium == 'minimum':
|
||||
if ratio > 1.0:
|
||||
return ratio,'completed'
|
||||
else:
|
||||
return ratio,'partial'
|
||||
else:
|
||||
if ratio>0.8 and ratio<1.2:
|
||||
return ratio,'completed'
|
||||
else:
|
||||
return ratio,'partial'
|
||||
elif ps.sessiontype == 'test':
|
||||
if ratio==1.0:
|
||||
return ratio,'completed'
|
||||
else:
|
||||
return ratio,'partial'
|
||||
elif ps.sessiontype == 'challenge':
|
||||
if ps.criterium == 'exact':
|
||||
if ratio == 1.0:
|
||||
return ratio,'completed'
|
||||
else:
|
||||
return ratio,'partial'
|
||||
elif ps.criterium == 'minimum':
|
||||
if ratio > 1.0:
|
||||
return ratio,'completed'
|
||||
else:
|
||||
return ratio,'partial'
|
||||
else:
|
||||
return ratio,'partial'
|
||||
|
||||
else:
|
||||
return ratio,status
|
||||
|
||||
def rank_results(ps):
|
||||
return 1
|
||||
|
||||
def add_team_session(t,ps):
|
||||
ps.team.add(t)
|
||||
ps.save()
|
||||
|
||||
return 1
|
||||
|
||||
def add_rower_session(r,ps):
|
||||
ps.rower.add(r)
|
||||
ps.save()
|
||||
|
||||
return 1
|
||||
|
||||
def remove_team_session(t,ps):
|
||||
ps.team.remove(t)
|
||||
|
||||
return 1
|
||||
|
||||
def remove_rower_session(r,ps):
|
||||
ps.rower.remove(r)
|
||||
|
||||
return 1
|
||||
|
||||
def get_dates_timeperiod(timeperiod):
|
||||
# set start end date according timeperiod
|
||||
if timeperiod=='today':
|
||||
startdate=date.today()
|
||||
enddate=date.today()
|
||||
elif timeperiod=='tomorrow':
|
||||
startdate=date.today()+timezone.timedelta(days=1)
|
||||
enddate=date.today()+timezone.timedelta(days=1)
|
||||
elif timeperiod=='thisweek':
|
||||
today = date.today()
|
||||
startdate = date.today()-timezone.timedelta(days=today.weekday())
|
||||
enddate = startdate+timezone.timedelta(days=6)
|
||||
elif timeperiod=='thismonth':
|
||||
today = date.today()
|
||||
startdate = today.replace(day=1)
|
||||
enddate = startdate+timezone.timedelta(days=32)
|
||||
enddate = enddate.replace(day=1)
|
||||
enddate = enddate-timezone.timedelta(days=1)
|
||||
elif timeperiod=='lastweek':
|
||||
today = date.today()
|
||||
enddate = today-timezone.timedelta(days=today.weekday())-timezone.timedelta(days=1)
|
||||
startdate = enddate-timezone.timedelta(days=6)
|
||||
elif timeperiod=='lastmonth':
|
||||
today = date.today()
|
||||
startdate = today.replace(day=1)
|
||||
startdate = startdate-timezone.timedelta(days=3)
|
||||
startdate = startdate.replace(day=1)
|
||||
enddate = startdate+timezone.timedelta(days=32)
|
||||
enddate = enddate.replace(day=1)
|
||||
enddate = enddate-timezone.timedelta(days=1)
|
||||
else:
|
||||
startdate = date.today()
|
||||
enddate = date.today()
|
||||
|
||||
return startdate,enddate
|
||||
|
||||
def get_sessions(r,startdate=date.today(),
|
||||
enddate=date.today()+timezone.timedelta(+1000)):
|
||||
|
||||
sps = PlannedSession.objects.filter(
|
||||
rower__in=[r],
|
||||
startdate__lte=enddate,
|
||||
enddate__gte=startdate,
|
||||
).order_by("startdate","enddate")
|
||||
|
||||
return sps
|
||||
|
||||
def get_workouts_session(r,ps):
|
||||
ws = Workout.objects.filter(user=r,plannedsession=ps)
|
||||
|
||||
return ws
|
||||
|
||||
def update_plannedsession(ps,cd):
|
||||
for attr, value in cd.items():
|
||||
setattr(ps, attr, value)
|
||||
|
||||
ps.save()
|
||||
|
||||
return 1,'Planned Session Updated'
|
||||
@@ -90,15 +90,9 @@ def handle_uploaded_image(i):
|
||||
break
|
||||
exif=dict(image._getexif().items())
|
||||
|
||||
if exif[orientation] == 3:
|
||||
image=image.rotate(180, expand=True)
|
||||
elif exif[orientation] == 6:
|
||||
image=image.rotate(270, expand=True)
|
||||
elif exif[orientation] == 8:
|
||||
image=image.rotate(90, expand=True)
|
||||
except (AttributeError, KeyError, IndexError):
|
||||
# cases: image don't have getexif
|
||||
pass
|
||||
exif = {'orientation':0}
|
||||
|
||||
if image.mode not in ("L", "RGB"):
|
||||
image = image.convert("RGB")
|
||||
@@ -108,6 +102,12 @@ def handle_uploaded_image(i):
|
||||
hsize = int((float(image.size[1])*float(wpercent)))
|
||||
image = image.resize((basewidth,hsize), Image.ANTIALIAS)
|
||||
|
||||
if exif[orientation] == 3:
|
||||
image=image.rotate(180, expand=True)
|
||||
elif exif[orientation] == 6:
|
||||
image=image.rotate(270, expand=True)
|
||||
elif exif[orientation] == 8:
|
||||
image=image.rotate(90, expand=True)
|
||||
|
||||
filename = hashlib.md5(imagefile.getvalue()).hexdigest()+'.jpg'
|
||||
|
||||
|
||||
@@ -185,7 +185,8 @@ def get_strava_workout(user,stravaid):
|
||||
if nr_rows == 0:
|
||||
return (0,"Error: Time data had zero length")
|
||||
except IndexError:
|
||||
return (0,"Error: No Distance information in the Strava data")
|
||||
d = 0*t
|
||||
# return (0,"Error: No Distance information in the Strava data")
|
||||
except KeyError:
|
||||
return (0,"something went wrong with the Strava import")
|
||||
|
||||
|
||||
@@ -40,4 +40,4 @@
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
|
||||
<div class="grid_12">
|
||||
|
||||
Select start and end date for a date range:
|
||||
<div class="grid_4 alpha">
|
||||
|
||||
{% if team %}
|
||||
@@ -64,9 +63,25 @@
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
</div>
|
||||
<div class="grid_2 suffix_6 alpha">
|
||||
<div class="grid_2">
|
||||
<input name='daterange' class="button green" type="submit" value="Submit"> </form>
|
||||
</div>
|
||||
{% if user.is_authenticated and user|is_manager %}
|
||||
<div class="grid_2 omega dropdown">
|
||||
<button class="grid_2 alpha button green small dropbtn">
|
||||
Change Rower
|
||||
</button>
|
||||
<div class="dropdown-content">
|
||||
{% for member in user|team_members %}
|
||||
<a class="button green small" href="/rowers/u/{{ member.id }}/list-workouts/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}">{{ member.first_name }} {{ member.last_name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -77,11 +92,12 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="grid_12">
|
||||
|
||||
<div id="workouts_table" class="grid_8 alpha">
|
||||
{% if team %}
|
||||
<h3>{{ team.name }} Team Workouts</h3>
|
||||
{% else %}
|
||||
<h3>My Workouts</h3>
|
||||
<h3>Workouts of {{ rower.user.first_name }} {{ rower.user.last_name }}</h3>
|
||||
{% endif %}
|
||||
|
||||
{% if workouts %}
|
||||
@@ -154,7 +170,10 @@
|
||||
</td>
|
||||
{% else %}
|
||||
<td colspan="2">
|
||||
{{ workout.user.user.first_name }} {{ workout.user.user.last_name }}
|
||||
<a class="small" href="/rowers/{{ workout.user.id }}/list-workouts">
|
||||
{{ workout.user.user.first_name }}
|
||||
{{ workout.user.user.last_name }}
|
||||
</a>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td> <a class="small" href="/rowers/workout/{{ workout.id }}/flexchart">Flex</a> </td>
|
||||
|
||||
57
rowers/templates/planmembership.html
Normal file
57
rowers/templates/planmembership.html
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
{% extends "base.html" %}
|
||||
{% block title %}About us{% endblock title %}
|
||||
{% block content %}
|
||||
|
||||
<div class="grid_6 alpha">
|
||||
<h2>Coach and Self-Coach Membership</h2>
|
||||
|
||||
<p>You have arrived at this page, because you tried to create a
|
||||
training plan for yourself.</p>
|
||||
|
||||
<p>Currently, training planning is restricted to "coach" members with
|
||||
"team" functionality.</p>
|
||||
|
||||
<p>If you are interested in becoming a coach and planning sessions
|
||||
for a group of rowers on rowsandall.com, contact me through the contact
|
||||
form.
|
||||
</p>
|
||||
|
||||
<p>If you would like to find a coach who helps you plan your training
|
||||
through rowsandall.com, contact me throught the contact form.</p>
|
||||
|
||||
<p>For self-coached rowers who would like to add the training planning
|
||||
functionality, we will soon establish a "Self-Coach" plan, which will enable you to do so.</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="grid_6 omega">
|
||||
<h2>What training planning functionality do we offer?</h2>
|
||||
|
||||
<p>Over the spring of 2018, we will gradually expand this functionality.
|
||||
Our current roadmap is to deploy the following and more:</li>
|
||||
|
||||
<p>
|
||||
<ul>
|
||||
<li>Create Planned Sessions (trainings, tests, challenges) for yourself
|
||||
and for your team members (coach plan)</li>
|
||||
<li>Track your performance against plan.
|
||||
Match workouts to planned sessions.
|
||||
Get feedback on plan adherence.</li>
|
||||
<li>Track your teams performance against plan. See how well each
|
||||
of your team members adhere to their (team or personalized) plan.</li>
|
||||
<li>See test outcomes ranked by performance.</li>
|
||||
<li>Attach courses to your OTW tests. This advanced functionality
|
||||
allows you, for example, to assign "Row the 6km from bridge A to
|
||||
bridge B on Saturday" to your team members. The resulting workout
|
||||
tracks will be evaluated against the course, and you will receive
|
||||
a results table for the net time spent between the start and finish
|
||||
points on the course. It's like a mini head race.
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
112
rowers/templates/plannedsessioncreate.html
Normal file
112
rowers/templates/plannedsessioncreate.html
Normal file
@@ -0,0 +1,112 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}New Planned Session{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid_12 alpha">
|
||||
{% include "planningbuttons.html" %}
|
||||
|
||||
</div>
|
||||
<div class="grid_12 alpha">
|
||||
<div id="left" class="grid_6 alpha">
|
||||
<h1>Create Session for {{ rower.user.first_name }} {{ rower.user.last_name }}</h1>
|
||||
</div>
|
||||
<div id="timeperiod" class="grid_2 dropdown">
|
||||
<button class="grid_2 alpha button gray small dropbtn">Time Period</button>
|
||||
<div class="dropdown-content">
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/create/today/rower/{{ rower.id }}">
|
||||
Today
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/create/thisweek/rower/{{ rower.id }}">
|
||||
This Week
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/create/thismonth/rower/{{ rower.id }}">
|
||||
This Month
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/create/lastweek/rower/{{ rower.id }}">
|
||||
Last Week
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/create/lastmonth/rower/{{ rower.id }}">
|
||||
Last Month
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid_12 alpha">
|
||||
<div class="grid_6 alpha">
|
||||
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
|
||||
{% 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>
|
||||
<div id="right" class="grid_6 omega">
|
||||
<h1>Plan</h1>
|
||||
<p>
|
||||
Click on session name to view
|
||||
</p>
|
||||
<table class="listtable shortpadded" width="80%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>After</th>
|
||||
<th>Before</th>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
<th> </th>
|
||||
<th>Edit</th>
|
||||
<th>Clone</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ps in plannedsessions %}
|
||||
<tr>
|
||||
<td> {{ ps.startdate|date:"Y-m-d" }} </td>
|
||||
<td> {{ ps.enddate|date:"Y-m-d" }} </td>
|
||||
<td>
|
||||
{% if ps.name != '' %}
|
||||
<a class="small"
|
||||
href="/rowers/sessions/{{ ps.id }}">{{ ps.name }}</a>
|
||||
{% else %}
|
||||
<a class="small"
|
||||
href="/rowers/sessions/{{ ps.id }}">Unnamed Session</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td> {{ ps.sessionvalue }} </td>
|
||||
<td> {{ ps.sessionunit }} </td>
|
||||
<td>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/edit">Edit</a>
|
||||
</td>
|
||||
<td>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/clone">Clone</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/deleteconfirm">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
45
rowers/templates/plannedsessiondeleteconfirm.html
Normal file
45
rowers/templates/plannedsessiondeleteconfirm.html
Normal file
@@ -0,0 +1,45 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Planned Session{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid_12 alpha">
|
||||
{% include "planningbuttons.html" %}
|
||||
|
||||
</div>
|
||||
<div id="left" class="grid_6 alpha">
|
||||
<h1>Confirm Delete</h1>
|
||||
<p>This will permanently delete the planned session</p>
|
||||
|
||||
|
||||
<div class="grid_2 alpha">
|
||||
<p>
|
||||
<a class="button green small" href="/rowers/sessions/">Cancel</a>
|
||||
</div>
|
||||
|
||||
<div class="grid_2">
|
||||
<p>
|
||||
<a class="button red small" href="/rowers/sessions/{{ ps.id }}/delete">Delete</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="right" class="grid_6 omega">
|
||||
<h1>Session {{ psdict.name.1 }}</h1>
|
||||
<table class="listtable shortpadded">
|
||||
{% for attr in attrs %}
|
||||
{% for key,value in psdict.items %}
|
||||
{% if key == attr %}
|
||||
<tr>
|
||||
<td><b>{{ value.0 }}</b></td><td>{{ value.1 }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
123
rowers/templates/plannedsessionedit.html
Normal file
123
rowers/templates/plannedsessionedit.html
Normal file
@@ -0,0 +1,123 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Update Planned Session{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid_12 alpha">
|
||||
{% include "planningbuttons.html" %}
|
||||
|
||||
</div>
|
||||
<div class="grid_12 alpha">
|
||||
<div id="left" class="grid_6 alpha">
|
||||
<h1>Edit Session {{ thesession.name }}</h1>
|
||||
</div>
|
||||
<div id="timeperiod" class="grid_2 dropdown">
|
||||
<button class="grid_2 alpha button gray small dropbtn">Time Period</button>
|
||||
<div class="dropdown-content">
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/create/today/rower/{{ rower.id }}">
|
||||
Today
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/create/thisweek/rower/{{ rower.id }}">
|
||||
This Week
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/create/thismonth/rower/{{ rower.id }}">
|
||||
This Month
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/create/lastweek/rower/{{ rower.id }}">
|
||||
Last Week
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/create/lastmonth/rower/{{ rower.id }}">
|
||||
Last Month
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid_12 alpha">
|
||||
<div class="grid_6 alpha">
|
||||
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
|
||||
{% 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 class="grid_1 prefix_2 alpha">
|
||||
<a class="red button small" href="/rowers/sessions/{{ thesession.id }}/deleteconfirm">Delete</a>
|
||||
</div>
|
||||
<div class="grid_1">
|
||||
<a class="gray button small" href="/rowers/sessions/{{ thesession.id }}/clone">Clone</a>
|
||||
</div>
|
||||
<div id="formbutton" class="grid_1 suffix_1 omega">
|
||||
<input class="button green" type="submit" value="Save">
|
||||
</div>
|
||||
</div>
|
||||
<div id="right" class="grid_6 omega">
|
||||
<h1>Plan</h1>
|
||||
<p>
|
||||
Click on session name to view
|
||||
</p>
|
||||
<table class="listtable shortpadded" width="80%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>After</th>
|
||||
<th>Before</th>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
<th> </th>
|
||||
<th>Edit</th>
|
||||
<th>Clone</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ps in plannedsessions %}
|
||||
<tr>
|
||||
<td> {{ ps.startdate|date:"Y-m-d" }} </td>
|
||||
<td> {{ ps.enddate|date:"Y-m-d" }} </td>
|
||||
<td>
|
||||
{% if ps.name != '' %}
|
||||
<a class="small"
|
||||
href="/rowers/sessions/{{ ps.id }}">{{ ps.name }}</a>
|
||||
{% else %}
|
||||
<a class="small"
|
||||
href="/rowers/sessions/{{ ps.id }}">Unnamed Session</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td> {{ ps.sessionvalue }} </td>
|
||||
<td> {{ ps.sessionunit }} </td>
|
||||
<td>
|
||||
{% if timeperiod and rower %}
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/edit/{{ timeperiod }}/rower/{{ rower.id }}">Edit</a>
|
||||
{% elif timeperiod %}
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/edit/{{ timeperiod }}">Edit</a>
|
||||
{% else %}
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/edit">Edit</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/clone">Clone</a>
|
||||
</td>
|
||||
<td>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/deleteconfirm">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
109
rowers/templates/plannedsessions.html
Normal file
109
rowers/templates/plannedsessions.html
Normal file
@@ -0,0 +1,109 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Planned Sessions{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid_12 alpha">
|
||||
{% include "planningbuttons.html" %}
|
||||
</div>
|
||||
<div class="grid_6 alpha">
|
||||
<h1>Plan for {{ rower.user.first_name }} {{ rower.user.last_name }}</h1>
|
||||
</div>
|
||||
<div id="timeperiod" class="grid_2 dropdown">
|
||||
<button class="grid_2 alpha button gray small dropbtn">Time Period</button>
|
||||
<div class="dropdown-content">
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/today/rower/{{ rower.id }}">
|
||||
Today
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/thisweek/rower/{{ rower.id }}">
|
||||
This Week
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/thismonth/rower/{{ rower.id }}">
|
||||
This Month
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/lastweek/rower/{{ rower.id }}">
|
||||
Last Week
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/lastmonth/rower/{{ rower.id }}">
|
||||
Last Month
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid_12 alpha">
|
||||
{% if plannedsessions %}
|
||||
<p>
|
||||
Click on session name to view, edit to change the session and on the
|
||||
traffic light symbol to add workouts to the session
|
||||
</p>
|
||||
<table width="80%" class="listtable shortpadded">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>After</th>
|
||||
<th>Before</th>
|
||||
<th>Name</th>
|
||||
<th>Edit</th>
|
||||
<th>Value</th>
|
||||
<th> </th>
|
||||
<th>Type</th>
|
||||
<th>Status</th>
|
||||
<th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ps in plannedsessions %}
|
||||
<tr>
|
||||
<td> {{ ps.startdate|date:"Y-m-d" }} </td>
|
||||
<td> {{ ps.enddate|date:"Y-m-d" }} </td>
|
||||
<td>
|
||||
{% if ps.name != '' %}
|
||||
<a class="small"
|
||||
href="/rowers/sessions/{{ ps.id }}">{{ ps.name }}</a>
|
||||
{% else %}
|
||||
<a class="small"
|
||||
href="/rowers/sessions/{{ ps.id }}">Unnamed Session</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if ps.manager == request.user %}
|
||||
<a class="small"
|
||||
href="/rowers/sessions/{{ ps.id }}/edit">Edit</a>
|
||||
{% else %}
|
||||
|
||||
{% endif %}
|
||||
</td>
|
||||
<td> {{ ps.sessionvalue }} </td>
|
||||
<td> {{ ps.sessionunit }} </td>
|
||||
<td> {{ ps.sessiontype }} </td>
|
||||
<td>
|
||||
{% if completeness|lookup:ps.id == 'not done' %}
|
||||
<a class="white dot" href="/rowers/sessions/manage/{{ timeperiod }}/rower/{{ rower.id }}/session/{{ ps.id }}"> </a>
|
||||
{% elif completeness|lookup:ps.id == 'completed' %}
|
||||
<a class="green dot" href="/rowers/sessions/manage/{{ timeperiod }}/rower/{{ rower.id }}/session/{{ ps.id }}"> </a>
|
||||
{% elif completeness|lookup:ps.id == 'partial' %}
|
||||
<a class="orange dot" href="/rowers/sessions/manage/{{ timeperiod }}/rower/{{ rower.id }}/session/{{ ps.id }}"> </a>
|
||||
{% else %}
|
||||
<a class="red dot" href="/rowers/sessions/manage/{{ timeperiod }}/rower/{{ rower.id }}/session/{{ ps.id }}"> </a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
You have no planned workouts for this period. Planned workouts are created
|
||||
by your coach if you are part of a team. You can create your own
|
||||
planned workouts by purchasing the "Coach" or "Self-Coach" plans.
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
121
rowers/templates/plannedsessionsmanage.html
Normal file
121
rowers/templates/plannedsessionsmanage.html
Normal file
@@ -0,0 +1,121 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Planned Sessions{% endblock %}
|
||||
|
||||
{% block meta %}
|
||||
<script type='text/javascript'
|
||||
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
|
||||
</script>
|
||||
<script type='text/javascript'
|
||||
src='https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js'>
|
||||
</script>
|
||||
<script>
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="grid_12 alpha">
|
||||
{% include "planningbuttons.html" %}
|
||||
</div>
|
||||
<div class="grid_6 alpha">
|
||||
<h1>Manage Plan Execution for {{ rower.user.first_name }} {{ rower.user.last_name }}</h1>
|
||||
</div>
|
||||
|
||||
<div id="timeperiod" class="grid_2 dropdown">
|
||||
<button class="grid_2 alpha button gray small dropbtn">Time Period</button>
|
||||
<div class="dropdown-content">
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/manage/today/rower/{{ rower.id }}">
|
||||
Today
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/manage/thisweek/rower/{{ rower.id }}">
|
||||
This Week
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/manage/thismonth/rower/{{ rower.id }}">
|
||||
This Month
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/manage/lastweek/rower/{{ rower.id }}">
|
||||
Last Week
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/manage/lastmonth/rower/{{ rower.id }}">
|
||||
Last Month
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid_12 alpha">
|
||||
<p>Select one session on the left, and one or more workouts on the right
|
||||
to match the workouts to the session. For tests and training sessions,
|
||||
the selected workouts must be done on the same date. For all sessions,
|
||||
the workout dates must be between the start and end date for the
|
||||
session.
|
||||
</p>
|
||||
<p>
|
||||
If you select a workout that has already been matched to another session,
|
||||
it will change to match this session.
|
||||
</p>
|
||||
<p>
|
||||
We will make this form smarter in the near future.
|
||||
</p>
|
||||
</div>
|
||||
<form id="session_form" action="/rowers/sessions/manage/{{ timeperiod }}/rower/{{ rower.id }}"
|
||||
method="post">
|
||||
<div class="grid_12 alpha">
|
||||
<div class="grid_6 alpha">
|
||||
{{ ps_form.as_table}}
|
||||
</div>
|
||||
<div class="grid_6 omega">
|
||||
{{ w_form.as_table}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid_2 prefix_2 suffix_8">
|
||||
{% csrf_token %}
|
||||
<input class="button green" type="submit" value="Submit">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('#id_plannedsession').on('click', function(evt) {
|
||||
var selectedsession = $("input:radio[name='plannedsession']:checked").val();
|
||||
|
||||
var url = window.location.pathname;
|
||||
if (url.indexOf("/session/") >= 0) {
|
||||
url = url.replace(/\/session\/\d+/g, "/session/"+selectedsession);
|
||||
} else {
|
||||
url += "/session/"+selectedsession
|
||||
}
|
||||
// window.location.replace(url);
|
||||
$.getJSON(url, function(json) {
|
||||
var workouts = json['workouts'];
|
||||
for (i=0; i < workouts.length; i++) {
|
||||
var wid = workouts[i][0];
|
||||
var wcheck = workouts[i][2];
|
||||
if (wcheck) {
|
||||
$(":checkbox").filter(function() {
|
||||
return this.value == wid;
|
||||
}).prop("checked",true);
|
||||
} else {
|
||||
$(":checkbox").filter(function() {
|
||||
return this.value == wid;
|
||||
}).prop("checked",false);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
59
rowers/templates/plannedsessionview.html
Normal file
59
rowers/templates/plannedsessionview.html
Normal file
@@ -0,0 +1,59 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Planned Session{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid_12 alpha">
|
||||
{% include "planningbuttons.html" %}
|
||||
|
||||
</div>
|
||||
<div id="left" class="grid_6 alpha">
|
||||
<h1>Session {{ psdict.name.1 }}</h1>
|
||||
<table class="listtable shortpadded" width="80%">
|
||||
{% for attr in attrs %}
|
||||
{% for key,value in psdict.items %}
|
||||
{% if key == attr %}
|
||||
<tr>
|
||||
<td><b>{{ value.0 }}</b></td><td>{{ value.1 }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
<h1>Result</h1>
|
||||
<p>Status: {{ status }}</p>
|
||||
<p>Percentage complete: {{ ratio }} </p>
|
||||
</div>
|
||||
<div id="right" class="grid_6 omega">
|
||||
<h1>Workouts attached</h1>
|
||||
<table class="listtable shortpadded" width="80%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Name</th>
|
||||
<th>Distance</th>
|
||||
<th>Duration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for workout in workouts %}
|
||||
<tr>
|
||||
<td> {{ workout.date|date:"Y-m-d" }} </td>
|
||||
<td>
|
||||
<a href={% url manager.defaultlandingpage id=workout.id %}>
|
||||
{{ workout.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td> {{ workout.distance }}m</td>
|
||||
<td> {{ workout.duration |durationprint:"%H:%M:%S.%f" }} </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
48
rowers/templates/plannedsssionsmanage.html
Normal file
48
rowers/templates/plannedsssionsmanage.html
Normal file
@@ -0,0 +1,48 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Planned Sessions{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid_12 alpha">
|
||||
{% include "planningbuttons.html" %}
|
||||
</div>
|
||||
<div class="grid_6 alpha">
|
||||
<h1>Plan for {{ rower.user.first_name }} {{ rower.user.last_name }}</h1>
|
||||
</div>
|
||||
<div id="timeperiod" class="grid_2 dropdown">
|
||||
<button class="grid_2 alpha button gray small dropbtn">Time Period</button>
|
||||
<div class="dropdown-content">
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/today/rower/{{ rower.id }}">
|
||||
Today
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/thisweek/rower/{{ rower.id }}">
|
||||
This Week
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/thismonth/rower/{{ rower.id }}">
|
||||
This Month
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/lastweek/rower/{{ rower.id }}">
|
||||
Last Week
|
||||
</a>
|
||||
<a class="button gray small alpha"
|
||||
href="/rowers/sessions/lastmonth/rower/{{ rower.id }}">
|
||||
Last Month
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid_12 alpha">
|
||||
<p>
|
||||
Click on session name to view
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
28
rowers/templates/planningbuttons.html
Normal file
28
rowers/templates/planningbuttons.html
Normal file
@@ -0,0 +1,28 @@
|
||||
{% load rowerfilters %}
|
||||
<div class="grid_2 alpha">
|
||||
<p>
|
||||
{% if timeperiod and rower %}
|
||||
<a class="button gray small" href="/rowers/sessions/{{ timeperiod }}/rower/{{ rower.id }}">Plan Overview</a>
|
||||
{% elif timeperiod %}
|
||||
<a class="button gray small" href="/rowers/sessions/{{ timeperiod }}">Plan Overview</a>
|
||||
{% else %}
|
||||
<a class="button gray small" href="/rowers/sessions">Plan Overview</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
<p>
|
||||
{% if timeperiod and rower %}
|
||||
<a class="button gray small" href="/rowers/sessions/manage/{{ timeperiod }}/rower/{{ rower.id }}">Manage Sessions</a>
|
||||
{% elif timeperiod %}
|
||||
<a class="button gray small" href="/rowers/sessions/manage/{{ timeperiod }}">Manage Sessions</a>
|
||||
{% else %}
|
||||
<a class="button gray small" href="/rowers/sessions/manage">Manage Sessions</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_2 suffix_6 omega">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/sessions/create">Add Session</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -2,9 +2,9 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}About us{% endblock title %}
|
||||
{% block content %}
|
||||
<h2>Pro Membership</h2>
|
||||
|
||||
<div class="grid_6 alpha">
|
||||
<h2>Pro Membership</h2>
|
||||
|
||||
<p>Donations are welcome to keep this web site going. To help cover the hosting
|
||||
costs, I have created a <q>Pro</q> membership option (for only 15 EURO per year). Once I process your
|
||||
@@ -27,7 +27,7 @@ You will be taken to the secure PayPal payment site.
|
||||
</div>
|
||||
|
||||
<div class="grid_6 omega">
|
||||
<h3>Recurring Payment</h2>
|
||||
<h2>Recurring Payment</h2>
|
||||
<p>You need a Paypal account for this</p>
|
||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
|
||||
<input type="hidden" name="cmd" value="_s-xclick">
|
||||
@@ -40,7 +40,7 @@ You will be taken to the secure PayPal payment site.
|
||||
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
|
||||
</form>
|
||||
|
||||
<h3>One Year Subscription</h3>
|
||||
<h2>One Year Subscription</h2>
|
||||
<p>Only a credit card needed. Will not automatically renew</p>
|
||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
|
||||
<input type="hidden" name="cmd" value="_s-xclick">
|
||||
@@ -49,7 +49,7 @@ You will be taken to the secure PayPal payment site.
|
||||
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
|
||||
</form>
|
||||
|
||||
<h3>Payment Processing</h3>
|
||||
<h2>Payment Processing</h2>
|
||||
<p>After you do the payment, we will manually change your membership to
|
||||
"Pro". Depending on our availability, this may take some time
|
||||
(typically one working day). Don't hesitate to contact us
|
||||
|
||||
@@ -151,3 +151,4 @@ def team_members(user):
|
||||
return []
|
||||
|
||||
return []
|
||||
|
||||
|
||||
@@ -137,6 +137,10 @@ urlpatterns = [
|
||||
url(r'^list-workouts/ranking$',views.workouts_view,{'rankingonly':True}),
|
||||
url(r'^list-workouts/team/(?P<teamid>\d+)/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view),
|
||||
url(r'^list-workouts/team/(?P<teamid>\d+)/$',views.workouts_view),
|
||||
url(r'^(?P<rowerid>\d+)/list-workouts/$',views.workouts_view),
|
||||
url(r'^(?P<rowerid>\d+)/list-workouts/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view),
|
||||
url(r'^u/(?P<userid>\d+)/list-workouts/$',views.workouts_view),
|
||||
url(r'^u/(?P<userid>\d+)/list-workouts/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view),
|
||||
url(r'^list-workouts/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view),
|
||||
url(r'^list-workouts/$',views.workouts_view),
|
||||
url(r'^addmanual/$',views.addmanual_view),
|
||||
@@ -378,6 +382,7 @@ urlpatterns = [
|
||||
url(r'^videos', TemplateView.as_view(template_name='videos.html'),name='videos'),
|
||||
url(r'^analysis', TemplateView.as_view(template_name='analysis.html'),name='analysis'),
|
||||
url(r'^promembership', TemplateView.as_view(template_name='promembership.html'),name='promembership'),
|
||||
url(r'^planmembership', TemplateView.as_view(template_name='planmembership.html'),name='planmembership'),
|
||||
url(r'^paypaltest', TemplateView.as_view(template_name='paypaltest.html'),name='paypaltest'),
|
||||
url(r'^legal', TemplateView.as_view(template_name='legal.html'),name='legal'),
|
||||
url(r'^register$',views.rower_register_view),
|
||||
@@ -392,6 +397,48 @@ urlpatterns = [
|
||||
url(r'^workout/compare/(?P<id1>\d+)/(?P<id2>\d+)/(?P<xparam>\w+.*)/(?P<yparam>[\w\ ]+.*)/$',views.workout_comparison_view2),
|
||||
url(r'^test\_callback',views.rower_process_testcallback),
|
||||
url(r'^workout/(?P<id>\d+)/test\_strokedata$',views.strokedataform),
|
||||
url(r'^sessions/create$',views.plannedsession_create_view),
|
||||
url(r'^sessions/create/rower/(?P<rowerid>\d+)$',
|
||||
views.plannedsession_create_view),
|
||||
url(
|
||||
r'^sessions/create/(?P<timeperiod>[\w\ ]+.*)/rower/(?P<rowerid>\d+)$',
|
||||
views.plannedsession_create_view),
|
||||
url(r'^sessions/create/(?P<timeperiod>[\w\ ]+.*)$',
|
||||
views.plannedsession_create_view),
|
||||
|
||||
url(r'^sessions/(?P<id>\d+)/edit$',views.plannedsession_edit_view),
|
||||
url(r'^sessions/(?P<id>\d+)/edit/(?P<timeperiod>[\w\ ]+.*)/rower/(?P<rowerid>\d+)$',views.plannedsession_edit_view),
|
||||
url(r'^sessions/(?P<id>\d+)/edit/(?P<timeperiod>[\w\ ]+.*)$',views.plannedsession_edit_view),
|
||||
|
||||
url(r'^sessions/(?P<id>\d+)/clone$',views.plannedsession_clone_view),
|
||||
url(r'^sessions/(?P<id>\d+)/clone/(?P<timeperiod>[\w\ ]+.*)/rower/(?P<rowerid>\d+)$',views.plannedsession_clone_view),
|
||||
url(r'^sessions/(?P<id>\d+)/clone/(?P<timeperiod>[\w\ ]+.*)$',views.plannedsession_clone_view),
|
||||
|
||||
url(r'^sessions/(?P<id>\d+)$',views.plannedsession_view),
|
||||
url(r'^sessions/(?P<id>\d+)/deleteconfirm$',views.plannedsession_deleteconfirm_view),
|
||||
url(r'^sessions/(?P<id>\d+)/delete$',views.plannedsession_delete_view),
|
||||
url(r'^sessions/manage/session/(?P<initialsession>\d+)$',
|
||||
views.plannedsessions_manage_view),
|
||||
url(r'^sessions/manage/rower/(?P<rowerid>\d+)/session/(?P<initialsession>\d+)$',
|
||||
views.plannedsessions_manage_view),
|
||||
url(r'^sessions/manage/(?P<timeperiod>[\w\ ]+.*)/rower/(?P<rowerid>\d+)/session/(?P<initialsession>\d+)$',
|
||||
views.plannedsessions_manage_view),
|
||||
url(r'^sessions/manage/(?P<timeperiod>[\w\ ]+.*)/session/(?P<initialsession>\d+)$',
|
||||
views.plannedsessions_manage_view),
|
||||
|
||||
|
||||
url(r'^sessions/manage/?$',
|
||||
views.plannedsessions_manage_view),
|
||||
url(r'^sessions/manage/rower/(?P<rowerid>\d+)$',
|
||||
views.plannedsessions_manage_view),
|
||||
url(r'^sessions/manage/(?P<timeperiod>[\w\ ]+.*)/rower/(?P<rowerid>\d+)$',
|
||||
views.plannedsessions_manage_view),
|
||||
url(r'^sessions/manage/(?P<timeperiod>[\w\ ]+.*)$',
|
||||
views.plannedsessions_manage_view),
|
||||
url(r'^sessions/?$',views.plannedsessions_view),
|
||||
url(r'^sessions/rower/(?P<rowerid>\d+)$',views.plannedsessions_view),
|
||||
url(r'^sessions/(?P<timeperiod>[\w\ ]+.*)/rower/(?P<rowerid>\d+)$',views.plannedsessions_view),
|
||||
url(r'^sessions/(?P<timeperiod>[\w\ ]+.*)$',views.plannedsessions_view),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
||||
@@ -291,3 +291,16 @@ from datetime import date
|
||||
def calculate_age(born):
|
||||
today = date.today()
|
||||
return today.year - born.year - ((today.month, today.day) < (born.month, born.day))
|
||||
|
||||
def my_dict_from_instance(instance,model):
|
||||
thedict = {}
|
||||
|
||||
for attr, value in instance.__dict__.iteritems():
|
||||
try:
|
||||
verbose_name = model._meta.get_field(attr).verbose_name
|
||||
except:
|
||||
verbose_name = attr
|
||||
|
||||
thedict[attr] = (verbose_name,value)
|
||||
|
||||
return thedict
|
||||
|
||||
484
rowers/views.py
484
rowers/views.py
@@ -30,7 +30,7 @@ from rowers.forms import (
|
||||
LoginForm,DocumentsForm,UploadOptionsForm,ImageForm,
|
||||
TeamUploadOptionsForm,WorkFlowLeftPanelForm,WorkFlowMiddlePanelForm,
|
||||
WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement,
|
||||
LandingPageForm,
|
||||
LandingPageForm,PlannedSessionSelectForm,WorkoutSessionSelectForm
|
||||
)
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.exceptions import PermissionDenied
|
||||
@@ -50,13 +50,16 @@ from rowers.forms import (
|
||||
FusionMetricChoiceForm,BoxPlotChoiceForm,MultiFlexChoiceForm,
|
||||
TrendFlexModalForm,WorkoutSplitForm,WorkoutJoinParamForm,
|
||||
)
|
||||
from rowers.models import Workout, User, Rower, WorkoutForm,FavoriteChart
|
||||
from rowers.models import (
|
||||
Workout, User, Rower, WorkoutForm,FavoriteChart,
|
||||
PlannedSession
|
||||
)
|
||||
from rowers.models import (
|
||||
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm,
|
||||
RowerPowerZonesForm,AccountRowerForm,UserForm,StrokeData,
|
||||
Team,TeamForm,TeamInviteForm,TeamInvite,TeamRequest,
|
||||
WorkoutComment,WorkoutCommentForm,RowerExportForm,
|
||||
CalcAgePerformance,PowerTimeFitnessMetric,
|
||||
CalcAgePerformance,PowerTimeFitnessMetric,PlannedSessionForm
|
||||
)
|
||||
from rowers.models import FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement
|
||||
from rowers.metrics import rowingmetrics,defaultfavoritecharts
|
||||
@@ -104,6 +107,7 @@ import json
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from rest_framework.parsers import JSONParser
|
||||
from rowers.rows import handle_uploaded_file,handle_uploaded_image
|
||||
from rowers.plannedsessions import *
|
||||
from rowers.tasks import handle_makeplot,handle_otwsetpower,handle_sendemailtcx,handle_sendemailcsv
|
||||
from rowers.tasks import (
|
||||
handle_sendemail_unrecognized,handle_sendemailnewcomment,
|
||||
@@ -602,6 +606,7 @@ def rowhascoordinates(row):
|
||||
if rowdata != 0:
|
||||
try:
|
||||
latitude = rowdata.df[' latitude']
|
||||
|
||||
if not latitude.std():
|
||||
hascoordinates = 0
|
||||
except KeyError,AttributeError:
|
||||
@@ -737,12 +742,12 @@ from utils import (
|
||||
geo_distance,serialize_list,deserialize_list,uniqify,
|
||||
str2bool,range_to_color_hex,absolute,myqueue,get_call,
|
||||
calculate_age,rankingdistances,rankingdurations,
|
||||
is_ranking_piece
|
||||
is_ranking_piece,my_dict_from_instance
|
||||
)
|
||||
|
||||
import datautils
|
||||
|
||||
from rowers.models import checkworkoutuser
|
||||
from rowers.models import checkworkoutuser,checkaccessuser
|
||||
|
||||
# Check if a user is a Coach member
|
||||
def iscoachmember(user):
|
||||
@@ -759,7 +764,20 @@ def iscoachmember(user):
|
||||
|
||||
return result
|
||||
|
||||
# Check if a user can create planned sessions
|
||||
def hasplannedsessions(user):
|
||||
if not user.is_anonymous():
|
||||
try:
|
||||
r = Rower.objects.get(user=user)
|
||||
except Rower.DoesNotExist:
|
||||
r = Rower(user=user)
|
||||
r.save()
|
||||
|
||||
result = user.is_authenticated() and (r.rowerplan=='coach' or r.rowerplan=='plan')
|
||||
else:
|
||||
result = False
|
||||
|
||||
return result
|
||||
|
||||
def getrower(user):
|
||||
try:
|
||||
@@ -781,7 +799,7 @@ def ispromember(user):
|
||||
r = Rower(user=user)
|
||||
r.save()
|
||||
|
||||
result = user.is_authenticated() and (r.rowerplan=='pro' or r.rowerplan=='coach')
|
||||
result = user.is_authenticated() and (r.rowerplan=='pro' or r.rowerplan=='coach' or r.rowerplan=='plan')
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
@@ -923,7 +941,7 @@ def add_workout_from_strokedata(user,importid,data,strokedata,
|
||||
workouttype = 'rower'
|
||||
|
||||
if workouttype not in [x[0] for x in Workout.workouttypes]:
|
||||
workouttype = 'water'
|
||||
workouttype = 'other'
|
||||
try:
|
||||
comments = data['comments']
|
||||
except:
|
||||
@@ -1070,7 +1088,7 @@ def add_workout_from_runkeeperdata(user,importid,data):
|
||||
# To Do - add utcoffset to time
|
||||
workouttype = data['type']
|
||||
if workouttype not in [x[0] for x in Workout.workouttypes]:
|
||||
workouttype = 'water'
|
||||
workouttype = 'other'
|
||||
try:
|
||||
comments = data['notes']
|
||||
except:
|
||||
@@ -1243,7 +1261,7 @@ def add_workout_from_runkeeperdata(user,importid,data):
|
||||
def add_workout_from_stdata(user,importid,data):
|
||||
workouttype = data['type']
|
||||
if workouttype not in [x[0] for x in Workout.workouttypes]:
|
||||
workouttype = 'water'
|
||||
workouttype = 'other'
|
||||
try:
|
||||
comments = data['comments']
|
||||
except:
|
||||
@@ -1273,11 +1291,16 @@ def add_workout_from_stdata(user,importid,data):
|
||||
|
||||
try:
|
||||
res = splitstdata(data['distance'])
|
||||
distance = res[1]
|
||||
times_distance = res[0]
|
||||
except KeyError:
|
||||
return (0,"No distance data in the workout")
|
||||
try:
|
||||
res = splitstdata(data['heartrate'])
|
||||
times_distance = res[0]
|
||||
distance = 0*times_distance
|
||||
except KeyError:
|
||||
return (0,"No distance or heart rate data in the workout")
|
||||
|
||||
distance = res[1]
|
||||
times_distance = res[0]
|
||||
|
||||
try:
|
||||
l = data['location']
|
||||
@@ -6032,18 +6055,31 @@ def workouts_view(request,message='',successmessage='',
|
||||
startdatestring="",enddatestring="",
|
||||
startdate=timezone.now()-datetime.timedelta(days=365),
|
||||
enddate=timezone.now()+datetime.timedelta(days=1),
|
||||
teamid=0,rankingonly=False):
|
||||
teamid=0,rankingonly=False,rowerid=0,userid=0):
|
||||
request.session['referer'] = absolute(request)['PATH']
|
||||
try:
|
||||
r = getrower(request.user)
|
||||
if rowerid != 0:
|
||||
r = Rower.objects.get(id=rowerid)
|
||||
elif userid != 0:
|
||||
u = User.objects.get(id=userid)
|
||||
r = getrower(u)
|
||||
else:
|
||||
r = getrower(request.user)
|
||||
|
||||
except Rower.DoesNotExist:
|
||||
raise Http404("Rower doesn't exist")
|
||||
|
||||
# check if access is allowed
|
||||
if not checkaccessuser(request.user,r):
|
||||
raise Http404("You are not allowed access to these data")
|
||||
|
||||
if request.method == 'POST':
|
||||
dateform = DateRangeForm(request.POST)
|
||||
if dateform.is_valid():
|
||||
startdate = dateform.cleaned_data['startdate']
|
||||
enddate = dateform.cleaned_data['enddate']
|
||||
startdatestring = None
|
||||
enddatestring = None
|
||||
else:
|
||||
dateform = DateRangeForm(initial={
|
||||
'startdate':startdate,
|
||||
@@ -7490,7 +7526,6 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
|
||||
if w.workouttype in ('water','coastal'):
|
||||
ftp = ftp*(100.-r.otwslack)/100.
|
||||
|
||||
intensityfactor = datadf['power'].mean()/float(ftp)
|
||||
intensityfactor = normp/float(ftp)
|
||||
tss = 100.*((duration*normp*intensityfactor)/(3600.*ftp))
|
||||
|
||||
@@ -11674,3 +11709,422 @@ def agegrouprecordview(request,sex='male',weightcategory='hwt',
|
||||
'the_div':div,
|
||||
})
|
||||
|
||||
# Individual user creates training for himself
|
||||
@user_passes_test(hasplannedsessions,login_url="/rowers/planmembership/",
|
||||
redirect_field_name=None)
|
||||
def plannedsession_create_view(request,timeperiod='today',rowerid=0):
|
||||
|
||||
if rowerid==0:
|
||||
r = getrower(request.user)
|
||||
else:
|
||||
try:
|
||||
r = Rower.objects.get(id=rowerid)
|
||||
except Rower.DoesNotExist:
|
||||
raise Http404("This rower doesn't exist")
|
||||
if not checkaccessuser(request.user,r):
|
||||
raise Http404("You don't have access to this plan")
|
||||
|
||||
if request.method == 'POST':
|
||||
sessioncreateform = PlannedSessionForm(request.POST)
|
||||
if sessioncreateform.is_valid():
|
||||
cd = sessioncreateform.cleaned_data
|
||||
startdate = cd['startdate']
|
||||
enddate = cd['enddate']
|
||||
sessiontype = cd['sessiontype']
|
||||
sessionmode = cd['sessionmode']
|
||||
criterium = cd['criterium']
|
||||
sessionvalue = cd['sessionvalue']
|
||||
sessionunit = cd['sessionunit']
|
||||
comment = cd['comment']
|
||||
name = cd['name']
|
||||
|
||||
if sessionunit == 'min':
|
||||
sessionmode = 'time'
|
||||
elif sessionunit in ['km','m']:
|
||||
sessionmode = 'distance'
|
||||
|
||||
ps = PlannedSession(
|
||||
name=name,
|
||||
startdate=startdate,
|
||||
enddate=enddate,
|
||||
sessiontype=sessiontype,
|
||||
sessionmode=sessionmode,
|
||||
sessionvalue=sessionvalue,
|
||||
sessionunit=sessionunit,
|
||||
comment=comment,
|
||||
criterium=criterium,
|
||||
manager=request.user)
|
||||
|
||||
ps.save()
|
||||
|
||||
add_rower_session(r,ps)
|
||||
|
||||
url = reverse(plannedsession_create_view)
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
sessioncreateform = PlannedSessionForm()
|
||||
|
||||
startdate,enddate = get_dates_timeperiod(timeperiod)
|
||||
sps = get_sessions(r,startdate=startdate,enddate=enddate)
|
||||
|
||||
return render(request,'plannedsessioncreate.html',
|
||||
{
|
||||
'teams':get_my_teams(request.user),
|
||||
'form':sessioncreateform,
|
||||
'plannedsessions':sps,
|
||||
'rower':r,
|
||||
})
|
||||
|
||||
@login_required()
|
||||
def plannedsessions_view(request,timeperiod='today',rowerid=0):
|
||||
|
||||
if rowerid==0:
|
||||
r = getrower(request.user)
|
||||
else:
|
||||
try:
|
||||
r = Rower.objects.get(id=rowerid)
|
||||
except Rower.DoesNotExist:
|
||||
raise Http404("This rower doesn't exist")
|
||||
if not checkaccessuser(request.user,r):
|
||||
raise Http404("You don't have access to this plan")
|
||||
|
||||
startdate,enddate = get_dates_timeperiod(timeperiod)
|
||||
|
||||
sps = get_sessions(r,startdate=startdate,enddate=enddate)
|
||||
|
||||
completeness = {}
|
||||
|
||||
for ps in sps:
|
||||
ratio,status = is_session_complete(r,ps)
|
||||
completeness[ps.id] = status
|
||||
|
||||
return render(request,'plannedsessions.html',
|
||||
{
|
||||
'teams':get_my_teams(request.user),
|
||||
'plannedsessions':sps,
|
||||
'rower':r,
|
||||
'timeperiod':timeperiod,
|
||||
'completeness':completeness,
|
||||
})
|
||||
|
||||
@login_required()
|
||||
def plannedsessions_manage_view(request,timeperiod='today',rowerid=0,
|
||||
initialsession=0):
|
||||
|
||||
is_ajax = False
|
||||
if request.is_ajax():
|
||||
is_ajax = True
|
||||
|
||||
if rowerid==0:
|
||||
r = getrower(request.user)
|
||||
else:
|
||||
try:
|
||||
r = Rower.objects.get(id=rowerid)
|
||||
except Rower.DoesNotExist:
|
||||
raise Http404("This rower doesn't exist")
|
||||
if not checkaccessuser(request.user,r):
|
||||
raise Http404("You don't have access to this plan")
|
||||
|
||||
startdate,enddate = get_dates_timeperiod(timeperiod)
|
||||
|
||||
sps = get_sessions(r,startdate=startdate,enddate=enddate)
|
||||
if initialsession==0:
|
||||
initialsession=sps[0].id
|
||||
|
||||
ps0 = PlannedSession.objects.get(id=initialsession)
|
||||
|
||||
ws = Workout.objects.filter(
|
||||
user=r,date__gte=startdate,
|
||||
date__lte=enddate
|
||||
).order_by(
|
||||
"date","id"
|
||||
)
|
||||
|
||||
|
||||
initialworkouts = [w.id for w in Workout.objects.filter(user=r,plannedsession=ps0)]
|
||||
|
||||
plannedsessionstuple = []
|
||||
|
||||
for ps in sps:
|
||||
sessiontpl = (ps.id,ps.__unicode__())
|
||||
plannedsessionstuple.append(sessiontpl)
|
||||
|
||||
plannedsessionstuple = tuple(plannedsessionstuple)
|
||||
|
||||
workoutdata = {}
|
||||
workoutdata['initial'] = []
|
||||
|
||||
choices = []
|
||||
|
||||
for w in ws:
|
||||
wtpl = (w.id, w.__unicode__())
|
||||
choices.append(wtpl)
|
||||
if w.id in initialworkouts:
|
||||
workoutdata['initial'].append(w.id)
|
||||
|
||||
workoutdata['choices'] = tuple(choices)
|
||||
|
||||
if request.method == 'POST':
|
||||
ps_form = PlannedSessionSelectForm(plannedsessionstuple,request.POST)
|
||||
w_form = WorkoutSessionSelectForm(workoutdata,request.POST)
|
||||
|
||||
if ps_form.is_valid():
|
||||
ps = PlannedSession.objects.get(id=ps_form.cleaned_data['plannedsession'])
|
||||
if w_form.is_valid():
|
||||
selectedworkouts = w_form.cleaned_data['workouts']
|
||||
else:
|
||||
selectedworkouts = []
|
||||
|
||||
|
||||
if selectedworkouts:
|
||||
workouts = Workout.objects.filter(user=r,id__in=selectedworkouts)
|
||||
for w in ws:
|
||||
if w.id not in selectedworkouts:
|
||||
remove_workout_plannedsession(w,ps)
|
||||
|
||||
result,comments,errors = add_workouts_plannedsession(workouts,ps)
|
||||
for c in comments:
|
||||
messages.info(request,c)
|
||||
for er in errors:
|
||||
messages.error(request,er)
|
||||
|
||||
|
||||
ps_form = PlannedSessionSelectForm(plannedsessionstuple,
|
||||
initialsession=initialsession)
|
||||
w_form = WorkoutSessionSelectForm(workoutdata=workoutdata)
|
||||
|
||||
|
||||
if is_ajax:
|
||||
ajax_workouts = []
|
||||
for id,name in workoutdata['choices']:
|
||||
if id in initialworkouts:
|
||||
ajax_workouts.append((id,name,True))
|
||||
else:
|
||||
ajax_workouts.append((id,name,False))
|
||||
|
||||
ajax_response = {
|
||||
'workouts':ajax_workouts,
|
||||
'plannedsessionstuple':plannedsessionstuple,
|
||||
}
|
||||
return JSONResponse(ajax_response)
|
||||
|
||||
|
||||
return render(request,'plannedsessionsmanage.html',
|
||||
{
|
||||
'teams':get_my_teams(request.user),
|
||||
'plannedsessions':sps,
|
||||
'workouts':ws,
|
||||
'timeperiod':timeperiod,
|
||||
'rower':r,
|
||||
'ps_form':ps_form,
|
||||
'w_form':w_form,
|
||||
})
|
||||
|
||||
|
||||
# Clone an existing planned session
|
||||
@user_passes_test(hasplannedsessions,login_url="/rowers/planmembership/",
|
||||
redirect_field_name=None)
|
||||
def plannedsession_clone_view(request,id=0,rowerid=0,
|
||||
timeperiod='today'):
|
||||
if rowerid==0:
|
||||
r = getrower(request.user)
|
||||
else:
|
||||
try:
|
||||
r = Rower.objects.get(id=rowerid)
|
||||
except Rower.DoesNotExist:
|
||||
raise Http404("This rower doesn't exist")
|
||||
if not checkaccessuser(request.user,r):
|
||||
raise Http404("You don't have access to this plan")
|
||||
|
||||
startdate,enddate = get_dates_timeperiod(timeperiod)
|
||||
|
||||
try:
|
||||
ps = PlannedSession.objects.get(id=id)
|
||||
except PlannedSession.DoesNotExist:
|
||||
raise Http404("Planned Session does not exist")
|
||||
|
||||
if ps.manager != request.user:
|
||||
raise Http404("You are not allowed to clone this planned session")
|
||||
|
||||
ps.pk = None
|
||||
|
||||
ps.startdate = timezone.now()
|
||||
ps.enddate = timezone.now()
|
||||
ps.name += ' (copy)'
|
||||
|
||||
ps.save()
|
||||
|
||||
add_rower_session(r,ps)
|
||||
|
||||
url = reverse(plannedsession_edit_view,
|
||||
kwargs = {
|
||||
'id':ps.id,
|
||||
'timeperiod':timeperiod,
|
||||
'rowerid':r.id,
|
||||
}
|
||||
)
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
# Edit an existing planned session
|
||||
@user_passes_test(hasplannedsessions,login_url="/rowers/planmembership/",
|
||||
redirect_field_name=None)
|
||||
def plannedsession_edit_view(request,id=0,timeperiod='today',rowerid=0):
|
||||
|
||||
if rowerid==0:
|
||||
r = getrower(request.user)
|
||||
else:
|
||||
try:
|
||||
r = Rower.objects.get(id=rowerid)
|
||||
except Rower.DoesNotExist:
|
||||
raise Http404("This rower doesn't exist")
|
||||
if not checkaccessuser(request.user,r):
|
||||
raise Http404("You don't have access to this plan")
|
||||
|
||||
startdate,enddate = get_dates_timeperiod(timeperiod)
|
||||
|
||||
|
||||
try:
|
||||
ps = PlannedSession.objects.get(id=id)
|
||||
except PlannedSession.DoesNotExist:
|
||||
raise Http404("Planned Session does not exist")
|
||||
|
||||
if ps.manager != request.user:
|
||||
raise Http404("You are not allowed to edit this planned session")
|
||||
|
||||
if request.method == 'POST':
|
||||
sessioncreateform = PlannedSessionForm(request.POST,instance=ps)
|
||||
if sessioncreateform.is_valid():
|
||||
cd = sessioncreateform.cleaned_data
|
||||
|
||||
if cd['sessionunit'] == 'min':
|
||||
cd['sessionmode'] = 'time'
|
||||
elif cd['sessionunit'] in ['km','m']:
|
||||
cd['sessionmode'] = 'distance'
|
||||
|
||||
|
||||
res,message = update_plannedsession(ps,cd)
|
||||
|
||||
if res:
|
||||
messages.info(request,message)
|
||||
else:
|
||||
messages.error(request,message)
|
||||
|
||||
url = reverse(plannedsession_edit_view,
|
||||
kwargs={
|
||||
'id':int(ps.id),
|
||||
'timeperiod':timeperiod,
|
||||
'rowerid':r.id,
|
||||
})
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
sessioncreateform = PlannedSessionForm(instance=ps)
|
||||
|
||||
sps = get_sessions(r,startdate=startdate,enddate=enddate)
|
||||
|
||||
return render(request,'plannedsessionedit.html',
|
||||
{
|
||||
'teams':get_my_teams(request.user),
|
||||
'form':sessioncreateform,
|
||||
'plannedsessions':sps,
|
||||
'thesession':ps,
|
||||
'rower':r,
|
||||
'timeperiod':timeperiod,
|
||||
})
|
||||
|
||||
|
||||
|
||||
@login_required()
|
||||
def plannedsession_view(request,id=0,rowerid=0):
|
||||
|
||||
m = getrower(request.user)
|
||||
|
||||
if not rowerid:
|
||||
r = m
|
||||
else:
|
||||
r = Rower.objects.get(id=rowerid)
|
||||
|
||||
try:
|
||||
ps = PlannedSession.objects.get(id=id)
|
||||
except PlannedSession.DoesNotExist:
|
||||
raise Http404("Planned Session does not exist")
|
||||
|
||||
|
||||
if ps.manager != request.user:
|
||||
raise Http404("You are not allowed to delete this planned session")
|
||||
|
||||
|
||||
psdict = my_dict_from_instance(ps,PlannedSession)
|
||||
|
||||
ws = get_workouts_session(r,ps)
|
||||
|
||||
ratio,status = is_session_complete(r,ps)
|
||||
|
||||
ratio = int(100.*ratio)
|
||||
|
||||
return render(request,'plannedsessionview.html',
|
||||
{
|
||||
'psdict': psdict,
|
||||
'attrs':[
|
||||
'name','startdate','enddate','sessiontype',
|
||||
'sessionvalue','sessionunit'
|
||||
],
|
||||
'workouts': ws,
|
||||
'manager':m,
|
||||
'rower':r,
|
||||
'ratio':ratio,
|
||||
'status':status
|
||||
}
|
||||
)
|
||||
|
||||
@login_required()
|
||||
def plannedsession_delete_view(request,id=0):
|
||||
r = getrower(request.user)
|
||||
|
||||
try:
|
||||
ps = PlannedSession.objects.get(id=id)
|
||||
except PlannedSession.DoesNotExist:
|
||||
raise Http404("Planned Session does not exist")
|
||||
|
||||
|
||||
if ps.manager != request.user:
|
||||
raise Http404("You are not allowed to delete this planned session")
|
||||
|
||||
ws = Workout.objects.filter(plannedsession=ps)
|
||||
for w in ws:
|
||||
w.plannedsession=None
|
||||
w.save()
|
||||
|
||||
ps.delete()
|
||||
|
||||
url = reverse(plannedsessions_view)
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
@login_required()
|
||||
def plannedsession_deleteconfirm_view(request,id=0):
|
||||
|
||||
r = getrower(request.user)
|
||||
|
||||
try:
|
||||
ps = PlannedSession.objects.get(id=id)
|
||||
except PlannedSession.DoesNotExist:
|
||||
raise Http404("Planned Session does not exist")
|
||||
|
||||
|
||||
if ps.manager != request.user:
|
||||
raise Http404("You are not allowed to delete this planned session")
|
||||
|
||||
|
||||
psdict = my_dict_from_instance(ps,PlannedSession)
|
||||
|
||||
return render(request,'plannedsessiondeleteconfirm.html',
|
||||
{
|
||||
'ps':ps,
|
||||
'psdict': psdict,
|
||||
'attrs':[
|
||||
'name','startdate','enddate','sessiontype',
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
@@ -291,6 +291,19 @@ th.rotate > div > span {
|
||||
border: solid 1px #333;
|
||||
}
|
||||
|
||||
.dot {
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
text-align: center;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border: solid 1px #333;
|
||||
}
|
||||
|
||||
.dot:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button {
|
||||
font: 1.1em/1.5em sans-serif;
|
||||
text-decoration: none;
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
{% extends "basebase.html" %}
|
||||
{% block filters %}
|
||||
{% load rowerfilters %}
|
||||
{% load rowerfilters %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block teams %}
|
||||
{% if user.is_authenticated and user|has_teams %}
|
||||
<div class="grid_1 alpha dropdown">
|
||||
<button class="grid_1 alpha button gray small dropbtn">
|
||||
Teams
|
||||
</button>
|
||||
<div class="dropdown-content">
|
||||
<a class="button gray small" href="/rowers/me/teams/">Manage Teams</a>
|
||||
{% if user|is_manager %}
|
||||
<a class="button gray small" href="/rowers/workout/upload/team/">Upload Team Member Workout</a>
|
||||
{% endif %}
|
||||
{% for t in user|user_teams %}
|
||||
<a class="button gray small" href="/rowers/list-workouts/team/{{ t.id }}/">{{ t.name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<span class="tooltiptext">See recent workouts for your team</span>
|
||||
{% elif user.is_authenticated and user.rower.team.all %}
|
||||
<div class="grid_1 alpha dropdown">
|
||||
<button class="grid_1 alpha button gray small dropbtn">
|
||||
Teams
|
||||
</button>
|
||||
<div class="dropdown-content">
|
||||
{% for t in user.rower.team.all %}
|
||||
<a class="button gray small" href="/rowers/list-workouts/team/{{ t.id }}/">{{ t.name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<span class="tooltiptext">See recent workouts for your team</span>
|
||||
{% else %}
|
||||
<p> </p>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated and user|has_teams %}
|
||||
<div class="grid_1 alpha dropdown">
|
||||
<button class="grid_1 alpha button gray small dropbtn">
|
||||
Teams
|
||||
</button>
|
||||
<div class="dropdown-content">
|
||||
<a class="button gray small" href="/rowers/me/teams/">Manage Teams</a>
|
||||
{% if user|is_manager %}
|
||||
<a class="button gray small" href="/rowers/workout/upload/team/">Upload Team Member Workout</a>
|
||||
{% endif %}
|
||||
{% for t in user|user_teams %}
|
||||
<a class="button gray small" href="/rowers/list-workouts/team/{{ t.id }}/">{{ t.name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<span class="tooltiptext">See recent workouts for your team</span>
|
||||
{% elif user.is_authenticated and user.rower.team.all %}
|
||||
<div class="grid_1 alpha dropdown">
|
||||
<button class="grid_1 alpha button gray small dropbtn">
|
||||
Teams
|
||||
</button>
|
||||
<div class="dropdown-content">
|
||||
{% for t in user.rower.team.all %}
|
||||
<a class="button gray small" href="/rowers/list-workouts/team/{{ t.id }}/">{{ t.name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<span class="tooltiptext">See recent workouts for your team</span>
|
||||
{% else %}
|
||||
<p> </p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -201,6 +201,17 @@
|
||||
{% block teams %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div class="grid_1 tooltip">
|
||||
{% if user.is_authenticated %}
|
||||
<p><a class="button gray small" href="/rowers/sessions/">Plans</a></p>
|
||||
{% else %}
|
||||
<p> </p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="grid_1 tooltip">
|
||||
{% block challenges %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user