Private
Public Access
1
0

Merge branch 'develop' into feature/metrics

This commit is contained in:
Sander Roosendaal
2020-02-08 12:41:42 +01:00
60 changed files with 3651 additions and 11939 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -148,6 +148,7 @@ psycopg2==2.8.1
ptyprocess==0.6.0 ptyprocess==0.6.0
py==1.8.0 py==1.8.0
pyarrow==0.15.0 pyarrow==0.15.0
pycairo==1.19.0
pycparser==2.19 pycparser==2.19
Pygments==2.3.1 Pygments==2.3.1
pyparsing==2.3.1 pyparsing==2.3.1
@@ -173,9 +174,10 @@ ratelim==0.1.6
redis==3.2.1 redis==3.2.1
requests==2.21.0 requests==2.21.0
requests-oauthlib==1.2.0 requests-oauthlib==1.2.0
rowingdata==2.5.7 rowingdata==2.6.7
rowingphysics==0.5.0 rowingphysics==0.5.0
rq==0.13.0 rq==0.13.0
rules==2.1
scipy==1.2.1 scipy==1.2.1
SecretStorage==3.1.1 SecretStorage==3.1.1
Send2Trash==1.5.0 Send2Trash==1.5.0
@@ -194,6 +196,7 @@ testpath==0.4.2
text-unidecode==1.2 text-unidecode==1.2
thrift==0.11.0 thrift==0.11.0
timezonefinder==4.0.1 timezonefinder==4.0.1
tk==0.1.0
toolz==0.10.0 toolz==0.10.0
tornado==6.0.1 tornado==6.0.1
tqdm==4.31.1 tqdm==4.31.1
+3 -2
View File
@@ -14,6 +14,7 @@ import datetime
from requests import Request, Session from requests import Request, Session
import rowers.mytypes as mytypes import rowers.mytypes as mytypes
from rowers.mytypes import otwtypes from rowers.mytypes import otwtypes
from rowers.rower_rules import is_workout_user,ispromember
from iso8601 import ParseError from iso8601 import ParseError
import numpy import numpy
@@ -155,7 +156,7 @@ def add_stroke_data(user,c2id,workoutid,startdatetime,csvfilename,
def get_c2_workouts(rower): def get_c2_workouts(rower):
if not isprorower(rower): if not ispromember(rower.user):
return 0 return 0
try: try:
@@ -863,7 +864,7 @@ def workout_c2_upload(user,w):
r = Rower.objects.get(user=user) r = Rower.objects.get(user=user)
# ready to upload. Hurray # ready to upload. Hurray
if (checkworkoutuser(user,w)): if (is_workout_user(user,w)):
c2userid = get_userid(r.c2token) c2userid = get_userid(r.c2token)
if not c2userid: if not c2userid:
raise NoTokenError("User has no token") raise NoTokenError("User has no token")
+4 -4
View File
@@ -140,10 +140,10 @@ def timedeltaconv(x):
def rdata(file,rower=rrower()): def rdata(file,rower=rrower()):
try: try:
res = rrdata(file,rower=rower) res = rrdata(csvfile=file,rower=rower)
except IOError: except IOError:
try: try:
res = rrdata(file+'.gz',rower=rower) res = rrdata(csvfile=file+'.gz',rower=rower)
except IOError: except IOError:
res = 0 res = 0
@@ -948,7 +948,7 @@ def smalldataprep(therows,xparam,yparam1,yparam2):
f1 = workout.csvfilename f1 = workout.csvfilename
try: try:
rowdata = dataprep(rrdata(f1).df) rowdata = dataprep(rrdata(csvfile=f1).df)
rowdata = pd.DataFrame({xparam: rowdata[xparam], rowdata = pd.DataFrame({xparam: rowdata[xparam],
yparam1: rowdata[yparam1], yparam1: rowdata[yparam1],
@@ -960,7 +960,7 @@ def smalldataprep(therows,xparam,yparam1,yparam2):
df = pd.concat([df,rowdata],ignore_index=True) df = pd.concat([df,rowdata],ignore_index=True)
except IOError: except IOError:
try: try:
rowdata = dataprep(rrdata(f1+'.gz').df) rowdata = dataprep(rrdata(csvfile=f1+'.gz').df)
rowdata = pd.DataFrame({xparam: rowdata[xparam], rowdata = pd.DataFrame({xparam: rowdata[xparam],
yparam1: rowdata[yparam1], yparam1: rowdata[yparam1],
yparam2: rowdata[yparam2], yparam2: rowdata[yparam2],
+4 -1
View File
@@ -4,6 +4,7 @@ from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib.auth.decorators import login_required,user_passes_test from django.contrib.auth.decorators import login_required,user_passes_test
from django.urls import reverse
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
@@ -29,6 +30,9 @@ def user_passes_test(test_func, message=default_message,login_url=None,redirect_
def decorator(view_func): def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func)) @wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view(request, *args, **kwargs): def _wrapped_view(request, *args, **kwargs):
if request.user.is_anonymous:
url = reverse('login')+'?next='+request.path
return HttpResponseRedirect(url)
if not test_func(request.user): if not test_func(request.user):
messages.error(request, message) messages.error(request, message)
return HttpResponseRedirect(login_url) return HttpResponseRedirect(login_url)
@@ -48,4 +52,3 @@ def login_required_message(function=None, message=default_message):
if function: if function:
return actual_decorator(function) return actual_decorator(function)
return actual_decorator return actual_decorator
+1
View File
@@ -1404,6 +1404,7 @@ class FlexAxesForm(forms.Form):
extrametrics = kwargs.pop('extrametrics',[]) extrametrics = kwargs.pop('extrametrics',[])
super(FlexAxesForm, self).__init__(*args, **kwargs) super(FlexAxesForm, self).__init__(*args, **kwargs)
rower = Rower.objects.get(user=request.user) rower = Rower.objects.get(user=request.user)
axchoicespro = ( axchoicespro = (
+2 -3
View File
@@ -43,7 +43,7 @@ from django.contrib.auth.decorators import login_required
# from .models import Profile # from .models import Profile
from rowingdata import rowingdata, make_cumvalues from rowingdata import rowingdata, make_cumvalues
import pandas as pd import pandas as pd
from rowers.models import Rower,Workout,checkworkoutuser,TombStone from rowers.models import Rower,Workout,TombStone
import rowers.mytypes as mytypes import rowers.mytypes as mytypes
from rowsandall_app.settings import ( from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
@@ -54,7 +54,7 @@ from rowsandall_app.settings import (
from rowers.utils import ( from rowers.utils import (
NoTokenError, custom_exception_handler, ewmovingaverage, NoTokenError, custom_exception_handler, ewmovingaverage,
geo_distance,isprorower,uniqify geo_distance,uniqify
) )
@@ -309,4 +309,3 @@ def imports_token_refresh(user,tokenname,refreshtokenname,expirydatename,oauth_d
r.save() r.save()
return access_token return access_token
+8 -2
View File
@@ -2680,7 +2680,10 @@ def interactive_windchart(id=0,promember=0):
if rowdata == 0: if rowdata == 0:
return 0 return 0
dist = rowdata.df.loc[:,'cum_dist'] try:
dist = rowdata.df.loc[:,'cum_dist']
except KeyError:
return ['','No Data Found']
try: try:
vwind = rowdata.df.loc[:,'vwind'] vwind = rowdata.df.loc[:,'vwind']
@@ -2770,7 +2773,10 @@ def interactive_streamchart(id=0,promember=0):
if rowdata == 0: if rowdata == 0:
return "","No Valid Data Available" return "","No Valid Data Available"
dist = rowdata.df.loc[:,'cum_dist'] try:
dist = rowdata.df.loc[:,'cum_dist']
except KeyError:
return ['','No Data found']
try: try:
vstream = rowdata.df.loc[:,'vstream'] vstream = rowdata.df.loc[:,'vstream']
+4 -4
View File
@@ -64,18 +64,18 @@ def send_confirm(user, name, link, options):
def rdata(file, rower=rrower()): def rdata(file, rower=rrower()):
""" Reads rowingdata data or returns 0 on Error """ """ Reads rowingdata data or returns 0 on Error """
try: try:
result = rrdata(file, rower=rower) result = rrdata(csvfile=file, rower=rower)
except IOError: except IOError:
try: try:
result = rrdata(file + '.gz', rower=rower) result = rrdata(csvfile=file + '.gz', rower=rower)
except IOError: except IOError:
result = 0 result = 0
except TypeError: except TypeError:
try: try:
result = rrdata(file) result = rrdata(csvfile=file)
except IOError: except IOError:
try: try:
result = rrdata(file+'.gz', rower=rower) result = rrdata(csvfile=file+'.gz', rower=rower)
except IOError: except IOError:
result = 0 result = 0
+111 -123
View File
@@ -39,6 +39,10 @@ import pandas as pd
from dateutil import parser from dateutil import parser
import datetime import datetime
#from rules.contrib.models import RulesModel
from rowers.rower_rules import *
from rowers.rows import validate_file_extension from rowers.rows import validate_file_extension
from collections import OrderedDict from collections import OrderedDict
from timezonefinder import TimezoneFinder from timezonefinder import TimezoneFinder
@@ -53,6 +57,10 @@ from rowsandall_app.settings import (
TWEET_CONSUMER_SECRET, TWEET_CONSUMER_SECRET,
) )
# END PERMISSIONS
tweetapi = twitter.Api(consumer_key=TWEET_CONSUMER_KEY, tweetapi = twitter.Api(consumer_key=TWEET_CONSUMER_KEY,
consumer_secret=TWEET_CONSUMER_SECRET, consumer_secret=TWEET_CONSUMER_SECRET,
access_token_key=TWEET_ACCESS_TOKEN_KEY, access_token_key=TWEET_ACCESS_TOKEN_KEY,
@@ -322,14 +330,7 @@ class C2WorldClassAgePerformance(models.Model):
return thestring return thestring
def is_not_basic(user):
if user.rower.rowerplan == 'basic':
raise ValidationError(
"Basic user cannot be team manager"
)
# For future Team functionality
@python_2_unicode_compatible @python_2_unicode_compatible
class Team(models.Model): class Team(models.Model):
choices = ( choices = (
@@ -344,30 +345,28 @@ class Team(models.Model):
name = models.CharField(max_length=150,unique=True,verbose_name='Team Name') name = models.CharField(max_length=150,unique=True,verbose_name='Team Name')
notes = models.CharField(blank=True,max_length=200,verbose_name='Team Purpose') notes = models.CharField(blank=True,max_length=200,verbose_name='Team Purpose')
manager = models.ForeignKey(User, null=True,on_delete=models.CASCADE) # validators=[is_not_basic]) manager = models.ForeignKey(User, null=True,on_delete=models.CASCADE)
private = models.CharField(max_length=30,choices=choices,default='open', private = models.CharField(max_length=30,choices=choices,default='open',
verbose_name='Team Type') verbose_name='Team Type')
viewing = models.CharField(max_length=30,choices=viewchoices,default='allmembers',verbose_name='Sharing Behavior') viewing = models.CharField(max_length=30,choices=viewchoices,default='allmembers',verbose_name='Sharing Behavior')
def __str__(self): def __str__(self):
return self.name return self.name
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
manager = self.manager manager = self.manager
if manager.rower.rowerplan == 'basic': if not can_have_teams(manager):
if manager.rower.protrialexpires < datetime.date.today() and manager.rower.plantrialexpires < datetime.date.today():
raise ValidationError( raise ValidationError(
"Basic user cannot be team manager" "Basic user cannot be team manager"
) )
if manager.rower.rowerplan in ['plan','pro']: if not can_add_team(manager):
otherteams = Team.objects.filter(manager=manager) raise ValidationError(
if otherteams.count() >= 1: "Pro and Self-Coach users cannot have more than one team"
raise ValidationError( )
"Pro and Self-Coach users cannot have more than one team"
)
super(Team, self).save(*args,**kwargs) super(Team, self).save(*args,**kwargs)
@@ -658,6 +657,9 @@ class CoachingGroup(models.Model):
rs = Rower.objects.filter(coachinggroups__in=[self]) rs = Rower.objects.filter(coachinggroups__in=[self])
return rs.count() return rs.count()
def get_coaches(self):
return Rower.objects.filter(mycoachgroup=self)
# Extension of User with rowing specific data # Extension of User with rowing specific data
@python_2_unicode_compatible @python_2_unicode_compatible
class Rower(models.Model): class Rower(models.Model):
@@ -896,6 +898,20 @@ class Rower(models.Model):
super(Rower, self).save(*args, **kwargs) super(Rower, self).save(*args, **kwargs)
def get_managed_teams(self):
return Team.objects.filter(manager=self.user)
def get_coaches(self):
coaches = []
for group in self.coachinggroups.all():
try:
coach = Rower.objects.get(mycoachgroup=group)
coaches.append(coach)
except Rower.DoesNotExist:
pass
return coaches
class DeactivateUserForm(forms.ModelForm): class DeactivateUserForm(forms.ModelForm):
class Meta: class Meta:
model = User model = User
@@ -925,18 +941,19 @@ class CoachOffer(models.Model):
from django.db.models.signals import m2m_changed from django.db.models.signals import m2m_changed
def check_teams_on_change(sender, **kwargs): def check_teams_on_change(sender, **kwargs):
instance = kwargs.pop('instance', None) instance = kwargs.pop('instance', None)
action = kwargs.pop('action', None) action = kwargs.pop('action', None)
pk_set = kwargs.pop('pk_set',None) pk_set = kwargs.pop('pk_set',None)
if action == 'pre_add' and instance.rowerplan=='basic': if action == 'pre_add':
if instance.protrialexpires < datetime.date.today() and instance.plantrialexpires < datetime.date.today(): #if instance.protrialexpires < datetime.date.today() and instance.plantrialexpires < datetime.date.today():
for id in pk_set: for id in pk_set:
team = Team.objects.get(id=id) team = Team.objects.get(id=id)
if team.manager.rower.rowerplan not in ['coach']: if not can_join_team(instance.user,team):
raise ValidationError( raise ValidationError(
"You cannot join a team led by a Pro, Free Coach Plan or Self-Coach user" "You cannot join a team led by a Pro, Free Coach Plan or Self-Coach user"
) )
m2m_changed.connect(check_teams_on_change, sender=Rower.team.through) m2m_changed.connect(check_teams_on_change, sender=Rower.team.through)
@@ -1165,95 +1182,7 @@ class BasePlannedSessionFormSet(BaseFormSet):
return return
# Check if workout is owned by this user
def checkworkoutuser(user,workout):
if user.is_anonymous:
return False
try:
r = Rower.objects.get(user=user)
if workout.user == r:
return True
coaches = []
for group in workout.user.coachinggroups.all():
coach = Rower.objects.get(mycoachgroup=group)
coaches.append(coach)
for coach in coaches:
if user.rower == coach and workout.privacy == 'visible':
return True
else:
return False
except Rower.DoesNotExist:
return False
# Check if workout may be viewed by this user
def checkworkoutuserview(user,workout):
if user.is_anonymous:
return False
try:
r = Rower.objects.get(user=user)
if workout.user == r:
return True
teams = workout.user.team.all()
for team in teams:
if team in r.team.all():
return True
return False
except Rower.DoesNotExist:
return False
return False
def checkviewworkouts(user,rower):
try:
r = user.rower
if rower == r:
return True
teams = Team.objects.filter(manager=user)
if rower in Rower.objects.filter(team__in=teams):
return True
if rower in Rower.objects.filter(coachinggroups__in=[user.rower.mycoachgroup]):
return True
except Rower.DoesNotExist:
return False
# check if user is plan and rower is in his group
def checkaccessplanuser(user,rower):
try:
r = Rower.objects.get(user=user)
if rower == r:
return True
team_managers = [t.manager for t in rower.team.all() if t.manager.rower.rowerplan in ['plan','coach','freecoach']]
if user.rower.rowerplan != 'basic':
return user in team_managers
else:
return False
return False
except Rower.DoesNotExist:
return False
# Check if user is coach or rower
def checkaccessuser(user,rower):
try:
r = Rower.objects.get(user=user)
if rower == r:
return True
coaches = []
for group in rower.coachinggroups.all():
coach = Rower.objects.get(mycoachgroup=group)
coaches.append(coach)
for coach in coaches:
if user.rower == coach:
return True
else:
return False
except Rower.DoesNotExist:
return False
timezones = ( timezones = (
(x,x) for x in pytz.common_timezones (x,x) for x in pytz.common_timezones
@@ -1354,6 +1283,18 @@ class TrainingTarget(models.Model):
return stri return stri
def check_trainingtarget_on_change(sender,**kwargs):
instance = kwargs.pop('instance',None)
action = kwargs.pop('action',None)
pk_set = kwargs.pop('pk_set',None)
if action == 'pre_add':
for id in pk_set:
rower = Rower.objects.get(id=id)
if not can_plan_user(instance.manager.user,rower):
raise ValidationError("You cannot add this rower. Not your coachee")
m2m_changed.connect(check_trainingtarget_on_change, sender=TrainingTarget.rowers.through)
class TrainingTargetForm(ModelForm): class TrainingTargetForm(ModelForm):
class Meta: class Meta:
model = TrainingTarget model = TrainingTarget
@@ -1423,12 +1364,12 @@ class TrainingPlan(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
manager = self.manager manager = self.manager
if not can_add_plan(manager.user):
raise ValidationError(
"Basic user cannot have a training plan"
)
if manager.rowerplan in ['basic','pro']:
if manager.plantrialexpires < timezone.now().date():
raise ValidationError(
"Basic user cannot have a training plan"
)
if self.enddate < self.startdate: if self.enddate < self.startdate:
startdate = self.startdate startdate = self.startdate
@@ -1439,6 +1380,13 @@ class TrainingPlan(models.Model):
if not self.enddate <= self.startdate: if not self.enddate <= self.startdate:
super(TrainingPlan,self).save(*args, **kwargs) super(TrainingPlan,self).save(*args, **kwargs)
# only add athletes that are allowed to be added
for rower in self.rowers.all():
if can_plan_user(manager.user,rower):
self.rowers.add(rower)
else:
self.rowers.remove(rower)
if self.status: if self.status:
otherplans = TrainingPlan.objects.filter( otherplans = TrainingPlan.objects.filter(
status=True).exclude( status=True).exclude(
@@ -1475,6 +1423,18 @@ class TrainingPlan(models.Model):
else: else:
createmacrofillers(self) createmacrofillers(self)
def check_trainingplan_on_change(sender, **kwargs):
instance = kwargs.pop('instance',None)
action = kwargs.pop('action',None)
pk_set = kwargs.pop('pk_set',None)
if action == 'pre_add':
for id in pk_set:
rower = Rower.objects.get(id=id)
if not can_plan_user(instance.manager.user,rower):
raise ValidationError("You cannot add this rower. Not your coachee")
m2m_changed.connect(check_trainingplan_on_change, sender=TrainingPlan.rowers.through)
class TrainingPlanForm(ModelForm): class TrainingPlanForm(ModelForm):
class Meta: class Meta:
@@ -2158,11 +2118,10 @@ class PlannedSession(models.Model):
manager = self.manager manager = self.manager
if self.sessiontype not in ['race','indoorrace']: if self.sessiontype not in ['race','indoorrace']:
if manager.rower.rowerplan in ['basic','pro']: if not can_add_session(self.manager):
if manager.rower.plantrialexpires < timezone.now().date(): raise ValidationError(
raise ValidationError( "You must be a Self-Coach user or higher to create a planned session"
"You must be a Self-Coach user or higher to create a planned session" )
)
# sort units # sort units
@@ -2342,6 +2301,35 @@ class PlannedSessionForm(ModelForm):
self.fields['sessiontype'].choices = regularsessiontypechoices self.fields['sessiontype'].choices = regularsessiontypechoices
class PlannedSessionTemplateForm(ModelForm):
class Meta:
model = PlannedSession
fields = [
'name',
'sessiontype',
'sessionmode',
'criterium',
'sessionvalue',
'sessionunit',
'course',
'comment',
]
dateTimeOptions = {
'format': 'yyyy-mm-dd',
'autoclose': True,
}
widgets = {
'comment': forms.Textarea,
}
def __init__(self,*args,**kwargs):
super(PlannedSessionTemplateForm, self).__init__(*args, **kwargs)
self.fields['course'].queryset = GeoCourse.objects.all().order_by("country","name")
self.fields['sessiontype'].choices = regularsessiontypechoices
def get_course_timezone(course): def get_course_timezone(course):
polygons = GeoPolygon.objects.filter(course = course) polygons = GeoPolygon.objects.filter(course = course)
points = GeoPoint.objects.filter(polygon = polygons[0]) points = GeoPoint.objects.filter(polygon = polygons[0])
@@ -2740,7 +2728,7 @@ class Workout(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
user = self.user user = self.user
if self.user.rowerplan == 'freecoach': if not can_add_workout(user.user):
raise forms.ValidationError("Free Coach User cannot have any workouts") raise forms.ValidationError("Free Coach User cannot have any workouts")
super(Workout, self).save(*args, **kwargs) super(Workout, self).save(*args, **kwargs)
+3 -7
View File
@@ -38,7 +38,7 @@ from django.contrib.auth.decorators import login_required
from rowingdata import rowingdata from rowingdata import rowingdata
import pandas as pd import pandas as pd
from rowers.models import Rower,Workout from rowers.models import Rower,Workout
from rowers.models import checkworkoutuser
import rowers.dataprep as dataprep import rowers.dataprep as dataprep
from rowers.dataprep import columndict from rowers.dataprep import columndict
@@ -136,7 +136,7 @@ def get_polar_notifications():
return available_data return available_data
from rowers.utils import isprorower from rowers.rower_rules import ispromember
def get_all_new_workouts(available_data,testing=False): def get_all_new_workouts(available_data,testing=False):
for record in available_data: for record in available_data:
@@ -146,7 +146,7 @@ def get_all_new_workouts(available_data,testing=False):
try: try:
r = Rower.objects.get(polaruserid=record['user-id']) r = Rower.objects.get(polaruserid=record['user-id'])
u = r.user u = r.user
if r.polar_auto_import and isprorower(r): if r.polar_auto_import and ispromember(u):
exercise_list = get_polar_workouts(u) exercise_list = get_polar_workouts(u)
if testing: if testing:
print(exercise_list) print(exercise_list)
@@ -337,7 +337,3 @@ def get_polar_workout(user,id,transactionid):
return result return result
return None return None
+708
View File
@@ -0,0 +1,708 @@
import rules
import datetime
# PERMISSIONS
# USER permissions
"""
USER permissions
- There are 5 types of user: basic, pro, plan, coach, freecoach
- These methods exist on user
- user.rower
- user.is_anonymous
- user.is_staff
- These methods exist on rower
- rower.rowerplan
- rower.protrialexpires
- rower.plantrialexpires
- rower.mycoachgroup
- rower.coachinggroups
- rower.team
- These Rules apply to Rowers
- Coach can have any number of groups
- test_permissions.PermissionFreeCoach.test_coach_groupmanager
- test_permissions.PermissionsBasicsTests.test_coach_groupmanager
- test_permissions.PermissionsViewTests.test_coach_groups_create
- Pro and Plan users can have one group and not more
- test_permissions.PermissionsFreeCoach.test_pro_groupmanager
- test_permissions.PermissionsViewTests.test_pro_groups_create
- Free Coach user cannot have workouts
- test_permissions.PermissionFreeCoach.test_add_workout_freecoach
- Free coach can create more than one group
- test_permissions.PermissionsFreeCoach.test_plan_groupmanager
- Free Coach & Plan (Self Coach) can create planned sessions and team planned sessions
- test_permissions.PermissionsFreeCoach.test_plan_create_session
- test_permissions.PermissionsFreeCoach.test_coach_create_session
- Pro cannot create planned sessions or team planned sessions
- test_permissions.PermissionsViewTests.test_pro_create_session
- Basic cannot join groups led by Free Coach
- test_permissions.PermissionFreeCoach.test_add_basic_pro_or_plan
- Coach can edit athlete settings (if in coachinggroup)
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_prefs
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_prefs_not
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_settings
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_settings_not
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_account
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_account_not
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_exportsettings
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_exportsettings_not
- Coach can upload a workout on behalf of athlete
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_upload
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_upload_not
- Pro and Plan cannot upload a workout on behalf of athlete
- test_permissions.PermissionsViewTests.test_plan_edit_athlete_upload
- Coach can run analytics for athlete
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_analysis
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_analysis_not
- test_permissions.PermissionsViewTests.test_pro_edit_athlete_analysis
- Pro and Plan user cannot run analysis for members of their groups
- test_permissions.PermissionsViewTests.test_plan_edit_athlete_analysis
- Pro and Plan user cannot edit athlete sessings
- test_permissions.PermissionsViewTests.test_plan_edit_athlete_settings
- test_permissions.PermissionsViewTests.test_pro_edit_athlete_settings
- Self-Coach and Pro cannot create more than one group
- test_permissions.PermissionsFreeCoach.test_plan_groupmanager
- Pro users and higher can join group led by other Pro or higher user
- test_permissions.PermissionsFreeCoach.test_add_proplan_pro_or_plan
- Pro or basic cannot create planned sessions or team planned sessions
- test_permissions.PermissionsFreeCoach.test_pro_create_plannedsession
"""
# used in can_plan_user
@rules.predicate
def user_is_not_basic(user):
if user.rower.rowerplan != 'basic':
return True
if user.rower.protrialexpires >= datetime.date.today() and user.rower.plantrialexpires >= datetime.date.today():
return True
return False
@rules.predicate
def can_start_trial(user):
if user.is_anonymous:
return False
return user.rower.protrialexpires == datetime.date(1970,1,1)
@rules.predicate
def can_start_plantrial(user):
if user.is_anonymous:
return False
return user.rower.plantrialexpires == datetime.date(1970,1,1)
@rules.predicate
def is_staff(user):
return user.is_staff
@rules.predicate
def is_coach(user):
return user.rower.rowerplan in ['coach','freecoach']
def is_paid_coach(user):
return user.rower.rowerplan == 'coach'
@rules.predicate
def is_planmember(user):
try:
r = user.rower
except AttributeError:
return False
return r.rowerplan in ['coach','plan'] # freecoach?
@rules.predicate
def is_promember(user):
try:
r = user.rower
except AttributeError:
return False
return r.rowerplan in ['pro','coach','plan'] # freecoach?
@rules.predicate
def is_protrial(user):
try:
r = user.rower
except AttributeError:
return False
if r.rowerplan == 'basic':
return r.protrialexpires >= datetime.date.today()
if r.rowerplan == 'freecoach':
if r.mycoachgroup is not None:
return len(r.mycoachgroup)>=4
return False
ispromember = is_promember | is_protrial
can_have_teams = ispromember | is_coach
@rules.predicate
def can_add_team(user):
if is_coach(user):
return True
if ispromember(user):
otherteams = user.rower.get_managed_teams()
if otherteams.count() == 0:
return True
return False
@rules.predicate
def can_add_plan(user):
return isplanmember(user) or is_coach(user)
@rules.predicate
def can_add_workout(user):
if user.is_anonymous:
return False
return user.rower.rowerplan != 'freecoach'
@rules.predicate
def is_plantrial(user):
try:
r = user.rower
except AttributeError:
return False
if r.rowerplan in ['basic','pro']:
return r.plantrialexpires >= datetime.date.today()
if r.rowerplan == 'freecoach':
if r.mycoachgroup is not None:
return len(r.mycoachgroup)>=4
return False
isplanmember = is_planmember | is_plantrial
@rules.predicate
def can_add_session(user):
return isplanmember(user) or is_coach(user)
# User / Coach relationships (Rower object)
@rules.predicate
def can_plan(user):
return user.rower.rowerplan in ['plan','coach','freecoach']
# checks if rower is coach of user (or is user himself)
@rules.predicate
def is_coach_user(usercoach,userrower):
if usercoach == userrower:
return True
if not is_coach(usercoach):
return False
r = userrower.rower
coaches = []
for group in r.coachinggroups.all():
newcoaches = group.get_coaches()
for coach in newcoaches:
coaches.append(coach)
for coach in coaches:
if usercoach.rower == coach:
return True
return False
# checks if rower is coach of user (or is user himself)
@rules.predicate
def is_anonymous_or_coach(usercoach,userrower):
print(usercoach,userrower)
if usercoach == userrower:
return True
if userrower.is_anonymous and userrower.is_anonymous:
return True
if not is_coach(usercoach):
return False
r = userrower.rower
coaches = []
for group in r.coachinggroups.all():
newcoaches = group.get_coaches()
for coach in newcoaches:
coaches.append(coach)
for coach in coaches:
if usercoach.rower == coach:
return True
return False
# check if rower and user are members of the same team
@rules.predicate
def is_rower_team_member(user,rower):
if user.rower == rower:
return True
teams = rower.team.all()
for team in teams:
if team.private == 'open':
if team in rower.team.all():
return True
if team.manager == rower.user:
return True
return False
@rules.predicate
def can_add_workout_member(user,rower):
if not user:
return False
if user.is_anonymous:
return False
if user == rower.user:
return True
# only below tested - need test user == rower.user
return is_coach(user) and user.rower in rower.get_coaches()
# check if user can plan for the rower
@rules.predicate
def can_plan_user(user,rower):
# user must have planning permission
if not can_plan(user):
return False
# if has planning permission, can always plan for himself
if rower == user.rower:
return True
teams = user.rower.get_managed_teams()
# free coach, plan etc cannot plan for basic
if not is_paid_coach(user) and user_is_not_basic(user):
for t in teams:
if rower in t.rower.all():
return True
# paying coach can plan for all kinds of rowers
if is_paid_coach(user):
for t in teams:
if rower in t.rower.all():
return True
return False
rules.add_perm('rower.add_plan',can_plan_user) # replaces checkaccessplanuser
rules.add_perm('rower.is_coach',is_coach_user) # replaces checkaccessuser
rules.add_perm('rower.is_pro',ispromember)
rules.add_perm('rower.is_staff',is_staff)
# WORKOUT permissions
"""
WORKOUT permissions
- These methods exist on Workout
- workout.privacy
- These rules apply to workouts
- User can add, delete and change their own workouts
- test_aworkouts
- Coach can add and change workouts for their athletes, but not delete
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_upload
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_upload_not
- Basic, Pro and Plan user can view but cannot add or change an athlete's workout
- test_permissions.PermissionsViewTests.test_coach_edit_athlete_workout
- test_permissions.PermissionsViewTests.test_plan_edit_athlete_upload
- test_permissions.PermissionsViewTests.test_plan_edit_athlete_workout
- test_permissions.PermissionsViewTests.test_basic_edit_athelte_workout
- Anonymous users can view team members' workout (but not see list of workouts)
- test_aworkouts
- Rules for Workouts are transferred to objects related to the Workout,
- Charts
- Video Analysis
- WorkoutComment?
"""
# check if user is owner or coach of owner of workout
@rules.predicate
def is_workout_user(user,workout):
if user.is_anonymous:
return False
try:
r = user.rower
except AttributeError:
return False
if workout.user == r:
return True
return is_coach_user(user,workout.user.user)
# check if user is in same team as owner of workout
@rules.predicate
def is_workout_team(user,workout):
if user.is_anonymous:
return False
try:
r = user.rower
except AttributeError:
return False
if workout.user == r:
return True
return is_rower_team_member(user,workout.user)
# check if user can see workout
@rules.predicate
def can_view_workout(user,workout):
if workout.privacy != 'private':
return True
return user.rower == workout.user
can_change_workout = is_workout_user
rules.add_perm('workout.change_workout',can_change_workout) # replaces checkworkoutuser
rules.add_perm('workout.view_workout',can_view_workout) # replaces checkworkoutuserview
# checkviewworkouts
# PLANNING permissions
"""
- These rules apply to planning
- Free coach can create planned sessions and team planned sessions
- test_permissions.PermissionsViewTests.test_coach_create_session
- Self coach and higher can create planned sessions and team planned sessions
- test_permissions.PermissionsFreeCoach.test_plan_create_session
- test_permissions.PermissionsFreeCoach.test_coach_create_session
- Coach can create planned sessions and team planned sessions
- test_permissions.PermissionsFreeCoach.test_plan_create_session
- test_permissions.PermissionsFreeCoach.test_coach_create_session
- Self Coach and higher can create planned sessions and team planned sessions
- test_permissions.PermissionsViewTests.test_plan_create_session
- Pro or Basic cannot create planned sessions or team planned sessions
- test_permissions.PermissionsFreeCoach.test_pro_create_plannedsession
- test_permissions.PermissionsFreeCoach.test_basic_create_plannedsession
- WHO can comment (plannedsession_comment_view)
- Only Session manager can change session
- Strict View rules (stricter than workouts)
- Basic user cannot manage a training plan
- TrainingTarget
- rules for view, add, change, delete
- TrainingPlan
- rules for view, add, change, delete
- Cycle
- inherits rules from TrainingPlan
- PlannedSession
- rules for view, add, change, delete, clone, save as template
- check team
- Special rules for Race (cannot be edited ex post)
"""
# Training Target rules
# untested can_view_target to can_delete_target
@rules.predicate
def can_view_target(user,target):
if user.is_anonymous:
return False
if user == target.manager.user:
return True
# a target's coach can view as well
if is_coach_user(user,target.manager.user):
return True
# the object can view as well
if user.rower in target.rowers.all():
return True
@rules.predicate
def can_change_target(user,target):
if user.is_anonymous:
return False
return user == target.manager.user
@rules.predicate
def can_delete_target(user,target):
if user.is_anonymous:
return False
return user == target.manager.user
rules.add_perm('target.view_target',can_view_target)
rules.add_perm('target.change_target',can_change_target)
rules.add_perm('target.delete_target',can_delete_target)
@rules.predicate
def can_view_plan(user,plan):
if user.is_anonymous:
return False
if user == plan.manager.user:
return True
# a plan's coach can view as well
# below untested
if is_coach_user(user,plan.manager.user):
return True
# the object can view as well
if user.rower in plan.rowers.all():
return True
@rules.predicate
def can_change_plan(user,plan):
if user.is_anonymous:
return False
return user == plan.manager.user
# below untested
@rules.predicate
def can_delete_plan(user,plan):
if user.is_anonymous:
return False
return user == plan.manager.user
rules.add_perm('plan.view_plan',can_view_plan)
rules.add_perm('plan.change_plan',can_change_plan)
rules.add_perm('plan.delete_plan',can_delete_plan)
rules.add_perm('plan.can_add_plan',can_add_plan)
# untested
@rules.predicate
def can_view_cycle(user,cycle):
try:
return can_view_cycle(user,cycle.plan)
except AttributeError:
return can_view_plan(user,cycle.plan)
return False
@rules.predicate
def can_change_cycle(user,cycle):
try:
return can_change_cycle(user,cycle.plan)
except AttributeError:
return can_change_plan(user,cycle.plan)
return False
@rules.predicate
def can_delete_cycle(user,cycle):
try:
return can_delete_cycle(user,cycle.plan)
except AttributeError:
return can_delete_plan(user,cycle.plan)
return False
rules.add_perm('cycle.view_cycle',can_view_cycle)
rules.add_perm('cycle.change_cycle',can_change_cycle)
rules.add_perm('cycle.delete_cycle',can_delete_cycle)
# check if user has view access to session
@rules.predicate
def can_view_session(user,session):
if session.sessiontype in ['race','indoorrace']:
return True
if user.is_anonymous:
return False
# session manager can view session
if user == session.manager:
return True
# if you're a rower in the session you can view it
# below untested
if user.rower in session.rower.all():
return True
# coach users can view sessions created by their team members
# below untested
if is_coach(user):
teams = user.rower.get_managed_teams()
teamusers = [member.u for member in t.rower.all()]
if session.manager in teamusers:
return True
return False
@rules.predicate
def can_change_session(user,session):
if user.is_anonymous:
return False
# session part of a race should not be changed through the session interface
if session.sessiontype in ['race','indoorrace']:
return False
if user == session.manager:
return True
return False
@rules.predicate
def can_delete_session(user,session):
if user.is_anonymous:
return False
if session.sessiontype in ['race','indoorrace']:
return False
if user == session.manager:
return True
return False
rules.add_perm('plannedsession.add_session',can_add_session)
rules.add_perm('plannedsession.view_session',can_view_session)
rules.add_perm('plannedsession.change_session',can_change_session)
rules.add_perm('plannedsession.delete_session',can_delete_session)
# TEAM (group) permissions
"""
- These methods exist on team
- team.manager
- team.private
- These rules apply to a team
- A Pro or Plan rower can be manager of only 1 team
- test_permissions.PermissionsFreeCoach.test_pro_groupmanager
- test_permissions.PermissionsViewTests.test_pro_groups_create
- A coach can have any number of teams (as manager)
- test_permissions.PermissionsBasicTest.test_coach_groupmanager
- Basic user cannot manage a group
- test_permissions.PermissionsFreeCoach.test_basic_groupmanager
- Basic user cannot join groups led by Free Coach or Pro
- test_permissions.PermissionFreeCoach.test_add_basic_pro_or_plan
- Basic athletes can be member of Coach led group
- test_permissions.PermissionsBasicTest.test_add_coach
- Pro users (and higher) can join team led by other Pro (or higher) user
- test_permissions.PermissionsFreeCoach.test_add_proplan_pro_or_plan
- test_permissions.PermissionsViewTests.test_team_member_request_pro_pro
- test_permissions.PermissionsViewTests.test_team_member_request_basic_pro
- On downgrade, Coach users lose all but their oldest team (add test!)
"""
# check if user is manager of the team
@rules.predicate
def is_team_manager(user,team):
return team.manager == user
# check is user is member of team - untested
@rules.predicate
def is_team_member(user,team):
members = team.rower.all()
return user in [member.user for member in members]
# check if user can view team
@rules.predicate
def can_view_team(user,team):
# user based - below untested
if user.rower.rowerplan == 'basic' and team.manager.rower.rowerplan != 'coach':
return False
# team is public
if team.private == 'open':
return True
# team is private - below untested
return is_team_member(user,team) | is_team_manager(user,team)
@rules.predicate
def can_change_team(user,team):
return is_team_manager(user,team)
@rules.predicate
def can_delete_team(user,team):
return is_team_manager(user,team)
@rules.predicate
def can_join_team(user,team):
return is_paid_coach(team.manager) or ispromember(user)
# For Team functionality
rules.add_perm('teams.view_team',can_view_team)
rules.add_perm('teams.add_team',user_is_not_basic)
rules.add_perm('teams.change_team',can_change_team)
rules.add_perm('teams.delete_team',can_delete_team)
# RACING permissions
"""
- VirtualRace
- rules to add, view, delete, change
- GeoCourse
- rules to add, view, delete, change
- RaceLogo
"""
@rules.predicate
def can_change_course(user,course):
if user.is_anonymous:
return False
return course.manager == user.rower
# untested
@rules.predicate
def can_delete_course(user,course):
if user.is_anonymous:
return False
return course.manager == user.rower
@rules.predicate
def can_delete_logo(user,logo):
if user.is_anonymous:
return False
return logo.user == user
@rules.predicate
def can_change_race(user,race):
if user.is_anonymous:
return False
return race.manager == user
# everybody can view or add a course
rules.add_perm('course.change_course',can_change_course)
rules.add_perm('course.delete_course',can_delete_course)
rules.add_perm('racelogo.delete_logo',can_delete_logo)
# everybody can view a race
rules.add_perm('virtualevent.change_race',can_change_race)
# can races be deleted?
# ANALYSIS permissions
"""
- Conditions
- Alerts
- rules to add, view, delete, change
- cpdata
"""
+3 -1
View File
@@ -7,6 +7,8 @@ from __future__ import unicode_literals, absolute_import
from rowers.imports import * from rowers.imports import *
import re import re
from rowers.rower_rules import is_workout_user
from rowsandall_app.settings import ( from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET, STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
@@ -285,7 +287,7 @@ def workout_runkeeper_upload(user,w):
# ready to upload. Hurray # ready to upload. Hurray
if (checkworkoutuser(user,w)): if (is_workout_user(user,w)):
data = createrunkeeperworkoutdata(w) data = createrunkeeperworkoutdata(w)
if not data: if not data:
message = "Data error in Runkeeper Upload" message = "Data error in Runkeeper Upload"
+2 -1
View File
@@ -18,6 +18,7 @@ from rowsandall_app.settings import (
) )
import rowers.mytypes as mytypes import rowers.mytypes as mytypes
from rowers.rower_rules import is_workout_user
oauth_data = { oauth_data = {
'client_id': SPORTTRACKS_CLIENT_ID, 'client_id': SPORTTRACKS_CLIENT_ID,
@@ -265,7 +266,7 @@ def workout_sporttracks_upload(user,w):
thetoken = sporttracks_open(user) thetoken = sporttracks_open(user)
if (checkworkoutuser(user,w)): if (is_workout_user(user,w)):
data = createsporttracksworkoutdata(w) data = createsporttracksworkoutdata(w)
if not data: if not data:
message = "Data error" message = "Data error"
+4 -2
View File
@@ -18,6 +18,8 @@ queuehigh = django_rq.get_queue('low')
from rowers.dataprep import columndict from rowers.dataprep import columndict
from rowers.rower_rules import is_workout_user,ispromember
import stravalib import stravalib
from stravalib.exc import ActivityUploadFailed,TimeoutExceeded from stravalib.exc import ActivityUploadFailed,TimeoutExceeded
@@ -123,7 +125,7 @@ def get_strava_workout_list(user,limit_n=0):
# gets all new Strava workouts for a rower # gets all new Strava workouts for a rower
def get_strava_workouts(rower): def get_strava_workouts(rower):
if not isprorower(rower): if not ispromember(rower.user):
return 0 return 0
try: try:
@@ -632,7 +634,7 @@ def workout_strava_upload(user,w):
s = "Token doesn't exist. Need to authorize" s = "Token doesn't exist. Need to authorize"
raise NoTokenError("Your hovercraft is full of eels") raise NoTokenError("Your hovercraft is full of eels")
else: else:
if (checkworkoutuser(user,w)): if (is_workout_user(user,w)):
try: try:
tcxfile,tcxmesg = createstravaworkoutdata(w) tcxfile,tcxmesg = createstravaworkoutdata(w)
if tcxfile: if tcxfile:
+17 -34
View File
@@ -26,6 +26,8 @@ from rowers.models import (
CoachingGroup CoachingGroup
) )
from rowers.rower_rules import is_team_manager,is_team_member,is_coach
from rowers.tasks import ( from rowers.tasks import (
handle_sendemail_invite, handle_sendemail_invite,
handle_sendemail_request, handle_sendemail_request,
@@ -60,7 +62,8 @@ def handle_add_workouts_team(ws,t):
return 1 return 1
def update_team(t,name,manager,private,notes,viewing): def update_team(t,name,manager,private,notes,viewing):
if t.manager != manager:
if not is_team_manager(manager,t):
return (0,'You are not the manager of this team') return (0,'You are not the manager of this team')
try: try:
t.name = name t.name = name
@@ -78,7 +81,7 @@ def create_team(name,manager,private='open',notes='',viewing='allmembers'):
if manager.rower.rowerplan == 'basic': if manager.rower.rowerplan == 'basic':
if manager.rower.protrialexpires < timezone.now().date() and manager.rower.plantrialexpires < timezone.now().date(): if manager.rower.protrialexpires < timezone.now().date() and manager.rower.plantrialexpires < timezone.now().date():
return (0,'You need to upgrade to a paid plan to establish a team') return (0,'You need to upgrade to a paid plan to establish a team')
if manager.rower.rowerplan not in ('coach','freecoach'): if not is_coach(manager):
ts = Team.objects.filter(manager=manager) ts = Team.objects.filter(manager=manager)
if len(ts)>=1: if len(ts)>=1:
return (0,'You need to upgrade to the Coach plan to have more than one team') return (0,'You need to upgrade to the Coach plan to have more than one team')
@@ -99,19 +102,6 @@ def remove_team(id):
send_team_delete_mail(t,r) send_team_delete_mail(t,r)
return t.delete() return t.delete()
#def set_teamplanexpires(rower):
# ts = Team.objects.filter(rower=rower)
# texp = datetime.date(timezone.now())
# for t in ts:
# print t.name
# mr = Rower.objects.get(user=t.manager)
# if mr.teamplanexpires > texp:
# rower.teamplanexpires = mr.teamplanexpires
# t.save()
return (1,'Updated rower team expiry') return (1,'Updated rower team expiry')
def add_coach(coach,rower): def add_coach(coach,rower):
@@ -179,24 +169,19 @@ def rower_get_managers(rower):
return managers return managers
def rower_get_coaches(rower): def rower_get_coaches(rower):
coaches = [] return rower.get_coaches()
for group in rower.coachinggroups.all():
try:
coach = Rower.objects.get(mycoachgroup=group)
coaches.append(coach)
except Rower.DoesNotExist:
pass
return coaches
def coach_getcoachees(coach): def coach_getcoachees(coach):
if coach.mycoachgroup and coach.rowerplan in ('coach','freecoach'): rowers = []
return Rower.objects.filter( if coach.mycoachgroup and is_coach(coach.user):
rs = Rower.objects.filter(
coachinggroups__in=[coach.mycoachgroup] coachinggroups__in=[coach.mycoachgroup]
).distinct().order_by("user__last_name","user__first_name") ).distinct().order_by("user__last_name","user__first_name")
else: for r in rs:
return [] rowers.append(r)
return rowers
def coach_remove_athlete(coach,rower): def coach_remove_athlete(coach,rower):
try: try:
@@ -213,7 +198,7 @@ def coach_remove_athlete(coach,rower):
def mgr_remove_member(id,manager,rower): def mgr_remove_member(id,manager,rower):
t = Team.objects.get(id=id) t = Team.objects.get(id=id)
if t.manager == manager: if is_team_manager(manager,t):
remove_member(id,rower) remove_member(id,rower)
send_email_member_dropped(id,rower) send_email_member_dropped(id,rower)
return (id,'Member removed') return (id,'Member removed')
@@ -345,7 +330,7 @@ def create_coaching_offer(coach,user):
def create_invite(team,manager,user=None,email=''): def create_invite(team,manager,user=None,email=''):
r = Rower.objects.get(user=manager) r = Rower.objects.get(user=manager)
if team.manager != manager: if not is_team_manager(manager,team):
return (0,'Not the team manager') return (0,'Not the team manager')
if user: if user:
try: try:
@@ -433,7 +418,7 @@ def revoke_invite(manager,id):
except TeamInvite.DoesNotExist: except TeamInvite.DoesNotExist:
return (0,'The invitation is invalid') return (0,'The invitation is invalid')
if invite.team.manager==manager: if is_team_manager(manager,invite.team):
invite.delete() invite.delete()
return (1,'Invitation revoked') return (1,'Invitation revoked')
else: else:
@@ -445,7 +430,7 @@ def reject_request(manager,id):
except TeamRequest.DoesNotExist: except TeamRequest.DoesNotExist:
return (0,'The request is invalid') return (0,'The request is invalid')
if rekwest.team.manager==manager: if is_team_manager(manager,rekwest.team):
send_request_reject_email(rekwest) send_request_reject_email(rekwest)
rekwest.delete() rekwest.delete()
return (1,'Request rejected') return (1,'Request rejected')
@@ -743,5 +728,3 @@ def send_coachoffer_accepted_email(rekwest):
res = myqueue(queuehigh, res = myqueue(queuehigh,
handle_sendemail_coachoffer_accepted, handle_sendemail_coachoffer_accepted,
coachemail,coachname,name) coachemail,coachname,name)
-2
View File
@@ -46,6 +46,4 @@
</ul> <!-- cd-accordion-menu --> </ul> <!-- cd-accordion-menu -->
{% include 'menuscript.html' %} {% include 'menuscript.html' %}
+2 -2
View File
@@ -44,13 +44,13 @@
{% if user.is_authenticated and user|is_manager and rower %} {% if user.is_authenticated and user|is_manager and rower %}
<p>&nbsp;</p> <p>&nbsp;</p>
{% if user|coach_rowers %} {% if request.user|coach_rowers %}
<ul class="cd-accordion-menu animated"> <ul class="cd-accordion-menu animated">
<li class="has-children" id="athletes"> <li class="has-children" id="athletes">
<input type="checkbox" name="athlete-selector" id="athlete-selector"> <input type="checkbox" name="athlete-selector" id="athlete-selector">
<label for="athlete-selector"><i class="fas fa-users fa-fw"></i>&nbsp;Athletes</label> <label for="athlete-selector"><i class="fas fa-users fa-fw"></i>&nbsp;Athletes</label>
<ul> <ul>
{% for member in user|coach_rowers %} {% for member in request.user|coach_rowers %}
<a href={{ request.path|userurl:member.user }}> <a href={{ request.path|userurl:member.user }}>
<i class="fas fa-user fa-fw"></i> <i class="fas fa-user fa-fw"></i>
{% if member.user == rower.user %} {% if member.user == rower.user %}
+1
View File
@@ -33,3 +33,4 @@
}); });
}); });
</script> </script>
<noscript>Your browser does not support JavaScript</noscript>
+5 -1
View File
@@ -108,6 +108,7 @@
<th>Name</th> <th>Name</th>
<th>Value</th> <th>Value</th>
<th>&nbsp;</th> <th>&nbsp;</th>
<th>Edit</th>
<th>Delete</th> <th>Delete</th>
</tr> </tr>
</thead> </thead>
@@ -125,8 +126,11 @@
</td> </td>
<td> {{ ps.sessionvalue }} </td> <td> {{ ps.sessionvalue }} </td>
<td> {{ ps.sessionunit }} </td> <td> {{ ps.sessionunit }} </td>
<td>
<a class="small" href="/rowers/sessions/{{ ps.id }}/templateedit/">Edit Template</a>
</td>
<td> <td>
<a class="small" href="/rowers/sessions/{{ ps.id }}/deleteconfirm/">Delete</a> <a class="small" href="/rowers/sessions/{{ ps.id }}/deleteconfirm/?next={{ request.path|urlencode }}">Delete</a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@@ -115,6 +115,7 @@
<th>Name</th> <th>Name</th>
<th>Value</th> <th>Value</th>
<th>&nbsp;</th> <th>&nbsp;</th>
<th>Edit</th>
<th>Delete</th> <th>Delete</th>
</tr> </tr>
</thead> </thead>
@@ -132,6 +133,9 @@
</td> </td>
<td> {{ ps.sessionvalue }} </td> <td> {{ ps.sessionvalue }} </td>
<td> {{ ps.sessionunit }} </td> <td> {{ ps.sessionunit }} </td>
<td>
<a class="small" href="/rowers/sessions/{{ ps.id }}/templateedit/">Edit Template</a>
</td>
<td> <td>
<a class="small" href="/rowers/sessions/{{ ps.id }}/deleteconfirm/">Delete</a> <a class="small" href="/rowers/sessions/{{ ps.id }}/deleteconfirm/">Delete</a>
</td> </td>
@@ -0,0 +1,197 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Update Template Session{% endblock %}
{% block main %}
<h1>Edit Session Template</h1>
<ul class="main-content">
<li class="grid_2">
<h2>{{ thesession.name }}</h2>
<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 %}
<p>
<table>
{{ form.as_table }}
</table>
</p>
{% csrf_token %}
<div id="id_guidance">
</div>
<p>
<a href="/rowers/sessions/{{ thesession.id }}/deleteconfirm">Delete</a>
</p>
<input class="button"
action="/rowers/sessions/{{ thesession.id }}/templateedit/" type="submit" value="Save">
</form>
</li>
<li class="grid_2">
<h1>Templates</h1>
<p>
Click on session name to clone to current period
</p>
<table class="listtable shortpadded" width="80%">
<thead>
<tr>
<th>Name</th>
<th>Value</th>
<th>&nbsp;</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{% for ps in sessiontemplates %}
<tr>
<td>
{% if ps.name != '' %}
<a class="small"
href="/rowers/sessions/{{ ps.id }}/clone/user/{{ rower.user.id }}/?when={{ timeperiod }}">{{ ps.name }}</a>
{% else %}
<a class="small"
href="/rowers/sessions/{{ ps.id }}/clone/user/{{ rower.user.id }}/?when={{ timeperiod }}">Unnamed Session</a>
{% endif %}
</td>
<td> {{ ps.sessionvalue }} </td>
<td> {{ ps.sessionunit }} </td>
<td>
<a class="small" href="/rowers/sessions/{{ ps.id }}/templateedit/">Edit Template</a>
</td>
<td>
<a class="small" href="/rowers/sessions/{{ ps.id }}/deleteconfirm/">Delete</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</li>
</ul>
{% endblock %}
{% block sidebar %}
{% include 'menu_plan.html' %}
{% endblock %}
{% block scripts %}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
$(document).ready(function(){
var o = $("td #id_sessiontype").find(":selected").val();
if (o != 'coursetest') {
$("td #id_course").hide();
$("th label[for='id_course']").hide();
} else {
$("td #id_course").show();
$("th label[for='id_course']").show();
}
$("td #id_sessionmode").change(function() {
if (this.value == 'TRIMP') {
$("td #id_sessionunit").prop("value","None");
$('#id_guidance').html("<p>TRIMP has no unit</p>");
}
if (this.value == 'distance') {
$("td #id_sessionunit").prop("value","m");
$('#id_guidance').html("<p>Distance: Set value to meters</p>");
}
if (this.value == 'time') {
$("td #id_sessionunit").prop("value","min");
$('#id_guidance').html("<p>Time: Set value to minutes</p>");
}
if (this.value == 'rScore') {
$("td #id_sessionunit").prop("value","None");
$('#id_guidance').html("<p>rScore has no unit</p>");
}
});
$("td #id_sessiontype").change(function() {
if (this.value == 'session') {
$("td #id_criterium").prop("value","none");
$('#id_guidance').html("<p>For Training Sessions, the default criterium is 'Approximately'</p>");
}
if (this.value == 'test') {
$("td #id_criterium").prop("value","exact");
$("td #id_sessionmode").prop("value","distance");
$("td #id_sessionunit").prop("value","m");
$('#id_guidance').html("<p>Set mode to distance. For Mandatory Tests, only distance or time are allowed.</p><p>For Mandatory Tests, the only criterium is 'Exactly'</p>");
}
if (this.value == 'coursetest') {
$("th label[for='id_course']").show();
$("td #id_course").show();
$("td #id_criterium").prop("value","none");
$("td #id_sessionmode").prop("value","distance");
$("td #id_sessionunit").prop("value","m");
$('#id_guidance').html("<p>Set mode to distance. For OTW Tests, only distance is allowed.</p><p>The exact value is not relevant because it is calculated from the course.</p>");
}
if (this.value != 'coursetest') {
$("th label[for='id_course']").hide();
$("td #id_course").hide();
}
if (this.value == 'challenge') {
$("td #id_criterium").prop("value","minimum");
$('#id_guidance').html("<p>For Challenges, the default criterium is 'At Least'</p>");
}
if (this.value == 'cycletarget') {
$("td #id_criterium").prop("value","none");
$('#id_guidance').html("<p>For Cycle Targets, the default criterium is 'Approximately'</p>");
}
}
);
$("td #id_sessionunit").change(function() {
if (this.value == 'm') {
$("td #id_sessionmode").prop("value","distance");
$('#id_guidance').html("<p>Mode was set to distance</p>");
}
if (this.value == 'km') {
$("td #id_sessionmode").prop("value","distance");
$('#id_guidance').html("<p>Mode was set to distance</p>");
}
if (this.value == 'None') {
$("td #id_sessionmode").prop("value","rScore");
$('#id_guidance').html("<p>Mode was set to rScore</p>");
}
if (this.value == 'min') {
$("td #id_sessionmode").prop("value","time");
$('#id_guidance').html("<p>Mode was set to time</p>");
}
}
);
});
</script>
{% endblock %}
+2
View File
@@ -16,6 +16,8 @@
<p> <p>
<a href="/rowers/sessions/{{ psdict.id.1 }}/edit/user/{{ rower.user.id }}"> <a href="/rowers/sessions/{{ psdict.id.1 }}/edit/user/{{ rower.user.id }}">
Edit Session</a> Edit Session</a>
/
<a href="/rowers/sessions/{{ psdict.id.1 }}/maketemplate/?next={{ request.path|urlencode }}">Save to template</a>
</p> </p>
{% endif %} {% endif %}
<h1>Session {{ psdict.name.1 }}</h1> <h1>Session {{ psdict.name.1 }}</h1>
+6 -7
View File
@@ -21,7 +21,7 @@ from rowers.plannedsessions import (
from rowers import c2stuff, runkeeperstuff from rowers import c2stuff, runkeeperstuff
from rowers.c2stuff import c2_open from rowers.c2stuff import c2_open
from rowers.runkeeperstuff import runkeeper_open from rowers.runkeeperstuff import runkeeper_open
from rowers.models import checkaccessuser from rowers.rower_rules import is_coach_user, is_workout_user, isplanmember,ispromember
from rowers.mytypes import otwtypes from rowers.mytypes import otwtypes
from rowers.utils import NoTokenError from rowers.utils import NoTokenError
@@ -385,14 +385,13 @@ def is_session_manager(id,user):
return ps.manager == user return ps.manager == user
from rowers.models import checkworkoutuser
@register.filter @register.filter
def may_edit(workout,request): def may_edit(workout,request):
mayedit = 0 mayedit = 0
if request.user == workout.user.user: if request.user == workout.user.user:
mayedit = True mayedit = True
if checkworkoutuser(request.user,workout): if is_workout_user(request.user,workout):
mayedit = True mayedit = True
return mayedit return mayedit
@@ -413,7 +412,7 @@ def mayeditplan(obj,request):
return request.user == obj.manager.user return request.user == obj.manager.user
rr = Rower.objects.get(user=request.user) rr = Rower.objects.get(user=request.user)
if checkaccessuser(request.user,obj.rower) and rr.rowerplan not in ['basic','pro']: if is_coach_user(request.user,obj.rower) and rr.rowerplan not in ['basic','pro']:
mayedit = True mayedit = True
@@ -431,7 +430,7 @@ def get_field_id(id,s,form):
from rowers.models import Rower,Team,TrainingPlan,TrainingTarget from rowers.models import Rower,Team,TrainingPlan,TrainingTarget
from rowers.views import ispromember
@register.filter @register.filter
def is_promember(user): def is_promember(user):
return ispromember(user) return ispromember(user)
@@ -441,10 +440,10 @@ def is_manager(user):
r = Rower.objects.get(user=user) r = Rower.objects.get(user=user)
return 'coach' in r.rowerplan return 'coach' in r.rowerplan
from rowers.views import hasplannedsessions
@register.filter @register.filter
def is_planmember(user): def is_planmember(user):
return hasplannedsessions(user) return isplanmember(user)
@register.filter @register.filter
def get_age(r): def get_age(r):
+6 -1
View File
@@ -18,7 +18,7 @@ from django.test import TestCase, Client,override_settings
from django.core.management import call_command from django.core.management import call_command
from django.utils.six import StringIO from django.utils.six import StringIO
from django.test.client import RequestFactory from django.test.client import RequestFactory
from rowers.views import checkworkoutuser,c2_open from rowers.views import c2_open
from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage
from rowers.forms import DocumentsForm,CNsummaryForm,RegistrationFormUniqueEmail from rowers.forms import DocumentsForm,CNsummaryForm,RegistrationFormUniqueEmail
import rowers.plots as plots import rowers.plots as plots
@@ -69,6 +69,10 @@ def mocked_myqueue(*args, **kwargs):
return Job() return Job()
def mock_c2open(*args, **kwargs):
print('mock')
return('aap')
def mocked_sqlalchemy(*args, **kwargs): def mocked_sqlalchemy(*args, **kwargs):
# return object with method # return object with method
@@ -606,6 +610,7 @@ def mocked_requests(*args, **kwargs):
def __init__(self, json_data, status_code): def __init__(self, json_data, status_code):
self.json_data = json_data self.json_data = json_data
self.status_code = status_code self.status_code = status_code
self.ok = True
def json(self): def json(self):
return self.json_data return self.json_data
+1 -3
View File
@@ -26,7 +26,7 @@ from django.core.management import call_command
from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils.six import StringIO from django.utils.six import StringIO
from django.test.client import RequestFactory from django.test.client import RequestFactory
from rowers.views import checkworkoutuser,c2_open, multi_compare_view from rowers.views import c2_open, multi_compare_view
from rowers.forms import ( from rowers.forms import (
DocumentsForm,CNsummaryForm,RegistrationFormUniqueEmail, DocumentsForm,CNsummaryForm,RegistrationFormUniqueEmail,
@@ -220,5 +220,3 @@ def cleanup(request):
request.addfinalizer(remove_test_files) request.addfinalizer(remove_test_files)
+2 -2
View File
@@ -242,9 +242,9 @@ class WorkoutViewTest(TestCase):
@patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.getsmallrowdata_db') @patch('rowers.dataprep.getsmallrowdata_db')
# @patch('rowers.weather.requests.get', side_effect=mocked_requests) @patch('rowers.weather.requests.get', side_effect=mocked_requests)
def test_waterworkout_view(self, def test_waterworkout_view(self,
mocked_sqlalchemy, mocked_getsmallrowdata_db): mocked_sqlalchemy, mocked_getsmallrowdata_db, mocked_requests):
login = self.c.login(username=self.u.username, password=self.password) login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login) self.assertTrue(login)
+2
View File
@@ -20,6 +20,8 @@ class SimpleViewTest(TestCase):
birthdate=faker.profile()['birthdate'], birthdate=faker.profile()['birthdate'],
gdproptin=True,surveydone=True, gdproptin=True,surveydone=True,
gdproptindate=timezone.now(), gdproptindate=timezone.now(),
protrialexpires=datetime.date(1970,1,1),
plantrialexpires=datetime.date(1970,1,1),
rowerplan='basic') rowerplan='basic')
self.c = Client() self.c = Client()
+2 -2
View File
@@ -8,7 +8,7 @@ from .statements import *
nu = datetime.datetime.now() nu = datetime.datetime.now()
from rowers.rower_rules import is_workout_user
#@pytest.mark.django_db #@pytest.mark.django_db
class WorkoutTests(TestCase): class WorkoutTests(TestCase):
@@ -28,7 +28,7 @@ class WorkoutTests(TestCase):
duration="0:55:00",distance=8000) duration="0:55:00",distance=8000)
def test_checkworkoutuser(self): def test_checkworkoutuser(self):
self.assertEqual(checkworkoutuser(self.u,self.w),True) self.assertEqual(is_workout_user(self.u,self.w),True)
#@pytest.mark.django_db #@pytest.mark.django_db
class C2Tests(TestCase): class C2Tests(TestCase):
+242 -21
View File
@@ -13,6 +13,87 @@ from django.db import transaction
import rowers.teams as teams import rowers.teams as teams
import rowers.plannedsessions as plannedsessions import rowers.plannedsessions as plannedsessions
from rowers.rower_rules import is_coach_user
@override_settings(TESTING=True)
class PlanningPermissionsCoach(TestCase):
def setUp(self):
self.c = Client()
self.ucoach = UserFactory()
self.rcoach = Rower.objects.create(
user=self.ucoach,
birthdate=faker.profile()['birthdate'],
gdproptin=True,surveydone=True,
gdproptindate=timezone.now(),
rowerplan='coach',
clubsize=10,
)
self.ucoachpassword = faker.word()
self.ucoach.set_password(self.ucoachpassword)
self.ucoach.save()
self.ucoachee = UserFactory()
self.rcoachee = Rower.objects.create(
user=self.ucoachee,
birthdate=faker.profile()['birthdate'],
gdproptin=True,surveydone=True,
gdproptindate=timezone.now(),
rowerplan='basic'
)
self.coachgroup = CoachingGroup.objects.create(name=self.ucoach.first_name)
self.rcoach.mycoachgroup = self.coachgroup
self.rcoach.save()
self.team = Team.objects.create(
name=faker.word()+'3',
notes=faker.text(),
manager=self.ucoach)
# coachee is in coach's team
self.rcoachee.team.add(self.team)
# coachee is coached by coach
self.rcoachee.coachinggroups.add(self.rcoach.mycoachgroup)
self.rcoachee.save()
self.ugroupmember = UserFactory()
self.rgroupmember = Rower.objects.create(
user=self.ugroupmember,
birthdate=faker.profile()['birthdate'],
gdproptin=True,surveydone=True,
gdproptindate=timezone.now(),
rowerplan='basic'
)
# groupmember is in coach's team (but not coached)
self.rgroupmember.team.add(self.team)
# def test_target(self):
def test_trainingplan(self):
plan = TrainingPlan(
name='test plan',
manager=self.ucoach.rower,
)
plan.save()
# can add self and coachee
plan.rowers.add(self.ucoach.rower)
plan.rowers.add(self.ucoachee.rower)
# test 1
athletes = plan.rowers.all()
self.assertTrue(self.ucoach.rower in athletes)
self.assertTrue(self.ucoachee.rower in athletes)
# can add groupmember if not basic
plan.rowers.add(self.rgroupmember)
athletes = plan.rowers.all()
self.assertTrue(self.rgroupmember in athletes)
@override_settings(TESTING=True) @override_settings(TESTING=True)
class PermissionsFreeCoach(TestCase): class PermissionsFreeCoach(TestCase):
@@ -528,6 +609,8 @@ class PermissionsViewTests(TestCase):
rowerplan='basic') rowerplan='basic')
self.ubasic_workouts = WorkoutFactory.create_batch(5, user=self.rbasic) self.ubasic_workouts = WorkoutFactory.create_batch(5, user=self.rbasic)
self.ubasic_workouts[0].privacy == 'private'
self.factory = RequestFactory() self.factory = RequestFactory()
self.ubasicpassword = faker.word() self.ubasicpassword = faker.word()
self.ubasic.set_password(self.ubasicpassword) self.ubasic.set_password(self.ubasicpassword)
@@ -553,6 +636,24 @@ class PermissionsViewTests(TestCase):
manager=self.ucoach) manager=self.ucoach)
## only ubasic can view ubasic_workouts[0] which is private
def test_view_workout(self):
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
url = reverse('workout_view',
kwargs={'id':self.ubasic_workouts[0].id})
response = self.c.get(url)
self.assertTrue(response.status_code,403)
login = self.c.login(username=self.ubasic.username, password=self.ubasicpassword)
self.assertTrue(login)
url = reverse('workout_view',
kwargs={'id':self.ubasic_workouts[0].id})
response = self.c.get(url)
self.assertTrue(response.status_code,200)
## Coach can have any number of groups ## Coach can have any number of groups
def test_coach_groups_create(self): def test_coach_groups_create(self):
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword) login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
@@ -644,7 +745,8 @@ class PermissionsViewTests(TestCase):
## Coach can edit on behalf of athlete if permitted ## Coach can edit on behalf of athlete if permitted
def test_coach_edit_athlete_settings(self): # 1, Preferences
def test_coach_edit_athlete_prefs(self):
self.rbasic.team.add(self.teamcoach) self.rbasic.team.add(self.teamcoach)
self.rbasic.coachinggroups.add(self.coachinggroup) self.rbasic.coachinggroups.add(self.coachinggroup)
@@ -656,7 +758,7 @@ class PermissionsViewTests(TestCase):
response = self.c.get(url) response = self.c.get(url)
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)
def test_coach_edit_athlete_settings_not(self): def test_coach_edit_athlete_prefs_not(self):
self.rbasic.team.add(self.teamcoach) self.rbasic.team.add(self.teamcoach)
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword) login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
@@ -667,6 +769,78 @@ class PermissionsViewTests(TestCase):
response = self.c.get(url) response = self.c.get(url)
self.assertEqual(response.status_code,403) self.assertEqual(response.status_code,403)
# 2, Favorite Charts
def test_coach_edit_athlete_settings(self):
self.rbasic.team.add(self.teamcoach)
self.rbasic.coachinggroups.add(self.coachinggroup)
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
url = reverse('rower_favoritecharts_view',kwargs={'userid':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,200)
def test_coach_edit_athlete_settings_not(self):
self.rbasic.team.add(self.teamcoach)
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
url = reverse('rower_favoritecharts_view',kwargs={'userid':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,403)
# 3, Export Settings
def test_coach_edit_athlete_exportsettings(self):
self.rbasic.team.add(self.teamcoach)
self.rbasic.coachinggroups.add(self.coachinggroup)
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
url = reverse('rower_exportsettings_view',kwargs={'userid':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,200)
def test_coach_edit_athlete_exportsettings_not(self):
self.rbasic.team.add(self.teamcoach)
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
url = reverse('rower_exportsettings_view',kwargs={'userid':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,403)
# 4, Account settings
def test_coach_edit_athlete_account(self):
self.rbasic.team.add(self.teamcoach)
self.rbasic.coachinggroups.add(self.coachinggroup)
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
url = reverse('rower_edit_view',kwargs={'userid':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,200)
def test_coach_edit_athlete_account_not(self):
self.rbasic.team.add(self.teamcoach)
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
url = reverse('rower_edit_view',kwargs={'userid':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,403)
## Coach can run analytics for athlete ## Coach can run analytics for athlete
@patch('rowers.dataprep.read_cols_df_sql', side_effect = mocked_read_df_cols_sql_multistats) @patch('rowers.dataprep.read_cols_df_sql', side_effect = mocked_read_df_cols_sql_multistats)
def test_coach_edit_athlete_analysis(self,mocked_df): def test_coach_edit_athlete_analysis(self,mocked_df):
@@ -679,7 +853,7 @@ class PermissionsViewTests(TestCase):
url = reverse('cumstats', url = reverse('cumstats',
kwargs={ kwargs={
'theuser':self.ubasic.id, 'userid':self.ubasic.id,
} }
) )
@@ -687,6 +861,8 @@ class PermissionsViewTests(TestCase):
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)
@patch('rowers.dataprep.read_cols_df_sql', side_effect = mocked_read_df_cols_sql_multistats) @patch('rowers.dataprep.read_cols_df_sql', side_effect = mocked_read_df_cols_sql_multistats)
def test_coach_edit_athlete_analysis_not(self,mocked_df): def test_coach_edit_athlete_analysis_not(self,mocked_df):
self.rbasic.team.add(self.teamcoach) self.rbasic.team.add(self.teamcoach)
@@ -697,7 +873,7 @@ class PermissionsViewTests(TestCase):
url = reverse('cumstats', url = reverse('cumstats',
kwargs={ kwargs={
'theuser':self.ubasic.id, 'userid':self.ubasic.id,
} }
) )
@@ -705,6 +881,24 @@ class PermissionsViewTests(TestCase):
self.assertEqual(response.status_code,403) self.assertEqual(response.status_code,403)
@patch('rowers.dataprep.read_cols_df_sql', side_effect = mocked_read_df_cols_sql_multistats)
def test_coach_edit_athlete_analysis_not2(self,mocked_df):
self.rbasic.team.add(self.teamcoach)
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
url = reverse('analysis_new',
kwargs={
'userid':self.ubasic.id,
}
)
response = self.c.get(url)
self.assertEqual(response.status_code,403)
## Coach can upload on behalf of athlete - if team allows ## Coach can upload on behalf of athlete - if team allows
@patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.create_engine')
@@ -810,6 +1004,32 @@ class PermissionsViewTests(TestCase):
response = self.c.get(url) response = self.c.get(url)
self.assertEqual(response.status_code,403) self.assertEqual(response.status_code,403)
## Coach can see list of workouts of athlete
def test_coach_athlete_workout_list(self):
self.rbasic.team.add(self.teamcoach)
login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword)
self.assertTrue(login)
url = reverse('workouts_view',
kwargs={'rowerid':self.rbasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,200)
url = reverse('workouts_view',
kwargs={'userid':self.ubasic.id})
response = self.c.get(url)
self.assertEqual(response.status_code,200)
url = reverse('workouts_view')
response = self.c.get(url)
self.assertEqual(response.status_code,200)
## Self coach can create one group ## Self coach can create one group
## Self coach cannot create more than one group ## Self coach cannot create more than one group
def test_plan_groups_create(self): def test_plan_groups_create(self):
@@ -857,7 +1077,7 @@ class PermissionsViewTests(TestCase):
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
expected_url = reverse('paidplans') expected_url = reverse('paidplans_view')
response = self.c.post(url,form_data,follow=True) response = self.c.post(url,form_data,follow=True)
self.assertRedirects(response, self.assertRedirects(response,
@@ -924,7 +1144,7 @@ class PermissionsViewTests(TestCase):
url = reverse('cumstats', url = reverse('cumstats',
kwargs={ kwargs={
'theuser':self.ubasic.id, 'userid':self.ubasic.id,
} }
) )
@@ -994,7 +1214,7 @@ class PermissionsViewTests(TestCase):
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
expected_url = reverse('paidplans') expected_url = reverse('paidplans_view')
response = self.c.post(url,form_data,follow=True) response = self.c.post(url,form_data,follow=True)
self.assertRedirects(response, self.assertRedirects(response,
@@ -1016,7 +1236,7 @@ class PermissionsViewTests(TestCase):
response = self.c.get(url,follow=True) response = self.c.get(url,follow=True)
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)
expected_url = reverse('paidplans') expected_url = reverse('paidplans_view')
self.assertRedirects(response, self.assertRedirects(response,
expected_url=expected_url, expected_url=expected_url,
@@ -1038,21 +1258,22 @@ class PermissionsViewTests(TestCase):
## Pro cannot run analytics on behalf of athlete ## Pro cannot run analytics on behalf of athlete
@patch('rowers.dataprep.read_cols_df_sql', side_effect = mocked_read_df_cols_sql_multistats) @patch('rowers.dataprep.read_cols_df_sql', side_effect = mocked_read_df_cols_sql_multistats)
def test_pro_edit_athlete_analysis(self,mocked_df): def test_pro_edit_athlete_analysis(self,mocked_df):
self.rpro.team.add(self.teampro) with transaction.atomic():
self.rpro.team.add(self.teampro)
login = self.c.login(username=self.upro2.username, password=self.upro2password) login = self.c.login(username=self.upro2.username, password=self.upro2password)
self.assertTrue(login) self.assertTrue(login)
url = reverse('cumstats', url = reverse('cumstats',
kwargs={ kwargs={
'theuser':self.ubasic.id, 'userid':self.ubasic.id,
} }
) )
response = self.c.get(url) response = self.c.get(url)
self.assertEqual(response.status_code,403) self.assertEqual(response.status_code,403)
## Self Coach cannot upload on behalf of athlete ## Self Coach cannot upload on behalf of athlete
@patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.create_engine')
@@ -1068,7 +1289,7 @@ class PermissionsViewTests(TestCase):
response = self.c.get(url,follow=True) response = self.c.get(url,follow=True)
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)
expected_url = reverse('paidplans') expected_url = reverse('paidplans_view')
self.assertRedirects(response, self.assertRedirects(response,
expected_url = expected_url, expected_url = expected_url,
status_code=302,target_status_code=200) status_code=302,target_status_code=200)
@@ -1180,7 +1401,7 @@ class PermissionsViewTests(TestCase):
response = self.c.get(url,follow=True) response = self.c.get(url,follow=True)
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)
expected_url = reverse('team_view',kwargs={'id':self.teampro.id}) expected_url = reverse('team_view',kwargs={'team_id':self.teampro.id})
self.assertRedirects(response, self.assertRedirects(response,
expected_url = expected_url, expected_url = expected_url,
@@ -1200,7 +1421,7 @@ class PermissionsViewTests(TestCase):
response = self.c.get(url,follow=True) response = self.c.get(url,follow=True)
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)
expected_url = reverse('paidplans') expected_url = reverse('paidplans_view')
self.assertRedirects(response, self.assertRedirects(response,
expected_url = expected_url, expected_url = expected_url,
status_code=302,target_status_code=200) status_code=302,target_status_code=200)
+501
View File
@@ -0,0 +1,501 @@
from .statements import *
viewstotest = [
('workout_view',
{
'anonymous': True,
'anonymous_response':200,
'own': 'basic',
'own_response': 200,
'own_nonperm': 302,
'member': 'basic',
'member_response': 200,
'member_nonperm': 404,
'coachee': 'coach',
'coachee_response': 200,
'coachee_nonperm': 404,
'is_staff': False,
'workoutid': True,
'userid': False,
'dotest': True,
}
),
]
import pandas as pd
df = pd.read_csv('./rowers/tests/viewnames.csv')
for id, row in df.iterrows():
view = row['view']
tpl = (view,
{
'anonymous':row.anonymous,
'anonymous_response':row.anonymous_response,
'own': row.own,
'own_response':row.own_response,
'own_nonperm': row.own_nonperm,
'member': row.member,
'member_response':row.member_response,
'member_nonperm': row.member_nonperm,
'coachee': row.coachee,
'coachee_response':row.coachee_response,
'coachee_nonperm': row.coachee_nonperm,
'is_staff':row.is_staff,
'workoutid':row.workoutid,
'userid':row.userid,
'dotest':row.dotest,
})
if row.dotest:
viewstotest.append(tpl)
plans = ['basic','plan','coach','pro']
@override_settings(TESTING=True)
class PermissionsViewTests(TestCase):
def setUp(self):
self.c = Client()
## Users - Pro, Basic, Coach & Self Coach
ucoach = UserFactory(username='coachuser')
rcoach = Rower.objects.create(user=ucoach,
birthdate=faker.profile()['birthdate'],
gdproptin=True,surveydone=True,gdproptindate=timezone.now(),
rowerplan='coach',clubsize=10)
ucoach_workouts = WorkoutFactory.create_batch(5, user=rcoach)
coachinggroup = CoachingGroup.objects.create()
rcoach.mycoachgroup = coachinggroup
rcoach.save()
ucoachpassword = faker.word()
ucoach.set_password(ucoachpassword)
ucoach.save()
self.ucoach = {
'user': ucoach,
'password': ucoachpassword,
'username': ucoach.username,
'workouts': ucoach_workouts,
}
uplan = UserFactory(username='planuser')
rplan = Rower.objects.create(user=uplan,
birthdate=faker.profile()['birthdate'],
gdproptin=True,surveydone=True,gdproptindate=timezone.now(),
rowerplan='plan',clubsize=10)
uplan_workouts = WorkoutFactory.create_batch(5, user=rplan)
rcoach.save()
uplanpassword = faker.word()
uplan.set_password(uplanpassword)
uplan.save()
self.uplan = {
'user': uplan,
'password': uplanpassword,
'username': uplan.username,
'workouts': uplan_workouts,
}
uplan2 = UserFactory(username='plan2user')
rplan2 = Rower.objects.create(user=uplan2,
birthdate=faker.profile()['birthdate'],
gdproptin=True,surveydone=True,gdproptindate=timezone.now(),
rowerplan='plan',clubsize=10)
uplan2_workouts = WorkoutFactory.create_batch(5, user=rplan2)
rcoach.save()
uplan2password = faker.word()
uplan2.set_password(uplan2password)
uplan2.save()
self.uplan2 = {
'user': uplan2,
'password': uplan2password,
'username': uplan2.username,
'workouts': uplan2_workouts,
}
upro = UserFactory(username='prouser')
rpro = Rower.objects.create(user=upro,
birthdate=faker.profile()['birthdate'],
gdproptin=True,surveydone=True,gdproptindate=timezone.now(),
rowerplan='pro',clubsize=10)
upro_workouts = WorkoutFactory.create_batch(5, user=rpro)
rcoach.save()
upropassword = faker.word()
upro.set_password(upropassword)
upro.save()
self.upro = {
'user': upro,
'password': upropassword,
'username': upro.username,
'workouts': upro_workouts,
}
ubasic = UserFactory(username='basicuser')
rbasic = Rower.objects.create(user=ubasic,
birthdate=faker.profile()['birthdate'],
gdproptin=True,surveydone=True,gdproptindate=timezone.now(),
rowerplan='basic',clubsize=10)
ubasic_workouts = WorkoutFactory.create_batch(5, user=rbasic)
rcoach.save()
ubasicpassword = faker.word()
ubasic.set_password(ubasicpassword)
ubasic.save()
self.ubasic = {
'user': ubasic,
'password': ubasicpassword,
'username': ubasic.username,
'workouts': ubasic_workouts,
}
ustrange = UserFactory(username='strangeuser')
rstrange = Rower.objects.create(user=ustrange,
birthdate=faker.profile()['birthdate'],
gdproptin=True,surveydone=True,gdproptindate=timezone.now(),
rowerplan='basic',clubsize=10)
ustrange_workouts = WorkoutFactory.create_batch(5, user=rstrange)
rcoach.save()
ustrangepassword = faker.word()
ustrange.set_password(ustrangepassword)
ustrange.save()
self.ustrange = {
'user': ustrange,
'password': ustrangepassword,
'username': ustrange.username,
'workouts': ustrange_workouts,
}
## TeamPro, TeamCoach, TeamSelfCoach
self.teampro = Team.objects.create(
name=faker.word(),
notes=faker.text(),
manager=self.upro['user'])
self.teamplan = Team.objects.create(
name=faker.word()+'2',
notes=faker.text(),
manager=self.uplan2['user'])
self.teamcoach = Team.objects.create(
name=faker.word()+'3',
notes=faker.text(),
manager=self.ucoach['user'])
self.uplan['user'].rower.team.add(self.teamcoach)
self.ubasic['user'].rower.team.add(self.teamcoach)
self.ucoach['user'].rower.team.add(self.teamcoach)
self.upro['user'].rower.team.add(self.teamcoach)
self.upro['user'].rower.team.add(self.teamplan)
self.uplan2['user'].rower.team.add(self.teamplan)
self.upro['user'].rower.team.add(self.teampro)
self.uplan['user'].rower.team.add(self.teampro)
self.uplan['user'].rower.coachinggroups.add(coachinggroup)
self.ubasic['user'].rower.coachinggroups.add(coachinggroup)
# Test access for anonymous users
@parameterized.expand(viewstotest)
@patch('rowers.c2stuff.Session', side_effect=mocked_requests)
@patch('rowers.c2stuff.c2_open')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('requests.get',side_effect=mocked_requests)
@patch('requests.post',side_effect=mocked_requests)
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
def test_permissions_anonymous(
self,view,permissions,
mock_Session,
mock_c2open,
mocked_sqlalchemy,
mocked_read_df_sql,
mocked_getsmallrowdata_db,
mock_get,
mock_post,
mocked_get_video_data,
):
print()
print(view,'anonymous',permissions['anonymous'],permissions['is_staff'],permissions['dotest'])
print()
if permissions['anonymous'] and not permissions['is_staff'] and permissions['dotest']:
urlstotest = []
if permissions['workoutid']:
url = reverse(view,kwargs={'id':encoder.encode_hex(self.ucoach['workouts'][0].id)})
urlstotest.append(url)
else:
url = reverse(view)
urlstotest.append(url)
print(view,'anonymous',urlstotest)
for url in urlstotest:
result = self.c.get(url)
self.assertEqual(result.status_code,permissions['anonymous_response'])
# Test access for logged in users - accessing own objects
@parameterized.expand(viewstotest)
@patch('rowers.c2stuff.Session', side_effect=mocked_requests)
@patch('rowers.c2stuff.c2_open')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('requests.get',side_effect=mocked_requests)
@patch('requests.post',side_effect=mocked_requests)
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
def test_permissions_own(
self,view,permissions,
mock_Session,
mock_c2open,
mocked_sqlalchemy,
mocked_read_df_sql,
mocked_getsmallrowdata_db,
mock_get,
mock_post,
mocked_get_video_data,
):
print()
print(view,'own')
print()
if permissions['own'] in plans and not permissions['is_staff'] and permissions['dotest']:
urlstotest = []
falseurlstotest = []
otheruserurls = []
thisuser = self.ubasic
memberuser = self.uplan
notuser = None
print(view,'own')
if permissions['own'] == 'basic':
thisuser = self.ubasic
memberuser = self.uplan
notuser = None
elif permissions['own'] == 'pro':
thisuser = self.upro
notuser = self.ubasic
elif permissions['own'] == 'plan':
thisuser = self.uplan
notuser = self.upro
elif permissions['own'] == 'coach':
thisuser = self.ucoach
notuser = self.uplan
if permissions['workoutid']:
workouts = thisuser['workouts']
url = reverse(view,kwargs={'id':encoder.encode_hex(workouts[0].id)})
urlstotest.append(url)
if notuser:
workouts = notuser['workouts']
url = reverse(view,kwargs={'id':encoder.encode_hex(workouts[0].id)})
falseurlstotest.append(url)
elif permissions['userid']:
url = reverse(view,kwargs={'userid':thisuser['user'].id})
urlstotest.append(url)
url = reverse(view,kwargs={'userid':self.ustrange['user'].id})
otheruserurls.append(url)
else:
url = reverse(view)
urlstotest.append(url)
print(view,'own',urlstotest,falseurlstotest,otheruserurls)
# test logged in as user who has permissions
for url in urlstotest:
login = self.c.login(username = thisuser['username'],password = thisuser['password'])
result = self.c.get(url)
self.assertEqual(result.status_code, permissions['own_response'])
# test logged as user with no permissions (e.g. too low plan)
for url in falseurlstotest:
login = self.c.login(username = notuser['username'],password = notuser['password'])
result = self.c.get(url)
self.assertEqual(result.status_code, permissions['own_nonperm'])
# test as user with permissions, accessing object of non-related user
for url in otheruserurls:
login = self.c.login(username=thisuser['username'],password = thisuser['password'])
result = self.c.get(url)
self.assertEqual(result.status_code,403)
# Test access for logged in users - accessing team member objects
@parameterized.expand(viewstotest)
@patch('rowers.c2stuff.Session', side_effect=mocked_requests)
@patch('rowers.c2stuff.c2_open')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('requests.get',side_effect=mocked_requests)
@patch('requests.post',side_effect=mocked_requests)
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
def test_permissions_member(
self,view,permissions,
mock_Session,
mock_c2open,
mocked_sqlalchemy,
mocked_read_df_sql,
mocked_getsmallrowdata_db,
mock_get,
mock_post,
mocked_get_video_data,
):
print()
print(view,'member')
print()
if permissions['member'] in plans and permissions['is_staff'] is False and permissions['dotest']:
urlstotest = []
falseurlstotest = []
otheruserurls = []
thisuser = self.ubasic
memberuser = self.uplan
notuser = None
print(view,'member')
if permissions['member'] == 'basic':
thisuser = self.ubasic
memberuser = self.uplan
notuser = None
elif permissions['member'] == 'pro':
thisuser = self.upro
memberuser = self.uplan
notuser = self.ubasic
elif permissions['member'] == 'plan':
thisuser = self.uplan
memberuser = self.ubasic
notuser = self.upro
elif permissions['member'] == 'coach':
thisuser = self.ucoach
memberuser = self.uplan
notuser = self.uplan
if permissions['workoutid']:
workouts = memberuser['workouts']
url = reverse(view,kwargs={'id':encoder.encode_hex(workouts[0].id)})
urlstotest.append(url)
if notuser:
falseurlstotest.append(url)
elif permissions['userid']:
url = reverse(view,kwargs={'userid':memberuser['user'].id})
urlstotest.append(url)
url = reverse(view,kwargs={'userid':self.ustrange['user'].id})
otheruserurls.append(url)
else:
url = reverse(view)
urlstotest.append(url)
print('member',urlstotest,falseurlstotest,otheruserurls)
# test logged in as user who has permissions
for url in urlstotest:
login = self.c.login(username = thisuser['username'],password = thisuser['password'])
result = self.c.get(url)
self.assertEqual(result.status_code, permissions['member_response'])
# test logged as user with no permissions (e.g. too low plan)
for url in falseurlstotest:
login = self.c.login(username = notuser['username'],password = notuser['password'])
result = self.c.get(url)
self.assertEqual(result.status_code, permissions['member_nonperm'])
# test as user with permissions, accessing object of non-related user
for url in otheruserurls:
login = self.c.login(username=thisuser['username'],password = thisuser['password'])
result = self.c.get(url)
self.assertEqual(result.status_code,403)
# Test access for logged in users - accessing coachee
@parameterized.expand(viewstotest)
@patch('rowers.c2stuff.Session', side_effect=mocked_requests)
@patch('rowers.c2stuff.c2_open')
@patch('rowers.dataprep.create_engine')
@patch('rowers.dataprep.read_df_sql')
@patch('rowers.dataprep.getsmallrowdata_db')
@patch('requests.get',side_effect=mocked_requests)
@patch('requests.post',side_effect=mocked_requests)
@patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data)
def test_permissions_coachee(
self,view,permissions,
mock_Session,
mock_c2open,
mocked_sqlalchemy,
mocked_read_df_sql,
mocked_getsmallrowdata_db,
mock_get,
mock_post,
mocked_get_video_data,
):
print()
print(view,'coachee')
print()
if permissions['coachee'] in plans and not permissions['is_staff'] and permissions['dotest']:
urlstotest = []
falseurlstotest = []
otheruserurls = []
thisuser = self.ucoach
coacheeuser = self.ubasic
notuser = self.uplan
if permissions['workoutid']:
workouts = coacheeuser['workouts']
url = reverse(view,kwargs={'id':encoder.encode_hex(workouts[0].id)})
urlstotest.append(url)
elif permissions['userid']:
url = reverse(view,kwargs={'userid':coacheeuser['user'].id})
urlstotest.append(url)
url = reverse(view,kwargs={'userid':self.ustrange['user'].id})
otheruserurls.append(url)
else:
url = reverse(view)
urlstotest.append(url)
print('coachee',urlstotest,falseurlstotest,otheruserurls)
# test logged in as user who has permissions
for url in urlstotest:
login = self.c.login(username = thisuser['username'],password = thisuser['password'])
result = self.c.get(url)
self.assertEqual(result.status_code, permissions['coachee_response'])
# test as user with permissions, accessing object of non-related user
for url in otheruserurls:
login = self.c.login(username=thisuser['username'],password = thisuser['password'])
result = self.c.get(url)
self.assertEqual(result.status_code,403)
+1 -1
View File
@@ -6,6 +6,7 @@ from __future__ import unicode_literals
#from __future__ import print_function #from __future__ import print_function
from .statements import * from .statements import *
nu = datetime.datetime.now() nu = datetime.datetime.now()
from rowers.utils import allmonths,allsundays from rowers.utils import allmonths,allsundays
import rowers.plannedsessions as plannedsessions import rowers.plannedsessions as plannedsessions
@@ -125,7 +126,6 @@ class TrainingPlanTest(TestCase):
for url in urls: for url in urls:
if 'planbyweeks' in url and not tested: if 'planbyweeks' in url and not tested:
print(url)
response = self.c.get(url,follow=True) response = self.c.get(url,follow=True)
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)
tested = True tested = True
+8 -6
View File
@@ -6,7 +6,8 @@ from __future__ import unicode_literals
from .statements import * from .statements import *
nu = datetime.datetime.now() nu = datetime.datetime.now()
from rowers.views import hasplannedsessions,iscoachmember,ispromember from rowers.views import isplanmember,iscoachmember,ispromember
from rowers.rower_rules import *
class TrialsTest(TestCase): class TrialsTest(TestCase):
def setUp(self): def setUp(self):
@@ -33,34 +34,35 @@ class TrialsTest(TestCase):
pass pass
def test_basictrial(self): def test_basictrial(self):
self.assertEqual(hasplannedsessions(self.u),False) self.assertEqual(isplanmember(self.u),False)
self.assertEqual(iscoachmember(self.u),False) self.assertEqual(iscoachmember(self.u),False)
self.assertEqual(ispromember(self.u),False) self.assertEqual(ispromember(self.u),False)
self.r.protrialexpires = (nu+datetime.timedelta(days=10)).date() self.r.protrialexpires = (nu+datetime.timedelta(days=10)).date()
self.r.save() self.r.save()
self.assertEqual(hasplannedsessions(self.u),False) self.assertEqual(isplanmember(self.u),False)
self.assertEqual(iscoachmember(self.u),False) self.assertEqual(iscoachmember(self.u),False)
self.assertEqual(ispromember(self.u),True) self.assertEqual(ispromember(self.u),True)
self.r.plantrialexpires = (nu+datetime.timedelta(days=10)).date() self.r.plantrialexpires = (nu+datetime.timedelta(days=10)).date()
self.r.save() self.r.save()
self.assertEqual(hasplannedsessions(self.u),True) self.assertEqual(isplanmember(self.u),True)
self.assertEqual(iscoachmember(self.u),False) self.assertEqual(iscoachmember(self.u),False)
self.assertEqual(ispromember(self.u),True) self.assertEqual(ispromember(self.u),True)
def test_protrial(self): def test_protrial(self):
self.r.rowerplan = 'pro' self.r.rowerplan = 'pro'
self.r.save() self.r.save()
self.assertEqual(hasplannedsessions(self.u),False) self.assertEqual(isplanmember(self.u),False)
self.assertEqual(iscoachmember(self.u),False) self.assertEqual(iscoachmember(self.u),False)
self.assertEqual(ispromember(self.u),True) self.assertEqual(ispromember(self.u),True)
self.r.plantrialexpires = (nu+datetime.timedelta(days=10)).date() self.r.plantrialexpires = (nu+datetime.timedelta(days=10)).date()
self.r.save() self.r.save()
self.assertEqual(hasplannedsessions(self.u),True) self.assertEqual(is_plantrial(self.u),True)
self.assertEqual(isplanmember(self.u),True)
self.assertEqual(iscoachmember(self.u),False) self.assertEqual(iscoachmember(self.u),False)
self.assertEqual(ispromember(self.u),True) self.assertEqual(ispromember(self.u),True)
-1
View File
@@ -209,7 +209,6 @@ class URLTests(TestCase):
'/rowers/workout/'+encoded1+'/editintervals/', '/rowers/workout/'+encoded1+'/editintervals/',
'/rowers/workout/'+encoded1+'/flexchart/', '/rowers/workout/'+encoded1+'/flexchart/',
'/rowers/workout/'+encoded1+'/forcecurve/', '/rowers/workout/'+encoded1+'/forcecurve/',
'/rowers/workout/'+encoded1+'/get-testscript/',
'/rowers/workout/'+encoded1+'/get-thumbnails/', '/rowers/workout/'+encoded1+'/get-thumbnails/',
'/rowers/workout/'+encoded1+'/histo/', '/rowers/workout/'+encoded1+'/histo/',
'/rowers/workout/'+encoded1+'/image/', '/rowers/workout/'+encoded1+'/image/',
+310
View File
@@ -0,0 +1,310 @@
,id,view,function,anonymous,anonymous_response,own,own_response,own_nonperm,member,member_response,member_nonperm,coachee,coachee_response,coachee_nonperm,is_staff,userid,workoutid,dotest,realtest,kwargs
0,0,workouts_summaries_email_view,sends summary excel with workouts list and links to data to user,TRUE,302,basic,200,302,FALSE,403,403,FALSE,403,403,FALSE,FALSE,FALSE,TRUE,TRUE,
1,1,rower_update_empower_view,updates old Empower Oarlock files (corrects Power bug),TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
2,2,agegroupcpview,needs age,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
3,4,ajax_agegrouprecords,gets age group records from C2 ,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
4,5,fitness_metric_view,updates a person's fitness metric,TRUE,302,basic,302,302,FALSE,403,302,FALSE,403,403,FALSE,FALSE,FALSE,TRUE,TRUE,
5,6,agegrouprecordview,shows ergo age group records,TRUE,200,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
6,7,workouts_view,workouts list,TRUE,302,basic,200,302,basic,200,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
7,8,virtualevents_view,virtual races list,TRUE,200,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
8,9,virtualevent_create_view,create virtual event,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
9,10,indoorvirtualevent_create_view,create indoor virtual event,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
10,11,virtualevent_toggle_email_view,toggle email notifications,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
11,12,indoorvirtualevent_toggle_email_view,toggle email notifications,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
12,13,virtualevent_view,view virtual race,TRUE,200,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
13,14,virtualevent_ranking_view,view ranking,TRUE,200,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
14,15,virtualevent_edit_view,edit race,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
15,16,indoorvirtualevent_edit_view,edit race,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
16,17,virtualevent_register_view,register for race,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
17,18,indoorvirtualevent_register_view,register for race,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
18,19,virtualevent_addboat_view,add a new boat type,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
19,20,virtualevent_withdraw_view,withdraw,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
20,21,virtualevent_submit_result_view,submit a result,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
21,22,virtualevent_disqualify_view,disqualify a racer,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
22,24,courses_view,see race courses,TRUE,200,FALSE,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
23,25,course_upload_view,upload a new course,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
24,26,addmanual_view,upload a manual workout,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
25,27,team_comparison_select,compare with a team member workout,TRUE,302,pro,200,302,pro,200,302,coach,200,302,FALSE,TRUE,TRUE,TRUE,TRUE,
26,28,workouts_join_view,join workouts,TRUE,302,pro,302,302,pro,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
27,29,workouts_join_select,select workouts to join,TRUE,404,pro,200,302,pro,403,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
28,30,user_boxplot_select,select boxplots,TRUE,302,pro,200,302,pro,403,403,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
29,31,analysis_new,analysis front page,TRUE,302,pro,200,302,FALSE,200,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
30,32,user_multiflex_select,select multiflex data,TRUE,302,pro,200,302,FALSE,200,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
31,33,session_jobs_view,view jobs,TRUE,302,basic,200,302,FALSE,200,302,coach,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
32,34,session_jobs_status,view jobs,TRUE,302,basic,200,302,FALSE,200,302,coach,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
33,35,kill_async_job,kill job,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
34,36,post_progress,post progress,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
35,37,graphs_view,view charts,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
36,38,fitnessmetric_view,view fitness metric,TRUE,302,plan,200,302,plan,403,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
37,39,rankings_view,view ranking,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
38,40,rankings_view2,view ranking,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
39,41,otwrankings_view,view ranking,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
40,42,oterankings_view,view ranking,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
41,43,cum_flex,flex all chart,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
42,44,analysis_view_data,redirects to analysis direct,TRUE,302,pro,302,302,pro,302,302,coach,302,302,FALSE,FALSE,FALSE,TRUE,TRUE,
43,47,cum_flex_data,flex all chart data (json),TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
44,48,histo,histogram view,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
45,49,histo_data,histogram data,TRUE,302,pro,200,302,pro,200,302,coach,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
46,51,cumstats,cumulative statistics,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
47,53,graph_show_view,show a chart,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
48,54,GraphDelete,delete a chart,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
49,55,get_thumbnails,get thumbnails,TRUE,403,basic,200,302,basic,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
50,56,otw_use_impeller,switch to impeller data,TRUE,403,basic,302,302,basic,403,302,coach,302,302,FALSE,FALSE,TRUE,TRUE,TRUE,
51,57,otw_use_gps,switch to GPS data,TRUE,403,basic,302,302,basic,403,302,coach,302,302,FALSE,FALSE,TRUE,TRUE,TRUE,
52,58,workout_toggle_ranking,toggle ranking,TRUE,302,basic,302,302,basic,403,302,coach,302,302,FALSE,FALSE,TRUE,TRUE,TRUE,
53,59,team_workout_upload_view,upload workout for team member,TRUE,302,coach,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
54,60,workout_upload_view,upload a workout,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
55,61,workout_histo_view,histogram ,TRUE,302,basic,200,302,basic,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
56,62,workout_forcecurve_view,force curve,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
57,63,workout_unsubscribe_view,unsubscribe from comments,TRUE,302,basic,200,302,basic,200,302,basic,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
58,64,workout_comment_view,comment on workout,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
59,65,workout_tcxemail_view,download TCX file,TRUE,403,basic,200,302,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE,
60,66,workout_gpxemail_view,download GPX file,TRUE,403,basic,200,302,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE,
61,67,workout_csvemail_view,download CSV file,TRUE,403,basic,200,302,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE,
62,68,workout_csvtoadmin_view,send CSV to admin,TRUE,403,basic,200,200,basic,200,200,coach,200,200,TRUE,FALSE,TRUE,TRUE,TRUE,
63,69,workout_edit_view,Edit Workout,TRUE,403,basic,200,403,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE,
64,70,workout_map_view,View workout Map,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
65,71,workout_update_cp_view,Update CP data based on new workout,TRUE,403,pro,302,302,pro,403,403,coach,302,302,FALSE,FALSE,TRUE,TRUE,TRUE,
66,72,instroke_chart,View In-Stroke data chart,TRUE,302,pro,302,302,pro,403,403,coach,302,302,FALSE,FALSE,FALSE,FALSE,FALSE,
67,73,instroke_view,Create in stroke chart,TRUE,403,basic,200,302,basic,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
68,74,workout_stats_view,View Workout Stats,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
69,75,workout_data_view,shows the detailed data for a workout,TRUE,403,basic,200,403,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE,
70,76,workout_otwsetpower_view,set weight and start offline OTW power calculations,TRUE,403,pro,200,302,pro,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE,
71,77,workout_otwpowerplot_view,generates OTW power chart,TRUE,200,basic,200,200,basic,200,200,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
72,78,workout_wind_view,set wind,TRUE,403,pro,200,302,pro,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE,
73,79,workout_uploadimage_view,upload image,TRUE,403,basic,200,403,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE,
74,80,virtualevent_compare_view,compare workouts from a virtual event,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
75,81,virtualevent_uploadimage_view,upload image to virtual event,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
76,82,virtualevent_setlogo_view,set logo of virtual event,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
77,83,logo_delete_view,delete logo,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
78,84,workout_downloadwind_view,download Wind,TRUE,403,pro,302,302,pro,403,403,coach,302,403,FALSE,FALSE,TRUE,FALSE,FALSE,
79,85,workout_downloadmetar_view,download METAR,TRUE,403,pro,200,302,pro,403,403,coach,200,403,FALSE,FALSE,TRUE,FALSE,FALSE,
80,86,workout_stream_view,edit stream (redirects as no data in test suite),TRUE,403,pro,200,302,pro,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
81,87,workout_summary_edit_view,edit workout summary,TRUE,403,basic,200,403,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE,
82,88,workout_summary_restore_view,restore workout summary,TRUE,403,basic,302,403,basic,403,403,coach,302,403,FALSE,FALSE,TRUE,TRUE,TRUE,
83,89,workout_split_view,split workout,TRUE,403,pro,200,302,pro,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
84,90,workout_view,view workout ,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
85,91,workout_video_create_view,add video ,TRUE,403,pro,200,302,pro,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
86,92,VideoDelete,delete video,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
87,93,workout_video_view_mini,minimal view,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
88,94,workout_video_view,"video view (not tested, no video in test suite)",TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
89,95,list_videos,list my videos,TRUE,302,pro,200,302,pro,200,302,coach,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
90,96,video_selectworkout,select workout to create video,TRUE,302,pro,200,302,pro,403,403,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
91,98,workout_fusion_view,needs to workouts,TRUE,403,pro,200,302,pro,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
92,99,workout_fusion_list,list workouts to fuse with,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE,
94,103,workout_add_chart_view,add chart,TRUE,403,basic,302,403,basic,403,403,coach,302,403,FALSE,FALSE,TRUE,FALSE,FALSE,
95,112,WorkoutDelete,delete workout,TRUE,403,basic,200,302,basic,403,403,coach,403,403,FALSE,FALSE,TRUE,FALSE,FALSE,
96,113,workout_smoothenpace_view,smoothen pace,TRUE,403,pro,302,302,pro,403,403,coach,302,403,FALSE,FALSE,TRUE,TRUE,TRUE,
97,114,workout_undo_smoothenpace_view,unsmoothen pace,TRUE,403,pro,302,302,pro,403,403,coach,302,403,FALSE,FALSE,TRUE,TRUE,TRUE,
98,115,workout_c2import_view,list workouts to be imported (test stops at notokenerror),TRUE,302,basic,302,302,basic,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
99,120,workout_stravaimport_view,list workouts to be imported (test stops at notokenerror),TRUE,302,basic,302,302,basic,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
100,122,workout_getc2workout_all,gets all C2 workouts (now redirects due to NoTokenError,TRUE,302,basic,302,302,FALSE,302,302,FALSE,302,302,FALSE,FALSE,FALSE,TRUE,TRUE,
101,124,workout_getimportview,imports a workout from third party,TRUE,200,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
102,125,workout_getstravaworkout_all,gets all C2 workouts (now redirects due to NoTokenError,TRUE,302,basic,302,302,FALSE,302,302,FALSE,302,302,FALSE,FALSE,FALSE,TRUE,TRUE,
103,126,workout_getstravaworkout_next,gets all strava workouts,TRUE,302,basic,302,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
104,127,workout_sporttracksimport_view,list workouts to be imported (test stops at notokenerror),TRUE,302,basic,302,302,basic,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
105,129,workout_getsporttracksworkout_all,gets all C2 workouts (now redirects due to NoTokenError,TRUE,302,basic,302,302,FALSE,302,302,FALSE,302,302,FALSE,FALSE,FALSE,TRUE,TRUE,
106,130,workout_polarimport_view,list workouts to be imported (test stops at notokenerror),TRUE,302,basic,302,302,basic,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
107,132,workout_runkeeperimport_view,list workouts to be imported (test stops at notokenerror),TRUE,302,basic,302,302,basic,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
108,134,workout_underarmourimport_view,list workouts to be imported (test stops at notokenerror),TRUE,302,basic,302,302,basic,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
109,135,workout_c2_upload_view,uploads workout to C2,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
110,136,workout_strava_upload_view,uploads workout to C2,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
111,137,workout_recalcsummary_view,recalculates workout summary,TRUE,403,basic,302,403,basic,403,403,coach,302,403,FALSE,FALSE,TRUE,TRUE,TRUE,
112,138,workout_sporttracks_upload_view,uploads workout to C2,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
113,139,workout_runkeeper_upload_view,uploads workout to C2,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
114,140,workout_underarmour_upload_view,uploads workout to C2,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
115,141,workout_tp_upload_view,uploads workout to C2,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
116,142,multi_compare_view,compare workouts ,TRUE,302,basic,302,403,basic,302,403,coach,302,302,FALSE,FALSE,TRUE,TRUE,TRUE,
117,145,alerts_view,view alerts,TRUE,302,basic,200,403,basic,403,403,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE,
118,147,AlertDelete,delete alert,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
119,148,alert_edit_view,edit alert,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
120,150,alert_create_view,create alert,TRUE,302,pro,200,403,pro,403,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
121,152,alert_report_view,view alerts report,TRUE,302,basic,200,403,basic,403,403,coach,200,403,FALSE,FALSE,FALSE,FALSE,FALSE,
122,155,boxplot_view,View boxplot,TRUE,302,pro,302,403,pro,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
123,157,boxplot_view_data,data for boxplot,TRUE,302,pro,200,403,pro,200,403,coach,200,403,FALSE,FALSE,FALSE,FALSE,FALSE,
124,158,multiflex_view,view multiflex,TRUE,302,pro,302,403,pro,403,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
125,160,multiflex_data,data for multiflex,TRUE,302,pro,200,403,pro,200,403,coach,200,403,FALSE,FALSE,FALSE,FALSE,FALSE,
126,161,deactivate_user,deactivate a user,TRUE,302,basic,200,302,FALSE,403,403,FALSE,403,403,FALSE,FALSE,FALSE,TRUE,TRUE,
127,162,remove_user,remove a user,TRUE,302,basic,200,302,FALSE,403,403,FALSE,403,403,FALSE,FALSE,FALSE,TRUE,TRUE,
128,163,user_gdpr_confirm,GDPR confirmation,TRUE,302,basic,302,302,FALSE,403,403,FALSE,403,403,FALSE,FALSE,FALSE,TRUE,TRUE,
129,165,user_gdpr_optin,GDPR optin,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
130,167,rower_teams_view,view team,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
131,168,rower_calcdps_view,calculates distance per stroke,TRUE,302,basic,302,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
132,169,rower_exportsettings_view,Export settings,TRUE,302,basic,200,302,basic,403,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
133,171,team_view,Team view,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
134,172,team_members_stats_view,Stats for team members,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
135,173,team_edit_view,View Team,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
136,174,team_leaveconfirm_view,Leave Team,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
137,175,team_leave_view,Leave Team,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
138,176,team_deleteconfirm_view,Delete Team,TRUE,302,pro,200,302,pro,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
139,177,team_requestmembership_view,Request team membership,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
140,178,reject_revoke_coach_request,Reject request,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
141,179,coach_drop_athlete_confirm_view,Drop athlete,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
142,180,coach_drop_athlete_view,Drop athlete,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
143,181,athlete_drop_coach_confirm_view,Drop athlete,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
144,182,athlete_drop_coach_view,Drop coach,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
145,184,reject_revoke_coach_offer,Reject offer,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
146,186,request_coaching_view,Request coaching,TRUE,302,basic,302,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
147,187,offer_coaching_view,Offer Coaching,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,TRUE,FALSE,FALSE,FALSE,
148,188,coach_accept_coachrequest_view,Accept Coach request,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
149,189,rower_accept_coachoffer_view,Accept Coach offer,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
150,190,team_delete_view,Delete Team,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
151,191,team_create_view,Create Team,TRUE,302,pro,302,302,FALSE,200,200,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
152,192,manager_member_drop_view,Drop member,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
153,193,invitation_reject_view,Reject invitation,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
154,194,invitation_revoke_view,redirects when no invitations,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
155,195,rower_invitations_view,See Invitations,TRUE,302,basic,302,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
156,196,raise_500,Test 500 error,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
157,198,request_revoke_view,redirects when no invitations,TRUE,302,basic,302,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
158,199,request_reject_view,rejects request,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
159,200,manager_requests_view,views requests,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
160,202,rower_edit_view,edit rower preferences,TRUE,302,basic,200,403,basic,403,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
161,204,rower_prefs_view,edit more preferences,TRUE,302,basic,200,403,basic,403,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
162,205,transactions_view,view transactions (staff only),TRUE,302,basic,200,302,basic,200,302,coach,200,302,TRUE,FALSE,FALSE,TRUE,TRUE,
163,208,rower_c2_authorize,redirects to C2 auth page,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
164,209,rower_polar_authorize,redirects to Polar,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
165,210,rower_revokeapp_view,revoke app,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
166,211,rower_strava_authorize,authorize on strava,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
167,212,rower_sporttracks_authorize,authorize on sporttracks,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
168,213,rower_underarmour_authorize,authorize on UA,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
169,214,rower_tp_authorize,authorize on TP,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
170,215,rower_runkeeper_authorize,authorize on RK,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
171,216,rower_sporttracks_token_refresh,refresh token,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
172,217,rower_underarmour_token_refresh,refresh token,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
173,218,rower_tp_token_refresh,refresh token,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
174,219,rower_c2_token_refresh,refresh token,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
175,220,rower_favoritecharts_view,See favorite charts,TRUE,302,pro,200,302,pro,403,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
176,222,workout_workflow_config2_view,configure workflow,TRUE,302,basic,200,302,basic,403,403,coach,200,403,FALSE,TRUE,FALSE,FALSE,FALSE,
177,224,workflow_default_view,resets workflow to default,TRUE,302,basic,302,302,FALSE,403,403,FALSE,302,403,FALSE,FALSE,FALSE,TRUE,TRUE,
178,225,sendmail,feedback form,TRUE,302,basic,302,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
180,232,laboratory_view,lab,TRUE,302,basic,200,302,basic,403,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
181,233,errormessage_view,not used,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
182,237,payment_confirm_view,confirm payment,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
183,238,upgrade_confirm_view,confirm upgrade,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
184,240,downgrade_confirm_view,confirm downgrade,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
185,241,billing_view,confirm billing,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
186,242,upgrade_view,confirm upgrade,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
187,243,downgrade_view,confirm downgrade,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
188,244,payment_completed_view,payment completed,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
189,245,downgrade_completed_view,downgrade completed,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
190,246,paidplans_view,paid plans,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
191,247,plan_stop_view,stop plan,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
192,248,plan_tobasic_view,reset to basic,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
193,249,checkouts_view,checkout,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
194,250,upgrade_checkouts_view,upgrade checkout,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
195,251,downgrade_checkouts_view,downgrade checkout,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
196,252,planrequired_view,a plan is required,TRUE,302,basic,302,302,basic,302,302,coach,302,302,FALSE,FALSE,FALSE,TRUE,TRUE,
197,253,start_trial_view,payments,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
198,254,start_plantrial_view,paid plans,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
199,256,rower_register_view,register rower,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
200,257,freecoach_register_view,register coach,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
201,259,workout_workflow_view,workout workflow vieq,TRUE,302,basic,200,200,basic,200,200,coach,200,200,FALSE,FALSE,TRUE,TRUE,TRUE,
202,260,workout_flexchart3_view,flex chart,TRUE,302,basic,200,403,basic,200,200,coach,200,200,FALSE,FALSE,TRUE,TRUE,TRUE,
203,264,rower_process_testcallback,test callback,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
204,265,rower_create_trainingplan,create training plan,TRUE,302,plan,200,302,FALSE,403,403,FALSE,200,403,FALSE,FALSE,FALSE,TRUE,TRUE,
205,267,TrainingPlanDelete,delete training plan,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
206,268,MicroCycleDelete,delete training plan,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
207,269,MesoCycleDelete,delete training plan,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
208,270,MacroCycleDelete,delete training plan,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
209,271,rower_trainingplan_view,see training plan,TRUE,200,plan,200,302,plan,403,403,coach,200,403,FALSE,TRUE,FALSE,FALSE,FALSE,
210,279,rower_trainingplan_execution_view,see training plan execution,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
211,281,TrainingMacroCycleUpdate,update macro cycle,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
212,282,TrainingMesoCycleUpdate,update cycle,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
213,283,planmacrocyclebymonth,update cycle,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
214,285,planmesocyclebyweek,plan meso by week,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
215,287,TrainingMicroCycleUpdate,update cycle,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
216,288,rower_delete_trainingtarget,delete training target,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
217,289,TrainingPlanUpdate,update trainingplan,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
218,290,TrainingTargetUpdate,update training target,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
219,291,strokedataform,test strokedata,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
220,292,plannedsession_teamcreate_view,create planned session for team,TRUE,302,coach,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE,
221,296,plannedsession_teamedit_view,edit planned sesssion,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
222,298,plannedsession_create_view,create planned session ,TRUE,302,plan,200,302,FALSE,200,302,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
223,300,plannedsession_multiclone_view,clone multiple planned sessions,TRUE,302,plan,200,302,plan,403,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
224,302,plannedsession_multicreate_view,create multiple planned sessions,TRUE,302,plan,200,302,plan,403,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
225,305,plannedsession_edit_view,edit planned sesssion,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
226,306,plannedsession_totemplate_view,planned session to template,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
227,307,plannedsession_compare_view,compare workouts from planned session,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
228,310,plannedsession_clone_view,clone planned session,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
229,311,plannedsession_teamclone_view,clone team planned session,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
230,313,plannedsession_detach_view,remove workout from session,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
231,315,plannedsession_view,view session,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
232,317,PlannedSessionDelete,delete session,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
233,319,plannedsessions_manage_view,manage planned sesions,TRUE,302,plan,200,302,plan,403,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
234,323,plannedsessions_coach_view,team view,TRUE,302,basic,200,403,basic,403,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
235,325,plannedsessions_coach_icsemail_view,team view,TRUE,302,coach,302,403,coach,302,403,coach,302,403,FALSE,TRUE,FALSE,TRUE,TRUE,
236,326,plannedsessions_print_view,print view,TRUE,302,basic,200,403,basic,403,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
237,327,plannedsession_comment_view,comment on planned session,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
238,330,plannedsessions_icsemail_view,send ICS email,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,TRUE,FALSE,FALSE,FALSE,
239,332,plannedsessions_view,View planned sessions,TRUE,302,basic,200,403,basic,403,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE,
240,334,course_edit_view,Edit course,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
241,335,course_delete_view,Delete course,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
242,336,course_kmldownload_view,Kmldownload course,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
243,337,course_replace_view,Replace course,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
244,338,course_view, view,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
245,339,course_map_view,Map course,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
246,341,c2listdebug_view,Debug view,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
247,343,rower_process_callback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
248,344,rower_process_stravacallback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
249,345,rower_process_sporttrackscallback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
250,346,rower_process_underarmourcallback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
251,347,rower_process_polarcallback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
252,348,rower_process_runkeepercallback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
253,349,rower_process_tpcallback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
254,350,rower_process_twittercallback,Tested in specific test suite,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
255,351,set_language,Set language,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
256,352,SetOffsetView,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
257,353,stats,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
258,354,stats_json,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
259,355,jobs,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
260,356,workers,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
261,357,worker_details,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
262,358,finished_jobs,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
263,359,started_jobs,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
264,360,deferred_jobs,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
265,361,clear_queue,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
266,362,requeue_all,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
267,363,job_detail,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
268,364,delete_job,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
269,365,actions,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
270,366,requeue_job_view,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
271,367,enqueue_job,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
272,368,JavaScriptCatalog,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
273,369,serve,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
274,371,LoginView,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
275,372,LogoutView,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
276,373,PasswordChangeView,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
277,374,PasswordChangeDoneView,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
278,375,PasswordResetView,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
279,376,PasswordResetDoneView,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
280,377,PasswordResetConfirmView,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
281,378,PasswordResetCompleteView,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
282,386,index,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
283,387,login,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
284,388,logout,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
285,389,password_change,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
286,390,password_change_done,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
287,391,i18n_javascript,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
288,392,shortcut,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
289,393,changelist_view,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
290,394,add_view,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
291,395,autocomplete_view,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
292,396,history_view,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
293,397,delete_view,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
294,398,change_view,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
295,399,RedirectView,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
296,568,app_index,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
297,571,landingview,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
298,573,get_blog_posts,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
299,576,AuthorizationView,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
300,577,TokenView,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
301,578,WorkoutViewSet,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
302,582,RowerViewSet,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
303,586,FavoriteChartViewSet,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
304,590,APIRootView,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
305,592,SwaggerSchemaView,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
306,595,strokedatajson,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
307,596,error500_view,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
308,603,failed_queue_view,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
309,604,failed_queue_empty,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE,
310,605,failed_job_view,Other Apps views,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,FALSE,FALSE,
1 id view function anonymous anonymous_response own own_response own_nonperm member member_response member_nonperm coachee coachee_response coachee_nonperm is_staff userid workoutid dotest realtest kwargs
2 0 0 workouts_summaries_email_view sends summary excel with workouts list and links to data to user TRUE 302 basic 200 302 FALSE 403 403 FALSE 403 403 FALSE FALSE FALSE TRUE TRUE
3 1 1 rower_update_empower_view updates old Empower Oarlock files (corrects Power bug) TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
4 2 2 agegroupcpview needs age TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
5 3 4 ajax_agegrouprecords gets age group records from C2 TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
6 4 5 fitness_metric_view updates a person's fitness metric TRUE 302 basic 302 302 FALSE 403 302 FALSE 403 403 FALSE FALSE FALSE TRUE TRUE
7 5 6 agegrouprecordview shows ergo age group records TRUE 200 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
8 6 7 workouts_view workouts list TRUE 302 basic 200 302 basic 200 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
9 7 8 virtualevents_view virtual races list TRUE 200 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
10 8 9 virtualevent_create_view create virtual event TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
11 9 10 indoorvirtualevent_create_view create indoor virtual event TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
12 10 11 virtualevent_toggle_email_view toggle email notifications TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
13 11 12 indoorvirtualevent_toggle_email_view toggle email notifications TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
14 12 13 virtualevent_view view virtual race TRUE 200 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
15 13 14 virtualevent_ranking_view view ranking TRUE 200 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
16 14 15 virtualevent_edit_view edit race TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
17 15 16 indoorvirtualevent_edit_view edit race TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
18 16 17 virtualevent_register_view register for race TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
19 17 18 indoorvirtualevent_register_view register for race TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
20 18 19 virtualevent_addboat_view add a new boat type TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
21 19 20 virtualevent_withdraw_view withdraw TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
22 20 21 virtualevent_submit_result_view submit a result TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE TRUE FALSE FALSE
23 21 22 virtualevent_disqualify_view disqualify a racer TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
24 22 24 courses_view see race courses TRUE 200 FALSE 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
25 23 25 course_upload_view upload a new course TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
26 24 26 addmanual_view upload a manual workout TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE TRUE TRUE
27 25 27 team_comparison_select compare with a team member workout TRUE 302 pro 200 302 pro 200 302 coach 200 302 FALSE TRUE TRUE TRUE TRUE
28 26 28 workouts_join_view join workouts TRUE 302 pro 302 302 pro 403 403 coach 302 403 FALSE TRUE FALSE TRUE TRUE
29 27 29 workouts_join_select select workouts to join TRUE 404 pro 200 302 pro 403 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
30 28 30 user_boxplot_select select boxplots TRUE 302 pro 200 302 pro 403 403 coach 200 302 FALSE TRUE FALSE TRUE TRUE
31 29 31 analysis_new analysis front page TRUE 302 pro 200 302 FALSE 200 302 coach 200 302 FALSE TRUE FALSE TRUE TRUE
32 30 32 user_multiflex_select select multiflex data TRUE 302 pro 200 302 FALSE 200 302 coach 200 302 FALSE TRUE FALSE TRUE TRUE
33 31 33 session_jobs_view view jobs TRUE 302 basic 200 302 FALSE 200 302 coach 200 302 FALSE FALSE FALSE TRUE TRUE
34 32 34 session_jobs_status view jobs TRUE 302 basic 200 302 FALSE 200 302 coach 200 302 FALSE FALSE FALSE TRUE TRUE
35 33 35 kill_async_job kill job TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
36 34 36 post_progress post progress TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
37 35 37 graphs_view view charts TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE TRUE FALSE TRUE TRUE
38 36 38 fitnessmetric_view view fitness metric TRUE 302 plan 200 302 plan 403 302 coach 200 302 FALSE TRUE FALSE TRUE TRUE
39 37 39 rankings_view view ranking TRUE 302 pro 200 302 pro 403 302 coach 200 302 FALSE TRUE FALSE TRUE TRUE
40 38 40 rankings_view2 view ranking TRUE 302 pro 200 302 pro 403 302 coach 200 302 FALSE TRUE FALSE TRUE TRUE
41 39 41 otwrankings_view view ranking TRUE 302 pro 200 302 pro 403 302 coach 200 302 FALSE TRUE FALSE TRUE TRUE
42 40 42 oterankings_view view ranking TRUE 302 pro 200 302 pro 403 302 coach 200 302 FALSE TRUE FALSE TRUE TRUE
43 41 43 cum_flex flex all chart TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
44 42 44 analysis_view_data redirects to analysis direct TRUE 302 pro 302 302 pro 302 302 coach 302 302 FALSE FALSE FALSE TRUE TRUE
45 43 47 cum_flex_data flex all chart data (json) TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
46 44 48 histo histogram view TRUE 302 pro 200 302 pro 403 302 coach 200 302 FALSE TRUE FALSE TRUE TRUE
47 45 49 histo_data histogram data TRUE 302 pro 200 302 pro 200 302 coach 200 302 FALSE FALSE FALSE TRUE TRUE
48 46 51 cumstats cumulative statistics TRUE 302 pro 200 302 pro 403 302 coach 200 302 FALSE TRUE FALSE TRUE TRUE
49 47 53 graph_show_view show a chart TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
50 48 54 GraphDelete delete a chart TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
51 49 55 get_thumbnails get thumbnails TRUE 403 basic 200 302 basic 403 403 coach 200 302 FALSE FALSE TRUE TRUE TRUE
52 50 56 otw_use_impeller switch to impeller data TRUE 403 basic 302 302 basic 403 302 coach 302 302 FALSE FALSE TRUE TRUE TRUE
53 51 57 otw_use_gps switch to GPS data TRUE 403 basic 302 302 basic 403 302 coach 302 302 FALSE FALSE TRUE TRUE TRUE
54 52 58 workout_toggle_ranking toggle ranking TRUE 302 basic 302 302 basic 403 302 coach 302 302 FALSE FALSE TRUE TRUE TRUE
55 53 59 team_workout_upload_view upload workout for team member TRUE 302 coach 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
56 54 60 workout_upload_view upload a workout TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
57 55 61 workout_histo_view histogram TRUE 302 basic 200 302 basic 403 403 coach 200 302 FALSE FALSE TRUE TRUE TRUE
58 56 62 workout_forcecurve_view force curve TRUE 302 pro 200 302 pro 403 302 coach 200 302 FALSE FALSE TRUE TRUE TRUE
59 57 63 workout_unsubscribe_view unsubscribe from comments TRUE 302 basic 200 302 basic 200 302 basic 200 302 FALSE FALSE TRUE TRUE TRUE
60 58 64 workout_comment_view comment on workout TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE TRUE TRUE
61 59 65 workout_tcxemail_view download TCX file TRUE 403 basic 200 302 basic 403 403 coach 200 403 FALSE FALSE TRUE TRUE TRUE
62 60 66 workout_gpxemail_view download GPX file TRUE 403 basic 200 302 basic 403 403 coach 200 403 FALSE FALSE TRUE TRUE TRUE
63 61 67 workout_csvemail_view download CSV file TRUE 403 basic 200 302 basic 403 403 coach 200 403 FALSE FALSE TRUE TRUE TRUE
64 62 68 workout_csvtoadmin_view send CSV to admin TRUE 403 basic 200 200 basic 200 200 coach 200 200 TRUE FALSE TRUE TRUE TRUE
65 63 69 workout_edit_view Edit Workout TRUE 403 basic 200 403 basic 403 403 coach 200 403 FALSE FALSE TRUE TRUE TRUE
66 64 70 workout_map_view View workout Map TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE TRUE TRUE
67 65 71 workout_update_cp_view Update CP data based on new workout TRUE 403 pro 302 302 pro 403 403 coach 302 302 FALSE FALSE TRUE TRUE TRUE
68 66 72 instroke_chart View In-Stroke data chart TRUE 302 pro 302 302 pro 403 403 coach 302 302 FALSE FALSE FALSE FALSE FALSE
69 67 73 instroke_view Create in stroke chart TRUE 403 basic 200 302 basic 403 403 coach 200 302 FALSE FALSE TRUE TRUE TRUE
70 68 74 workout_stats_view View Workout Stats TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE TRUE TRUE
71 69 75 workout_data_view shows the detailed data for a workout TRUE 403 basic 200 403 basic 403 403 coach 200 403 FALSE FALSE TRUE TRUE TRUE
72 70 76 workout_otwsetpower_view set weight and start offline OTW power calculations TRUE 403 pro 200 302 pro 403 403 coach 200 403 FALSE FALSE TRUE TRUE TRUE
73 71 77 workout_otwpowerplot_view generates OTW power chart TRUE 200 basic 200 200 basic 200 200 coach 200 302 FALSE FALSE TRUE TRUE TRUE
74 72 78 workout_wind_view set wind TRUE 403 pro 200 302 pro 403 403 coach 200 403 FALSE FALSE TRUE TRUE TRUE
75 73 79 workout_uploadimage_view upload image TRUE 403 basic 200 403 basic 403 403 coach 200 403 FALSE FALSE TRUE TRUE TRUE
76 74 80 virtualevent_compare_view compare workouts from a virtual event TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
77 75 81 virtualevent_uploadimage_view upload image to virtual event TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
78 76 82 virtualevent_setlogo_view set logo of virtual event TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
79 77 83 logo_delete_view delete logo TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
80 78 84 workout_downloadwind_view download Wind TRUE 403 pro 302 302 pro 403 403 coach 302 403 FALSE FALSE TRUE FALSE FALSE
81 79 85 workout_downloadmetar_view download METAR TRUE 403 pro 200 302 pro 403 403 coach 200 403 FALSE FALSE TRUE FALSE FALSE
82 80 86 workout_stream_view edit stream (redirects as no data in test suite) TRUE 403 pro 200 302 pro 403 403 coach 200 302 FALSE FALSE TRUE TRUE TRUE
83 81 87 workout_summary_edit_view edit workout summary TRUE 403 basic 200 403 basic 403 403 coach 200 403 FALSE FALSE TRUE TRUE TRUE
84 82 88 workout_summary_restore_view restore workout summary TRUE 403 basic 302 403 basic 403 403 coach 302 403 FALSE FALSE TRUE TRUE TRUE
85 83 89 workout_split_view split workout TRUE 403 pro 200 302 pro 403 403 coach 200 302 FALSE FALSE TRUE TRUE TRUE
86 84 90 workout_view view workout TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE TRUE TRUE
87 85 91 workout_video_create_view add video TRUE 403 pro 200 302 pro 403 403 coach 200 302 FALSE FALSE TRUE TRUE TRUE
88 86 92 VideoDelete delete video TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
89 87 93 workout_video_view_mini minimal view TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE FALSE FALSE
90 88 94 workout_video_view video view (not tested, no video in test suite) TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE FALSE FALSE
91 89 95 list_videos list my videos TRUE 302 pro 200 302 pro 200 302 coach 200 302 FALSE FALSE FALSE TRUE TRUE
92 90 96 video_selectworkout select workout to create video TRUE 302 pro 200 302 pro 403 403 coach 200 302 FALSE TRUE FALSE TRUE TRUE
93 91 98 workout_fusion_view needs to workouts TRUE 403 pro 200 302 pro 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
94 92 99 workout_fusion_list list workouts to fuse with TRUE 302 pro 200 302 pro 403 302 coach 200 302 FALSE FALSE TRUE TRUE TRUE
95 94 103 workout_add_chart_view add chart TRUE 403 basic 302 403 basic 403 403 coach 302 403 FALSE FALSE TRUE FALSE FALSE
96 95 112 WorkoutDelete delete workout TRUE 403 basic 200 302 basic 403 403 coach 403 403 FALSE FALSE TRUE FALSE FALSE
97 96 113 workout_smoothenpace_view smoothen pace TRUE 403 pro 302 302 pro 403 403 coach 302 403 FALSE FALSE TRUE TRUE TRUE
98 97 114 workout_undo_smoothenpace_view unsmoothen pace TRUE 403 pro 302 302 pro 403 403 coach 302 403 FALSE FALSE TRUE TRUE TRUE
99 98 115 workout_c2import_view list workouts to be imported (test stops at notokenerror) TRUE 302 basic 302 302 basic 403 403 coach 302 403 FALSE TRUE FALSE TRUE TRUE
100 99 120 workout_stravaimport_view list workouts to be imported (test stops at notokenerror) TRUE 302 basic 302 302 basic 403 403 coach 302 403 FALSE TRUE FALSE TRUE TRUE
101 100 122 workout_getc2workout_all gets all C2 workouts (now redirects due to NoTokenError TRUE 302 basic 302 302 FALSE 302 302 FALSE 302 302 FALSE FALSE FALSE TRUE TRUE
102 101 124 workout_getimportview imports a workout from third party TRUE 200 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
103 102 125 workout_getstravaworkout_all gets all C2 workouts (now redirects due to NoTokenError TRUE 302 basic 302 302 FALSE 302 302 FALSE 302 302 FALSE FALSE FALSE TRUE TRUE
104 103 126 workout_getstravaworkout_next gets all strava workouts TRUE 302 basic 302 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
105 104 127 workout_sporttracksimport_view list workouts to be imported (test stops at notokenerror) TRUE 302 basic 302 302 basic 403 403 coach 302 403 FALSE TRUE FALSE TRUE TRUE
106 105 129 workout_getsporttracksworkout_all gets all C2 workouts (now redirects due to NoTokenError TRUE 302 basic 302 302 FALSE 302 302 FALSE 302 302 FALSE FALSE FALSE TRUE TRUE
107 106 130 workout_polarimport_view list workouts to be imported (test stops at notokenerror) TRUE 302 basic 302 302 basic 403 403 coach 302 403 FALSE TRUE FALSE TRUE TRUE
108 107 132 workout_runkeeperimport_view list workouts to be imported (test stops at notokenerror) TRUE 302 basic 302 302 basic 403 403 coach 302 403 FALSE TRUE FALSE TRUE TRUE
109 108 134 workout_underarmourimport_view list workouts to be imported (test stops at notokenerror) TRUE 302 basic 302 302 basic 403 403 coach 302 403 FALSE TRUE FALSE TRUE TRUE
110 109 135 workout_c2_upload_view uploads workout to C2 TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE FALSE FALSE
111 110 136 workout_strava_upload_view uploads workout to C2 TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE FALSE FALSE
112 111 137 workout_recalcsummary_view recalculates workout summary TRUE 403 basic 302 403 basic 403 403 coach 302 403 FALSE FALSE TRUE TRUE TRUE
113 112 138 workout_sporttracks_upload_view uploads workout to C2 TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE FALSE FALSE
114 113 139 workout_runkeeper_upload_view uploads workout to C2 TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE FALSE FALSE
115 114 140 workout_underarmour_upload_view uploads workout to C2 TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE FALSE FALSE
116 115 141 workout_tp_upload_view uploads workout to C2 TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE FALSE FALSE
117 116 142 multi_compare_view compare workouts TRUE 302 basic 302 403 basic 302 403 coach 302 302 FALSE FALSE TRUE TRUE TRUE
118 117 145 alerts_view view alerts TRUE 302 basic 200 403 basic 403 403 coach 200 302 FALSE TRUE FALSE TRUE TRUE
119 118 147 AlertDelete delete alert TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
120 119 148 alert_edit_view edit alert TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
121 120 150 alert_create_view create alert TRUE 302 pro 200 403 pro 403 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
122 121 152 alert_report_view view alerts report TRUE 302 basic 200 403 basic 403 403 coach 200 403 FALSE FALSE FALSE FALSE FALSE
123 122 155 boxplot_view View boxplot TRUE 302 pro 302 403 pro 403 403 coach 302 403 FALSE TRUE FALSE TRUE TRUE
124 123 157 boxplot_view_data data for boxplot TRUE 302 pro 200 403 pro 200 403 coach 200 403 FALSE FALSE FALSE FALSE FALSE
125 124 158 multiflex_view view multiflex TRUE 302 pro 302 403 pro 403 403 coach 302 403 FALSE TRUE FALSE TRUE TRUE
126 125 160 multiflex_data data for multiflex TRUE 302 pro 200 403 pro 200 403 coach 200 403 FALSE FALSE FALSE FALSE FALSE
127 126 161 deactivate_user deactivate a user TRUE 302 basic 200 302 FALSE 403 403 FALSE 403 403 FALSE FALSE FALSE TRUE TRUE
128 127 162 remove_user remove a user TRUE 302 basic 200 302 FALSE 403 403 FALSE 403 403 FALSE FALSE FALSE TRUE TRUE
129 128 163 user_gdpr_confirm GDPR confirmation TRUE 302 basic 302 302 FALSE 403 403 FALSE 403 403 FALSE FALSE FALSE TRUE TRUE
130 129 165 user_gdpr_optin GDPR optin TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
131 130 167 rower_teams_view view team TRUE 302 basic 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
132 131 168 rower_calcdps_view calculates distance per stroke TRUE 302 basic 302 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
133 132 169 rower_exportsettings_view Export settings TRUE 302 basic 200 302 basic 403 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
134 133 171 team_view Team view TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
135 134 172 team_members_stats_view Stats for team members TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
136 135 173 team_edit_view View Team TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
137 136 174 team_leaveconfirm_view Leave Team TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
138 137 175 team_leave_view Leave Team TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
139 138 176 team_deleteconfirm_view Delete Team TRUE 302 pro 200 302 pro 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
140 139 177 team_requestmembership_view Request team membership TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
141 140 178 reject_revoke_coach_request Reject request TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
142 141 179 coach_drop_athlete_confirm_view Drop athlete TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
143 142 180 coach_drop_athlete_view Drop athlete TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
144 143 181 athlete_drop_coach_confirm_view Drop athlete TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
145 144 182 athlete_drop_coach_view Drop coach TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
146 145 184 reject_revoke_coach_offer Reject offer TRUE 302 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
147 146 186 request_coaching_view Request coaching TRUE 302 basic 302 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE FALSE FALSE
148 147 187 offer_coaching_view Offer Coaching TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE TRUE FALSE FALSE FALSE
149 148 188 coach_accept_coachrequest_view Accept Coach request TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
150 149 189 rower_accept_coachoffer_view Accept Coach offer TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
151 150 190 team_delete_view Delete Team TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
152 151 191 team_create_view Create Team TRUE 302 pro 302 302 FALSE 200 200 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
153 152 192 manager_member_drop_view Drop member TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
154 153 193 invitation_reject_view Reject invitation TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
155 154 194 invitation_revoke_view redirects when no invitations TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
156 155 195 rower_invitations_view See Invitations TRUE 302 basic 302 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
157 156 196 raise_500 Test 500 error TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
158 157 198 request_revoke_view redirects when no invitations TRUE 302 basic 302 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
159 158 199 request_reject_view rejects request TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
160 159 200 manager_requests_view views requests TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
161 160 202 rower_edit_view edit rower preferences TRUE 302 basic 200 403 basic 403 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
162 161 204 rower_prefs_view edit more preferences TRUE 302 basic 200 403 basic 403 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
163 162 205 transactions_view view transactions (staff only) TRUE 302 basic 200 302 basic 200 302 coach 200 302 TRUE FALSE FALSE TRUE TRUE
164 163 208 rower_c2_authorize redirects to C2 auth page TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
165 164 209 rower_polar_authorize redirects to Polar TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
166 165 210 rower_revokeapp_view revoke app TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
167 166 211 rower_strava_authorize authorize on strava TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
168 167 212 rower_sporttracks_authorize authorize on sporttracks TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
169 168 213 rower_underarmour_authorize authorize on UA TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
170 169 214 rower_tp_authorize authorize on TP TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
171 170 215 rower_runkeeper_authorize authorize on RK TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
172 171 216 rower_sporttracks_token_refresh refresh token TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
173 172 217 rower_underarmour_token_refresh refresh token TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
174 173 218 rower_tp_token_refresh refresh token TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
175 174 219 rower_c2_token_refresh refresh token TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
176 175 220 rower_favoritecharts_view See favorite charts TRUE 302 pro 200 302 pro 403 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
177 176 222 workout_workflow_config2_view configure workflow TRUE 302 basic 200 302 basic 403 403 coach 200 403 FALSE TRUE FALSE FALSE FALSE
178 177 224 workflow_default_view resets workflow to default TRUE 302 basic 302 302 FALSE 403 403 FALSE 302 403 FALSE FALSE FALSE TRUE TRUE
179 178 225 sendmail feedback form TRUE 302 basic 302 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
180 180 232 laboratory_view lab TRUE 302 basic 200 302 basic 403 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
181 181 233 errormessage_view not used TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
182 182 237 payment_confirm_view confirm payment TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
183 183 238 upgrade_confirm_view confirm upgrade TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
184 184 240 downgrade_confirm_view confirm downgrade TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
185 185 241 billing_view confirm billing TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
186 186 242 upgrade_view confirm upgrade TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
187 187 243 downgrade_view confirm downgrade TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
188 188 244 payment_completed_view payment completed TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
189 189 245 downgrade_completed_view downgrade completed TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
190 190 246 paidplans_view paid plans TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE TRUE TRUE
191 191 247 plan_stop_view stop plan TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
192 192 248 plan_tobasic_view reset to basic TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
193 193 249 checkouts_view checkout TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
194 194 250 upgrade_checkouts_view upgrade checkout TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
195 195 251 downgrade_checkouts_view downgrade checkout TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
196 196 252 planrequired_view a plan is required TRUE 302 basic 302 302 basic 302 302 coach 302 302 FALSE FALSE FALSE TRUE TRUE
197 197 253 start_trial_view payments TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
198 198 254 start_plantrial_view paid plans TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
199 199 256 rower_register_view register rower TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE TRUE TRUE
200 200 257 freecoach_register_view register coach TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE TRUE TRUE
201 201 259 workout_workflow_view workout workflow vieq TRUE 302 basic 200 200 basic 200 200 coach 200 200 FALSE FALSE TRUE TRUE TRUE
202 202 260 workout_flexchart3_view flex chart TRUE 302 basic 200 403 basic 200 200 coach 200 200 FALSE FALSE TRUE TRUE TRUE
203 203 264 rower_process_testcallback test callback TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
204 204 265 rower_create_trainingplan create training plan TRUE 302 plan 200 302 FALSE 403 403 FALSE 200 403 FALSE FALSE FALSE TRUE TRUE
205 205 267 TrainingPlanDelete delete training plan TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
206 206 268 MicroCycleDelete delete training plan TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
207 207 269 MesoCycleDelete delete training plan TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
208 208 270 MacroCycleDelete delete training plan TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
209 209 271 rower_trainingplan_view see training plan TRUE 200 plan 200 302 plan 403 403 coach 200 403 FALSE TRUE FALSE FALSE FALSE
210 210 279 rower_trainingplan_execution_view see training plan execution TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
211 211 281 TrainingMacroCycleUpdate update macro cycle TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
212 212 282 TrainingMesoCycleUpdate update cycle TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
213 213 283 planmacrocyclebymonth update cycle TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
214 214 285 planmesocyclebyweek plan meso by week TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
215 215 287 TrainingMicroCycleUpdate update cycle TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
216 216 288 rower_delete_trainingtarget delete training target TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
217 217 289 TrainingPlanUpdate update trainingplan TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
218 218 290 TrainingTargetUpdate update training target TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
219 219 291 strokedataform test strokedata TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
220 220 292 plannedsession_teamcreate_view create planned session for team TRUE 302 coach 200 302 FALSE 200 302 FALSE 200 302 FALSE FALSE FALSE TRUE TRUE
221 221 296 plannedsession_teamedit_view edit planned sesssion TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
222 222 298 plannedsession_create_view create planned session TRUE 302 plan 200 302 FALSE 200 302 coach 200 403 FALSE TRUE FALSE TRUE TRUE
223 223 300 plannedsession_multiclone_view clone multiple planned sessions TRUE 302 plan 200 302 plan 403 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
224 224 302 plannedsession_multicreate_view create multiple planned sessions TRUE 302 plan 200 302 plan 403 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
225 225 305 plannedsession_edit_view edit planned sesssion TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
226 226 306 plannedsession_totemplate_view planned session to template TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
227 227 307 plannedsession_compare_view compare workouts from planned session TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
228 228 310 plannedsession_clone_view clone planned session TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
229 229 311 plannedsession_teamclone_view clone team planned session TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
230 230 313 plannedsession_detach_view remove workout from session TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
231 231 315 plannedsession_view view session TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
232 232 317 PlannedSessionDelete delete session TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
233 233 319 plannedsessions_manage_view manage planned sesions TRUE 302 plan 200 302 plan 403 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
234 234 323 plannedsessions_coach_view team view TRUE 302 basic 200 403 basic 403 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
235 235 325 plannedsessions_coach_icsemail_view team view TRUE 302 coach 302 403 coach 302 403 coach 302 403 FALSE TRUE FALSE TRUE TRUE
236 236 326 plannedsessions_print_view print view TRUE 302 basic 200 403 basic 403 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
237 237 327 plannedsession_comment_view comment on planned session TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
238 238 330 plannedsessions_icsemail_view send ICS email TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE TRUE FALSE FALSE FALSE
239 239 332 plannedsessions_view View planned sessions TRUE 302 basic 200 403 basic 403 403 coach 200 403 FALSE TRUE FALSE TRUE TRUE
240 240 334 course_edit_view Edit course TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
241 241 335 course_delete_view Delete course TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
242 242 336 course_kmldownload_view Kmldownload course TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
243 243 337 course_replace_view Replace course TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
244 244 338 course_view view TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
245 245 339 course_map_view Map course TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
246 246 341 c2listdebug_view Debug view TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
247 247 343 rower_process_callback Tested in specific test suite TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
248 248 344 rower_process_stravacallback Tested in specific test suite TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
249 249 345 rower_process_sporttrackscallback Tested in specific test suite TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
250 250 346 rower_process_underarmourcallback Tested in specific test suite TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
251 251 347 rower_process_polarcallback Tested in specific test suite TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
252 252 348 rower_process_runkeepercallback Tested in specific test suite TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
253 253 349 rower_process_tpcallback Tested in specific test suite TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
254 254 350 rower_process_twittercallback Tested in specific test suite TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
255 255 351 set_language Set language TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
256 256 352 SetOffsetView Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
257 257 353 stats Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
258 258 354 stats_json Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
259 259 355 jobs Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
260 260 356 workers Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
261 261 357 worker_details Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
262 262 358 finished_jobs Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
263 263 359 started_jobs Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
264 264 360 deferred_jobs Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
265 265 361 clear_queue Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
266 266 362 requeue_all Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
267 267 363 job_detail Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
268 268 364 delete_job Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
269 269 365 actions Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
270 270 366 requeue_job_view Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
271 271 367 enqueue_job Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
272 272 368 JavaScriptCatalog Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
273 273 369 serve Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
274 274 371 LoginView Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
275 275 372 LogoutView Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
276 276 373 PasswordChangeView Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
277 277 374 PasswordChangeDoneView Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
278 278 375 PasswordResetView Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
279 279 376 PasswordResetDoneView Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
280 280 377 PasswordResetConfirmView Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
281 281 378 PasswordResetCompleteView Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
282 282 386 index Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
283 283 387 login Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
284 284 388 logout Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
285 285 389 password_change Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
286 286 390 password_change_done Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
287 287 391 i18n_javascript Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
288 288 392 shortcut Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
289 289 393 changelist_view Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
290 290 394 add_view Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
291 291 395 autocomplete_view Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
292 292 396 history_view Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
293 293 397 delete_view Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
294 294 398 change_view Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
295 295 399 RedirectView Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
296 296 568 app_index Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
297 297 571 landingview Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
298 298 573 get_blog_posts Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
299 299 576 AuthorizationView Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
300 300 577 TokenView Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
301 301 578 WorkoutViewSet Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
302 302 582 RowerViewSet Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
303 303 586 FavoriteChartViewSet Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
304 304 590 APIRootView Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
305 305 592 SwaggerSchemaView Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
306 306 595 strokedatajson Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE FALSE FALSE
307 307 596 error500_view Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
308 308 603 failed_queue_view Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
309 309 604 failed_queue_empty Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE FALSE FALSE FALSE
310 310 605 failed_job_view Other Apps views TRUE 200 basic 200 302 basic 200 302 coach 200 302 FALSE FALSE TRUE FALSE FALSE
+3 -1
View File
@@ -42,6 +42,8 @@ oauth_data = {
'scope':'write', 'scope':'write',
} }
from rowers.rower_rules import is_workout_user
# Checks if user has UnderArmour token, renews them if they are expired # Checks if user has UnderArmour token, renews them if they are expired
def tp_open(user): def tp_open(user):
@@ -171,7 +173,7 @@ def workout_tp_upload(user,w):
# need some code if token doesn't refresh # need some code if token doesn't refresh
if (checkworkoutuser(user,w)): if (is_workout_user(user,w)):
tcxfile = createtpworkoutdata(w) tcxfile = createtpworkoutdata(w)
if tcxfile: if tcxfile:
res,reason,status_code,headers = uploadactivity( res,reason,status_code,headers = uploadactivity(
+1 -1
View File
@@ -9,7 +9,7 @@ from django.test import TestCase, Client,override_settings
from django.core.management import call_command from django.core.management import call_command
from django.utils.six import StringIO from django.utils.six import StringIO
from django.test.client import RequestFactory from django.test.client import RequestFactory
from .views import checkworkoutuser,c2_open from .views import c2_open
from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage
from rowers.forms import DocumentsForm,CNsummaryForm,RegistrationFormUniqueEmail from rowers.forms import DocumentsForm,CNsummaryForm,RegistrationFormUniqueEmail
import rowers.plots as plots import rowers.plots as plots
+2 -1
View File
@@ -9,6 +9,7 @@ import numpy
import rowers.mytypes as mytypes import rowers.mytypes as mytypes
from rowers.mytypes import otwtypes from rowers.mytypes import otwtypes
from rowers.rower_rules import is_workout_user
from rowsandall_app.settings import ( from rowsandall_app.settings import (
UNDERARMOUR_CLIENT_KEY, UNDERARMOUR_CLIENT_KEY,
@@ -331,7 +332,7 @@ def workout_ua_upload(user,w):
# ready to upload. Hurray # ready to upload. Hurray
if (checkworkoutuser(user,w)): if (is_workout_user(user,w)):
data = createunderarmourworkoutdata(w) data = createunderarmourworkoutdata(w)
# return HttpResponse(json.dumps(data)) # return HttpResponse(json.dumps(data))
if not data: if not data:
+10 -8
View File
@@ -14,6 +14,8 @@ from rowers.tasks import (
from rowers.models import GraphImage from rowers.models import GraphImage
from rowers.rower_rules import ispromember
from PIL import Image from PIL import Image
import numpy as np import numpy as np
@@ -472,6 +474,8 @@ import rowers.runkeeperstuff as runkeeperstuff
import rowers.underarmourstuff as underarmourstuff import rowers.underarmourstuff as underarmourstuff
import rowers.tpstuff as tpstuff import rowers.tpstuff as tpstuff
from rowers.rower_rules import is_promember
def set_workouttype(w,options): def set_workouttype(w,options):
try: try:
w.workouttype = options['workouttype'] w.workouttype = options['workouttype']
@@ -500,8 +504,6 @@ def make_private(w,options):
return 1 return 1
from rowers.utils import isprorower
def do_sync(w,options): def do_sync(w,options):
try: try:
if options['stravaid'] != 0: if options['stravaid'] != 0:
@@ -511,7 +513,7 @@ def do_sync(w,options):
except KeyError: except KeyError:
pass pass
if ('upload_to_C2' in options and options['upload_to_C2']) or (w.user.c2_auto_export and isprorower(w.user)): if ('upload_to_C2' in options and options['upload_to_C2']) or (w.user.c2_auto_export and ispromember(w.user)):
if ('upload_to_C2' in options and not options['upload_to_C2']): if ('upload_to_C2' in options and not options['upload_to_C2']):
pass pass
else: else:
@@ -521,7 +523,7 @@ def do_sync(w,options):
id = 0 id = 0
message = "Something went wrong with the Concept2 sync" message = "Something went wrong with the Concept2 sync"
if ('upload_to_Strava' in options and options['upload_to_Strava']) or (w.user.strava_auto_export and isprorower(w.user)): if ('upload_to_Strava' in options and options['upload_to_Strava']) or (w.user.strava_auto_export and ispromember(w.user)):
if ('upload_to_Strava' in options and not options['upload_to_Strava']): if ('upload_to_Strava' in options and not options['upload_to_Strava']):
pass pass
else: else:
@@ -534,7 +536,7 @@ def do_sync(w,options):
message = "Please connect to Strava first" message = "Please connect to Strava first"
if ('upload_to_SportTracks' in options and options['upload_to_SportTracks']) or (w.user.sporttracks_auto_export and isprorower(w.user)): if ('upload_to_SportTracks' in options and options['upload_to_SportTracks']) or (w.user.sporttracks_auto_export and ispromember(w.user)):
if ('upload_to_SportTracks' in options and not options['upload_to_SportTracks']): if ('upload_to_SportTracks' in options and not options['upload_to_SportTracks']):
pass pass
else: else:
@@ -548,7 +550,7 @@ def do_sync(w,options):
id = 0 id = 0
if ('upload_to_RunKeeper' in options and options['upload_to_RunKeeper']) or (w.user.runkeeper_auto_export and isprorower(w.user)): if ('upload_to_RunKeeper' in options and options['upload_to_RunKeeper']) or (w.user.runkeeper_auto_export and ispromember(w.user)):
if ('upload_to_RunKeeper' in options and not options['upload_to_RunKeeper']): if ('upload_to_RunKeeper' in options and not options['upload_to_RunKeeper']):
pass pass
else: else:
@@ -561,7 +563,7 @@ def do_sync(w,options):
message = "Please connect to Runkeeper first" message = "Please connect to Runkeeper first"
id = 0 id = 0
if ('upload_to_MapMyFitness' in options and options['upload_to_MapMyFitness']) or (w.user.mapmyfitness_auto_export and isprorower(w.user)): if ('upload_to_MapMyFitness' in options and options['upload_to_MapMyFitness']) or (w.user.mapmyfitness_auto_export and ispromember(w.user)):
if ('upload_to_MapMyFitness' in options and not options['upload_to_MapMyFitness']): if ('upload_to_MapMyFitness' in options and not options['upload_to_MapMyFitness']):
pass pass
else: else:
@@ -574,7 +576,7 @@ def do_sync(w,options):
id = 0 id = 0
if ('upload_to_TrainingPeaks' in options and options['upload_to_TrainingPeaks']) or (w.user.trainingpeaks_auto_export and isprorower(w.user)): if ('upload_to_TrainingPeaks' in options and options['upload_to_TrainingPeaks']) or (w.user.trainingpeaks_auto_export and ispromember(w.user)):
if ('upload_to_TrainingPeaks' in options and not options['upload_to_TrainingPeaks']): if ('upload_to_TrainingPeaks' in options and not options['upload_to_TrainingPeaks']):
pass pass
else: else:
+30 -25
View File
@@ -153,6 +153,8 @@ urlpatterns = [
views.agegrouprecordview,name='agegrouprecordview'), views.agegrouprecordview,name='agegrouprecordview'),
re_path(r'^agegrouprecords/(?P<sex>\w+.*)/(?P<weightcategory>\w+.*)/$', re_path(r'^agegrouprecords/(?P<sex>\w+.*)/(?P<weightcategory>\w+.*)/$',
views.agegrouprecordview,name='agegrouprecordview'), views.agegrouprecordview,name='agegrouprecordview'),
re_path(r'^agegrouprecords/$',
views.agegrouprecordview,name='agegrouprecordview'),
re_path(r'^list-workouts/ranking/$',views.workouts_view,{'rankingonly':True}, re_path(r'^list-workouts/ranking/$',views.workouts_view,{'rankingonly':True},
name='workouts_view'), name='workouts_view'),
# re_path(r'^list-workouts/team/(?P<teamid>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.workouts_view, # re_path(r'^list-workouts/team/(?P<teamid>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.workouts_view,
@@ -191,7 +193,7 @@ urlpatterns = [
views.virtualevent_submit_result_view,name='virtualevent_submit_result_view'), views.virtualevent_submit_result_view,name='virtualevent_submit_result_view'),
re_path(r'^virtualevent/(?P<id>\d+)/submit/(?P<workoutid>\b[0-9A-Fa-f]+\b)/$', re_path(r'^virtualevent/(?P<id>\d+)/submit/(?P<workoutid>\b[0-9A-Fa-f]+\b)/$',
views.virtualevent_submit_result_view,name='virtualevent_submit_result_view'), views.virtualevent_submit_result_view,name='virtualevent_submit_result_view'),
re_path(r'^virtualevent/(?P<raceid>\d+)/disqualify/(?P<recordid>\d+)/', re_path(r'^virtualevent/(?P<id>\d+)/disqualify/(?P<recordid>\d+)/',
views.virtualevent_disqualify_view,name='virtualevent_disqualify_view'), views.virtualevent_disqualify_view,name='virtualevent_disqualify_view'),
re_path(r'^list-workouts/$',views.workouts_view,name='workouts_view'), re_path(r'^list-workouts/$',views.workouts_view,name='workouts_view'),
re_path(r'^list-courses/$',views.courses_view,name='courses_view'), re_path(r'^list-courses/$',views.courses_view,name='courses_view'),
@@ -218,6 +220,7 @@ urlpatterns = [
# re_path(r'^workouts-join-select/team/(?P<teamid>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.workouts_join_select, # re_path(r'^workouts-join-select/team/(?P<teamid>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.workouts_join_select,
# name='workouts_join_select'), # name='workouts_join_select'),
re_path(r'^workouts-join/$',views.workouts_join_view,name='workouts_join_view'), re_path(r'^workouts-join/$',views.workouts_join_view,name='workouts_join_view'),
re_path(r'^workouts-join/user/(?P<userid>\d+)$',views.workouts_join_view,name='workouts_join_view'),
re_path(r'^workouts-join-select/team/(?P<teamid>\d+)/$',views.workouts_join_select,name='workouts_join_select'), re_path(r'^workouts-join-select/team/(?P<teamid>\d+)/$',views.workouts_join_select,name='workouts_join_select'),
# re_path(r'^workouts-join-select/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.workouts_join_select,name='workouts_join_select'), # re_path(r'^workouts-join-select/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.workouts_join_select,name='workouts_join_select'),
re_path(r'^workouts-join-select/$',views.workouts_join_select,name='workouts_join_select'), re_path(r'^workouts-join-select/$',views.workouts_join_select,name='workouts_join_select'),
@@ -243,26 +246,27 @@ urlpatterns = [
re_path(r'^record-progress/(?P<id>.*)/$',views.post_progress), re_path(r'^record-progress/(?P<id>.*)/$',views.post_progress),
re_path(r'^record-progress/$',views.post_progress), re_path(r'^record-progress/$',views.post_progress),
re_path(r'^list-graphs/$',views.graphs_view,name='graphs_view'), re_path(r'^list-graphs/$',views.graphs_view,name='graphs_view'),
re_path(r'^list-graphs/user/(?P<userid>\d+)/$',views.graphs_view,name='graphs_view'),
re_path(r'^fitness-progress/$',views.fitnessmetric_view,name='fitnessmetric_view'), re_path(r'^fitness-progress/$',views.fitnessmetric_view,name='fitnessmetric_view'),
re_path(r'^fitness-progress/user/(?P<id>\d+)/$',views.fitnessmetric_view,name='fitnessmetric_view'), re_path(r'^fitness-progress/user/(?P<userid>\d+)/$',views.fitnessmetric_view,name='fitnessmetric_view'),
re_path(r'^fitness-progress/user/(?P<id>\d+)/(?P<mode>\w+.*)/$',views.fitnessmetric_view,name='fitnessmetric_view'), re_path(r'^fitness-progress/user/(?P<userid>\d+)/(?P<mode>\w+.*)/$',views.fitnessmetric_view,name='fitnessmetric_view'),
# re_path(r'^ote-bests/user/(?P<theuser>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.rankings_view,name='rankings_view'), # re_path(r'^ote-bests/user/(?P<theuser>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.rankings_view,name='rankings_view'),
re_path(r'^ote-bests/user/(?P<theuser>\d+)/$',views.rankings_view,name='rankings_view'), re_path(r'^ote-bests/user/(?P<userid>\d+)/$',views.rankings_view,name='rankings_view'),
# re_path(r'^ote-bests/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.rankings_view,name='rankings_view'), # re_path(r'^ote-bests/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.rankings_view,name='rankings_view'),
re_path(r'^ote-bests/$',views.rankings_view,name='rankings_view'), re_path(r'^ote-bests/$',views.rankings_view,name='rankings_view'),
re_path(r'^(?P<theuser>\d+)/ote-bests/$',views.rankings_view,name='rankings_view'), re_path(r'^(?P<userid>\d+)/ote-bests/$',views.rankings_view,name='rankings_view'),
# re_path(r'^(?P<theuser>\d+)/ote-bests2/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.rankings_view2,name='rankings_view2'), # re_path(r'^(?P<theuser>\d+)/ote-bests2/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.rankings_view2,name='rankings_view2'),
re_path(r'^ote-bests2/user/(?P<theuser>\d+)/$',views.rankings_view2,name='rankings_view2'), re_path(r'^ote-bests2/user/(?P<userid>\d+)/$',views.rankings_view2,name='rankings_view2'),
# re_path(r'^ote-bests2/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.rankings_view2,name='rankings_view2'), # re_path(r'^ote-bests2/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.rankings_view2,name='rankings_view2'),
re_path(r'^ote-bests2/$',views.rankings_view2,name='rankings_view2'), re_path(r'^ote-bests2/$',views.rankings_view2,name='rankings_view2'),
# re_path(r'^otw-bests/user/(?P<theuser>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.otwrankings_view,name='otwrankings_view'), # re_path(r'^otw-bests/user/(?P<theuser>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.otwrankings_view,name='otwrankings_view'),
# re_path(r'^otw-bests/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.otwrankings_view,name='otwrankings_view'), # re_path(r'^otw-bests/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.otwrankings_view,name='otwrankings_view'),
re_path(r'^otw-bests/user/(?P<theuser>\d+)/$',views.otwrankings_view,name='otwrankings_view'), re_path(r'^otw-bests/user/(?P<userid>\d+)/$',views.otwrankings_view,name='otwrankings_view'),
re_path(r'^otw-bests/$',views.otwrankings_view,name='otwrankings_view'), re_path(r'^otw-bests/$',views.otwrankings_view,name='otwrankings_view'),
# re_path(r'^ote-ranking/user/(?P<theuser>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.oterankings_view,name='oterankings_view'), # re_path(r'^ote-ranking/user/(?P<theuser>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.oterankings_view,name='oterankings_view'),
# re_path(r'^ote-ranking/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.oterankings_view,name='oterankings_view'), # re_path(r'^ote-ranking/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.oterankings_view,name='oterankings_view'),
re_path(r'^ote-ranking/$',views.oterankings_view,name='oterankings_view'), re_path(r'^ote-ranking/$',views.oterankings_view,name='oterankings_view'),
re_path(r'^ote-ranking/user/(?P<theuser>\d+)/$',views.oterankings_view,name='oterankings_view'), re_path(r'^ote-ranking/user/(?P<userid>\d+)/$',views.oterankings_view,name='oterankings_view'),
# re_path(r'^flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/user/(?P<theuser>\d+)/$',views.cum_flex,name='cum_flex'), # re_path(r'^flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/user/(?P<theuser>\d+)/$',views.cum_flex,name='cum_flex'),
# re_path(r'^flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.cum_flex,name='cum_flex'), # re_path(r'^flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.cum_flex,name='cum_flex'),
re_path(r'^flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/$',views.cum_flex,name='cum_flex'), re_path(r'^flexall/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/$',views.cum_flex,name='cum_flex'),
@@ -270,11 +274,11 @@ urlpatterns = [
re_path(r'^flexall/user/(?P<theuser>\d+)/$',views.cum_flex,name='cum_flex'), re_path(r'^flexall/user/(?P<theuser>\d+)/$',views.cum_flex,name='cum_flex'),
re_path(r'^flexall/$',views.cum_flex,name='cum_flex'), re_path(r'^flexall/$',views.cum_flex,name='cum_flex'),
re_path(r'^flexalldata/$',views.cum_flex_data,name='cum_flex_data'), re_path(r'^flexalldata/$',views.cum_flex_data,name='cum_flex_data'),
re_path(r'^histo/user/(?P<theuser>\d+)/$',views.histo,name='histo'), re_path(r'^histo/user/(?P<userid>\d+)/$',views.histo,name='histo'),
re_path(r'^histodata/$',views.histo_data,name='histo_data'), re_path(r'^histodata/$',views.histo_data,name='histo_data'),
# re_path(r'^histo/user/(?P<theuser>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.histo,name='histo'), # re_path(r'^histo/user/(?P<theuser>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.histo,name='histo'),
re_path(r'^histo/$',views.histo,name='histo'), re_path(r'^histo/$',views.histo,name='histo'),
re_path(r'^cumstats/user/(?P<theuser>\d+)/$',views.cumstats,name='cumstats'), re_path(r'^cumstats/user/(?P<userid>\d+)/$',views.cumstats,name='cumstats'),
# re_path(r'^cumstats/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.cumstats,name='cumstats'), # re_path(r'^cumstats/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.cumstats,name='cumstats'),
# re_path(r'^cumstats/user/(?P<theuser>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.cumstats,name='cumstats'), # re_path(r'^cumstats/user/(?P<theuser>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.cumstats,name='cumstats'),
# re_path(r'^cumstats/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.cumstats,name='cumstats'), # re_path(r'^cumstats/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/$',views.cumstats,name='cumstats'),
@@ -289,8 +293,6 @@ urlpatterns = [
name='otw_use_gps'), name='otw_use_gps'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/toggle-ranking/$',views.workout_toggle_ranking, re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/toggle-ranking/$',views.workout_toggle_ranking,
name='workout_toggle_ranking'), name='workout_toggle_ranking'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/get-testscript/$',views.get_testscript,
name='get_testscript'),
re_path(r'^workout/upload/team/$',views.team_workout_upload_view,name='team_workout_upload_view'), re_path(r'^workout/upload/team/$',views.team_workout_upload_view,name='team_workout_upload_view'),
re_path(r'^workout/upload/$',views.workout_upload_view,name='workout_upload_view'), re_path(r'^workout/upload/$',views.workout_upload_view,name='workout_upload_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/histo/$',views.workout_histo_view, re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/histo/$',views.workout_histo_view,
@@ -355,6 +357,7 @@ urlpatterns = [
re_path(r'^video/(?P<id>\w.+)/$',views.workout_video_view, re_path(r'^video/(?P<id>\w.+)/$',views.workout_video_view,
name='workout_video_view'), name='workout_video_view'),
re_path(r'^videos/',views.list_videos,name='list_videos'), re_path(r'^videos/',views.list_videos,name='list_videos'),
re_path(r'^add-video/user/(?P<userid>\d+)/$',views.video_selectworkout,name='video_selectworkout'),
re_path(r'^add-video/',views.video_selectworkout,name='video_selectworkout'), re_path(r'^add-video/',views.video_selectworkout,name='video_selectworkout'),
# re_path(r'^workout/(?P<id>\d+)/$',views.workout_view,name='workout_view'), # re_path(r'^workout/(?P<id>\d+)/$',views.workout_view,name='workout_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/$',views.workout_view,name='workout_view'), re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/$',views.workout_view,name='workout_view'),
@@ -418,6 +421,7 @@ urlpatterns = [
re_path(r'^workout/polarimport/user/(?P<userid>\d+)/',views.workout_polarimport_view,name='workout_polarimport_view'), re_path(r'^workout/polarimport/user/(?P<userid>\d+)/',views.workout_polarimport_view,name='workout_polarimport_view'),
re_path(r'^workout/runkeeperimport/$',views.workout_runkeeperimport_view,name='workout_runkeeperimport_view'), re_path(r'^workout/runkeeperimport/$',views.workout_runkeeperimport_view,name='workout_runkeeperimport_view'),
re_path(r'^workout/runkeeperimport/user/(?P<userid>\d+)/$',views.workout_runkeeperimport_view,name='workout_runkeeperimport_view'), re_path(r'^workout/runkeeperimport/user/(?P<userid>\d+)/$',views.workout_runkeeperimport_view,name='workout_runkeeperimport_view'),
re_path(r'^workout/underarmourimport/user/(?P<userid>\d+)/$',views.workout_underarmourimport_view,name='workout_underarmourimport_view'),
re_path(r'^workout/underarmourimport/$',views.workout_underarmourimport_view,name='workout_underarmourimport_view'), re_path(r'^workout/underarmourimport/$',views.workout_underarmourimport_view,name='workout_underarmourimport_view'),
# re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/deleteconfirm/$',views.workout_delete_confirm_view), # re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/deleteconfirm/$',views.workout_delete_confirm_view),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/c2uploadw/$',views.workout_c2_upload_view,name='workout_c2_upload_view'), re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/c2uploadw/$',views.workout_c2_upload_view,name='workout_c2_upload_view'),
@@ -458,12 +462,12 @@ urlpatterns = [
re_path(r'^me/calcdps/$',views.rower_calcdps_view,name='rower_calcdps_view'), re_path(r'^me/calcdps/$',views.rower_calcdps_view,name='rower_calcdps_view'),
re_path(r'^me/exportsettings/$',views.rower_exportsettings_view,name='rower_exportsettings_view'), re_path(r'^me/exportsettings/$',views.rower_exportsettings_view,name='rower_exportsettings_view'),
re_path(r'^me/exportsettings/user/(?P<userid>\d+)/$',views.rower_exportsettings_view,name='rower_exportsettings_view'), re_path(r'^me/exportsettings/user/(?P<userid>\d+)/$',views.rower_exportsettings_view,name='rower_exportsettings_view'),
re_path(r'^team/(?P<id>\d+)/$',views.team_view,name='team_view'), re_path(r'^team/(?P<team_id>\d+)/$',views.team_view,name='team_view'),
re_path(r'^team/(?P<id>\d+)/memberstats/$',views.team_members_stats_view,name='team_members_stats_view'), re_path(r'^team/(?P<team_id>\d+)/memberstats/$',views.team_members_stats_view,name='team_members_stats_view'),
re_path(r'^team/(?P<id>\d+)/edit/$',views.team_edit_view,name='team_edit_view'), re_path(r'^team/(?P<team_id>\d+)/edit/$',views.team_edit_view,name='team_edit_view'),
re_path(r'^team/(?P<id>\d+)/leaveconfirm/$',views.team_leaveconfirm_view,name='team_leaveconfirm_view'), re_path(r'^team/(?P<id>\d+)/leaveconfirm/$',views.team_leaveconfirm_view,name='team_leaveconfirm_view'),
re_path(r'^team/(?P<id>\d+)/leave/$',views.team_leave_view,name='team_leave_view'), re_path(r'^team/(?P<id>\d+)/leave/$',views.team_leave_view,name='team_leave_view'),
re_path(r'^team/(?P<id>\d+)/deleteconfirm/$',views.team_deleteconfirm_view,name='team_deleteconfirm_view'), re_path(r'^team/(?P<team_id>\d+)/deleteconfirm/$',views.team_deleteconfirm_view,name='team_deleteconfirm_view'),
re_path(r'^team/(?P<teamid>\d+)/requestmembership/(?P<userid>\d+)/$',views.team_requestmembership_view,name='team_requestmembership_view'), re_path(r'^team/(?P<teamid>\d+)/requestmembership/(?P<userid>\d+)/$',views.team_requestmembership_view,name='team_requestmembership_view'),
re_path(r'^me/coachrequest/(?P<id>\d+)/reject/$',views.reject_revoke_coach_request, re_path(r'^me/coachrequest/(?P<id>\d+)/reject/$',views.reject_revoke_coach_request,
name='reject_revoke_coach_request'), name='reject_revoke_coach_request'),
@@ -489,7 +493,7 @@ urlpatterns = [
name='coach_accept_coachrequest_view'), name='coach_accept_coachrequest_view'),
re_path(r'^me/coachoffer/(?P<code>\w+.*)/accept/$',views.rower_accept_coachoffer_view, re_path(r'^me/coachoffer/(?P<code>\w+.*)/accept/$',views.rower_accept_coachoffer_view,
name='rower_accept_coachoffer_view'), name='rower_accept_coachoffer_view'),
re_path(r'^team/(?P<id>\d+)/delete/$',views.team_delete_view,name='team_delete_view'), re_path(r'^team/(?P<team_id>\d+)/delete/$',views.team_delete_view,name='team_delete_view'),
re_path(r'^team/create/$',views.team_create_view,name='team_create_view'), re_path(r'^team/create/$',views.team_create_view,name='team_create_view'),
re_path(r'^me/team/(?P<teamid>\d+)/drop/(?P<userid>\d+)/$',views.manager_member_drop_view,name='manager_member_drop_view'), re_path(r'^me/team/(?P<teamid>\d+)/drop/(?P<userid>\d+)/$',views.manager_member_drop_view,name='manager_member_drop_view'),
re_path(r'^me/invitation/(?P<id>\d+)/reject/$',views.invitation_reject_view,name='invitation_reject_view'), re_path(r'^me/invitation/(?P<id>\d+)/reject/$',views.invitation_reject_view,name='invitation_reject_view'),
@@ -535,10 +539,10 @@ urlpatterns = [
# re_path(r'^compatibility', TemplateView.as_view(template_name='compatibility.html'),name='about'), # re_path(r'^compatibility', TemplateView.as_view(template_name='compatibility.html'),name='about'),
# re_path(r'^videos', TemplateView.as_view(template_name='videos.html'),name='videos'), # re_path(r'^videos', TemplateView.as_view(template_name='videos.html'),name='videos'),
re_path(r'^analysis/user/(?P<userid>\d+)/$', views.analysis_view,name='analysis'), re_path(r'^analysis/user/(?P<userid>\d+)/$', views.analysis_view,name='analysis'),
re_path(r'^laboratory/user/(?P<userid>\d+)/$', views.laboratory_view,name='laboratory'), re_path(r'^laboratory/$', views.laboratory_view,name='laboratory_view'),
re_path(r'^laboratory/user/(?P<userid>\d+)/$', views.laboratory_view,name='laboratory_view'),
re_path(r'^errormessage/(?P<errormessage>[\w\ ]+.*)/$',views.errormessage_view,name='errormessage_view'), re_path(r'^errormessage/(?P<errormessage>[\w\ ]+.*)/$',views.errormessage_view,name='errormessage_view'),
re_path(r'^analysis/$', views.analysis_view,name='analysis'), re_path(r'^analysis/$', views.analysis_view,name='analysis'),
re_path(r'^laboratory/$', views.laboratory_view,name='laboratory'),
re_path(r'^promembership', TemplateView.as_view(template_name='promembership.html'),name='promembership'), re_path(r'^promembership', TemplateView.as_view(template_name='promembership.html'),name='promembership'),
re_path(r'^checkout/(?P<planid>\d+)/$',views.payment_confirm_view,name='payment_confirm_view'), re_path(r'^checkout/(?P<planid>\d+)/$',views.payment_confirm_view,name='payment_confirm_view'),
re_path(r'^upgradecheckout/(?P<planid>\d+)/$',views.upgrade_confirm_view,name='upgrade_confirm_view'), re_path(r'^upgradecheckout/(?P<planid>\d+)/$',views.upgrade_confirm_view,name='upgrade_confirm_view'),
@@ -549,7 +553,7 @@ urlpatterns = [
re_path(r'^downgrade/$',views.downgrade_view,name='downgrade'), re_path(r'^downgrade/$',views.downgrade_view,name='downgrade'),
re_path(r'^paymentcompleted/$',views.payment_completed_view,name='payment_completed_view'), re_path(r'^paymentcompleted/$',views.payment_completed_view,name='payment_completed_view'),
re_path(r'^downgradecompleted/$',views.downgrade_completed_view,name='downgrade_completed_view'), re_path(r'^downgradecompleted/$',views.downgrade_completed_view,name='downgrade_completed_view'),
re_path(r'^paidplans/$',views.paidplans_view,name='paidplans'), re_path(r'^paidplans/$',views.paidplans_view,name='paidplans_view'),
re_path(r'^me/cancelsubscriptions/$',views.plan_stop_view,name='plan_stop_view'), re_path(r'^me/cancelsubscriptions/$',views.plan_stop_view,name='plan_stop_view'),
re_path(r'^me/cancelsubscription/(?P<id>[\w\ ]+.*)/$',views.plan_tobasic_view,name='plan_tobasic_view'), re_path(r'^me/cancelsubscription/(?P<id>[\w\ ]+.*)/$',views.plan_tobasic_view,name='plan_tobasic_view'),
re_path(r'^checkouts/$',views.checkouts_view,name='checkouts'), re_path(r'^checkouts/$',views.checkouts_view,name='checkouts'),
@@ -574,7 +578,7 @@ urlpatterns = [
# re_path(r'^workout/compare/(?P<id1>\d+)/(?P<id2>\d+)/(?P<xparam>\w+.*)/(?P<yparam>[\w\ ]+.*)/$',views.workout_comparison_view2), # re_path(r'^workout/compare/(?P<id1>\d+)/(?P<id2>\d+)/(?P<xparam>\w+.*)/(?P<yparam>[\w\ ]+.*)/$',views.workout_comparison_view2),
re_path(r'^test\_callback',views.rower_process_testcallback,name='rower_process_testcallback'), re_path(r'^test\_callback',views.rower_process_testcallback,name='rower_process_testcallback'),
re_path(r'^createplan/$',views.rower_create_trainingplan,name='rower_create_trainingplan'), re_path(r'^createplan/$',views.rower_create_trainingplan,name='rower_create_trainingplan'),
re_path(r'^createplan/user/(?P<userid>\d+)/$',views.rower_create_trainingplan,name='rower_create_trainingplan'), re_path(r'^createplan/user/(?P<id>\d+)/$',views.rower_create_trainingplan,name='rower_create_trainingplan'),
re_path(r'^deleteplan/(?P<pk>\d+)/$',login_required( re_path(r'^deleteplan/(?P<pk>\d+)/$',login_required(
views.TrainingPlanDelete.as_view()),name='trainingplan_delete_view'), views.TrainingPlanDelete.as_view()),name='trainingplan_delete_view'),
re_path(r'^deletemicrocycle/(?P<pk>\d+)/$',login_required( re_path(r'^deletemicrocycle/(?P<pk>\d+)/$',login_required(
@@ -637,9 +641,9 @@ urlpatterns = [
re_path(r'^sessions/teamcreate/team/$', re_path(r'^sessions/teamcreate/team/$',
views.plannedsession_teamcreate_view, views.plannedsession_teamcreate_view,
name='plannedsession_teamcreate_view'), name='plannedsession_teamcreate_view'),
re_path(r'^sessions/teamedit/(?P<sessionid>\d+)/$',views.plannedsession_teamedit_view, re_path(r'^sessions/teamedit/(?P<id>\d+)/$',views.plannedsession_teamedit_view,
name='plannedsession_teamedit_view'), name='plannedsession_teamedit_view'),
re_path(r'^sessions/teamedit/(?P<sessionid>\d+)/user/(?P<userid>\d+)/$', re_path(r'^sessions/teamedit/(?P<id>\d+)/user/(?P<userid>\d+)/$',
views.plannedsession_teamedit_view, views.plannedsession_teamedit_view,
name='plannedsession_teamedit_view'), name='plannedsession_teamedit_view'),
re_path(r'^sessions/create/$',views.plannedsession_create_view, re_path(r'^sessions/create/$',views.plannedsession_create_view,
@@ -653,7 +657,7 @@ urlpatterns = [
# re_path(r'^sessions/create/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/user/(?P<userid>\d+)/$', # re_path(r'^sessions/create/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)/user/(?P<userid>\d+)/$',
# views.plannedsession_create_view, # views.plannedsession_create_view,
# name='plannedsession_create_view'), # name='plannedsession_create_view'),
re_path(r'^sessions/multiclone/$',views.plannedsession_multiclone_view), re_path(r'^sessions/multiclone/$',views.plannedsession_multiclone_view,name='plannedsession_multiclone_view'),
re_path(r'^sessions/multiclone/user/(?P<userid>\d+)/$', re_path(r'^sessions/multiclone/user/(?P<userid>\d+)/$',
views.plannedsession_multiclone_view, views.plannedsession_multiclone_view,
name='plannedsession_multiclone_view'), name='plannedsession_multiclone_view'),
@@ -666,6 +670,7 @@ urlpatterns = [
views.plannedsession_multicreate_view, views.plannedsession_multicreate_view,
name='plannedsession_multicreate_view'), name='plannedsession_multicreate_view'),
re_path(r'^sessions/(?P<id>\d+)/edit/$',views.plannedsession_edit_view), re_path(r'^sessions/(?P<id>\d+)/edit/$',views.plannedsession_edit_view),
re_path(r'^sessions/(?P<id>\d+)/templateedit/',views.plannedsession_templateedit_view),
re_path(r'^sessions/(?P<id>\d+)/maketemplate/$',views.plannedsession_totemplate_view), re_path(r'^sessions/(?P<id>\d+)/maketemplate/$',views.plannedsession_totemplate_view),
re_path(r'^sessions/(?P<id>\d+)/compare/$', re_path(r'^sessions/(?P<id>\d+)/compare/$',
views.plannedsession_compare_view, views.plannedsession_compare_view,
@@ -716,7 +721,7 @@ urlpatterns = [
re_path(r'^sessions/print/user/(?P<userid>\d+)/$',views.plannedsessions_print_view, re_path(r'^sessions/print/user/(?P<userid>\d+)/$',views.plannedsessions_print_view,
name='plannedsessions_print_view'), name='plannedsessions_print_view'),
re_path(r'^sessions/sendcalendar/$',views.plannedsessions_icsemail_view, re_path(r'^sessions/sendcalendar/$',views.plannedsessions_icsemail_view,
name='plannedsessions_icsemail_view'), name='plannedsessions_coach_icsemail_view'),
re_path(r'^sessions/sendcalendar/user/(?P<userid>\d+)/$',views.plannedsessions_icsemail_view, re_path(r'^sessions/sendcalendar/user/(?P<userid>\d+)/$',views.plannedsessions_icsemail_view,
name='plannedsessions_icsemail_view'), name='plannedsessions_icsemail_view'),
re_path(r'^sessions/$',views.plannedsessions_view, re_path(r'^sessions/$',views.plannedsessions_view,
-12
View File
@@ -405,17 +405,6 @@ def totaltime_sec_to_string(totaltime):
return duration return duration
def isprorower(r):
result = False
result = r.rowerplan in ['pro','coach','plan']
if not result and r.protrialexpires:
result = r.rowerplan == 'basic' and r.protrialexpires >= datetime.date.today()
if not result and r.rowerplan == 'freecoach':
if r.mycoachgroup is not None:
result = len(r.mycoachgroup)>=4
return result
def iscoach(m,r): def iscoach(m,r):
result = False result = False
@@ -518,4 +507,3 @@ def allsundays(startdate,enddate):
while d<=enddate: while d<=enddate:
yield d yield d
d += datetime.timedelta(days=7) d += datetime.timedelta(days=7)
+125 -137
View File
@@ -33,6 +33,7 @@ defaultoptions = {
@user_passes_test(ispromember, login_url="/rowers/paidplans", @user_passes_test(ispromember, login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None) redirect_field_name=None)
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def analysis_new(request,userid=0,function='boxplot',teamid=0): def analysis_new(request,userid=0,function='boxplot',teamid=0):
r = getrequestrower(request, userid=userid) r = getrequestrower(request, userid=userid)
user = r.user user = r.user
@@ -669,6 +670,7 @@ def boxplotdata(workouts,options):
@user_passes_test(ispromember,login_url="/rowers/paidplans", @user_passes_test(ispromember,login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None) redirect_field_name=None)
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def analysis_view_data(request,userid=0): def analysis_view_data(request,userid=0):
if not request.is_ajax(): if not request.is_ajax():
url = reverse('analysis_new') url = reverse('analysis_new')
@@ -727,7 +729,8 @@ def analysis_view_data(request,userid=0):
@user_passes_test(ispromember,login_url="/rowers/paidplans", @user_passes_test(ispromember,login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None) redirect_field_name=None)
def histo(request,theuser=0, @permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def histo(request,userid=0,
startdate=timezone.now()-datetime.timedelta(days=365), startdate=timezone.now()-datetime.timedelta(days=365),
enddate=timezone.now(), enddate=timezone.now(),
deltadays=-1, deltadays=-1,
@@ -741,7 +744,7 @@ def histo(request,theuser=0,
'histoparam':'power' 'histoparam':'power'
}): }):
r = getrequestrower(request,userid=theuser) r = getrequestrower(request,userid=userid)
theuser = r.user theuser = r.user
if 'histoparam' in request.session: if 'histoparam' in request.session:
@@ -1327,17 +1330,18 @@ def cum_flex(request,theuser=0,
def planrequired_view(request): def planrequired_view(request):
messages.info(request,"This functionality requires Coach or Self-Coach membership") messages.info(request,"This functionality requires Coach or Self-Coach membership")
return HttpResponseRedirect(reverse('paidplans')) return HttpResponseRedirect(reverse('paidplans_view'))
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans", @user_passes_test(isplanmember,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan", message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) redirect_field_name=None)
def fitnessmetric_view(request,id=0,mode='rower', @permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def fitnessmetric_view(request,userid=0,mode='rower',
startdate=timezone.now()-timezone.timedelta(days=365), startdate=timezone.now()-timezone.timedelta(days=365),
enddate=timezone.now()): enddate=timezone.now()):
therower = getrequestrower(request,userid=id) therower = getrequestrower(request,userid=userid)
theuser = therower.user theuser = therower.user
@@ -1387,7 +1391,8 @@ def fitnessmetric_view(request,id=0,mode='rower',
# Show ranking distances including predicted paces # Show ranking distances including predicted paces
@login_required() @login_required()
def rankings_view(request,theuser=0, @permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def rankings_view(request,userid=0,
startdate=timezone.now()-datetime.timedelta(days=365), startdate=timezone.now()-datetime.timedelta(days=365),
enddate=timezone.now(), enddate=timezone.now(),
deltadays=-1, deltadays=-1,
@@ -1408,25 +1413,25 @@ def rankings_view(request,theuser=0,
enddate = startdate enddate = startdate
startdate = s startdate = s
if theuser == 0:
theuser = request.user.id
promember=0 promember=0
if not request.user.is_anonymous: r = getrequestrower(request,userid=userid)
r = getrower(request.user) theuser = r.user.id
if r.birthdate:
age = calculate_age(r.birthdate)
worldclasspower = int(metrics.getagegrouprecord(
age,
sex=r.sex,
weightcategory=r.weightcategory,
))
else:
worldclasspower = None
result = request.user.is_authenticated and ispromember(request.user) if r.birthdate:
if result: age = calculate_age(r.birthdate)
promember=1 worldclasspower = int(c2stuff.getagegrouprecord(
age,
sex=r.sex,
weightcategory=r.weightcategory,
))
else:
worldclasspower = None
result = request.user.is_authenticated and ispromember(request.user)
if result:
promember=1
# get all indoor rows in date range # get all indoor rows in date range
@@ -1737,7 +1742,8 @@ def ajax_agegrouprecords(request,
# Show ranking distances including predicted paces # Show ranking distances including predicted paces
@login_required() @login_required()
def rankings_view2(request,theuser=0, @permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def rankings_view2(request,userid=0,
startdate=timezone.now()-datetime.timedelta(days=365), startdate=timezone.now()-datetime.timedelta(days=365),
enddate=timezone.now(), enddate=timezone.now(),
deltadays=-1, deltadays=-1,
@@ -1758,83 +1764,84 @@ def rankings_view2(request,theuser=0,
enddate = startdate enddate = startdate
startdate = s startdate = s
if theuser == 0: if userid == 0:
theuser = request.user.id userid = request.user.id
else: else:
lastupdated = "01-01-1900" lastupdated = "01-01-1900"
promember=0 promember=0
if not request.user.is_anonymous: r = getrequestrower(request,userid=userid)
r = getrower(request.user) theuser = r.user
wcdurations = []
wcpower = []
lastupdated = "01-01-1900"
userid = 0
if 'options' in request.session:
options = request.session['options']
try:
wcdurations = options['wcdurations']
wcpower = options['wcpower']
lastupdated = options['lastupdated']
except KeyError:
pass
try:
userid = options['userid']
except KeyError:
userid = 0
else:
options = {}
lastupdatedtime = arrow.get(lastupdated).timestamp
current_time = arrow.utcnow().timestamp
deltatime_seconds = current_time - lastupdatedtime
recalc = False
if str(userid) != str(theuser) or deltatime_seconds > 3600:
recalc = True
options['lastupdated'] = arrow.utcnow().isoformat()
else:
recalc = False
options['userid'] = theuser.id
if r.birthdate:
age = calculate_age(r.birthdate)
else:
worldclasspower = None
age = 0
agerecords = CalcAgePerformance.objects.filter(
age = age,
sex = r.sex,
weightcategory = r.weightcategory)
if len(agerecords) == 0:
recalc = True
wcpower = []
wcduration = []
else:
wcdurations = [] wcdurations = []
wcpower = [] wcpower = []
for record in agerecords:
wcdurations.append(record.duration)
wcpower.append(record.power)
lastupdated = "01-01-1900" options['wcpower'] = wcpower
userid = 0 options['wcdurations'] = wcdurations
if 'options' in request.session: if theuser:
options = request.session['options'] options['userid'] = theuser.id
try:
wcdurations = options['wcdurations'] request.session['options'] = options
wcpower = options['wcpower']
lastupdated = options['lastupdated']
except KeyError:
pass
try:
userid = options['userid']
except KeyError:
userid = 0
else:
options = {}
result = request.user.is_authenticated and ispromember(request.user)
lastupdatedtime = arrow.get(lastupdated).timestamp if result:
current_time = arrow.utcnow().timestamp promember=1
deltatime_seconds = current_time - lastupdatedtime
recalc = False
if str(userid) != str(theuser) or deltatime_seconds > 3600:
recalc = True
options['lastupdated'] = arrow.utcnow().isoformat()
else:
recalc = False
options['userid'] = theuser
if r.birthdate:
age = calculate_age(r.birthdate)
else:
worldclasspower = None
age = 0
agerecords = CalcAgePerformance.objects.filter(
age = age,
sex = r.sex,
weightcategory = r.weightcategory)
if len(agerecords) == 0:
recalc = True
wcpower = []
wcduration = []
else:
wcdurations = []
wcpower = []
for record in agerecords:
wcdurations.append(record.duration)
wcpower.append(record.power)
options['wcpower'] = wcpower
options['wcdurations'] = wcdurations
if theuser:
options['userid'] = theuser
request.session['options'] = options
result = request.user.is_authenticated and ispromember(request.user)
if result:
promember=1
# get all indoor rows in date range # get all indoor rows in date range
@@ -1883,10 +1890,7 @@ def rankings_view2(request,theuser=0,
r=0 r=0
try: uu = theuser
uu = User.objects.get(id=theuser)
except User.DoesNotExist:
uu = ''
# test to fix bug # test to fix bug
@@ -2150,7 +2154,8 @@ def rankings_view2(request,theuser=0,
@user_passes_test(ispromember,login_url="/rowers/paidplans", @user_passes_test(ispromember,login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None) redirect_field_name=None)
def otwrankings_view(request,theuser=0, @permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def otwrankings_view(request,userid=0,
startdate=timezone.now()-datetime.timedelta(days=365), startdate=timezone.now()-datetime.timedelta(days=365),
enddate=timezone.now(), enddate=timezone.now(),
startdatestring="", startdatestring="",
@@ -2173,22 +2178,10 @@ def otwrankings_view(request,theuser=0,
enddate = startdate enddate = startdate
startdate = s startdate = s
if theuser == 0: r = getrequestrower(request,userid=userid)
if 'rowerid' in request.session: promember = True
try:
r = Rower.objects.get(id=request.session['rowerid'])
theuser = r.user.id
except Rower.DoesNotExist:
theuser = request.user.id
else:
theuser = request.user.id
promember=0 theuser = r.user
if not request.user.is_anonymous:
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated and ispromember(request.user)
if result:
promember=1
# get all OTW rows in date range # get all OTW rows in date range
@@ -2242,10 +2235,7 @@ def otwrankings_view(request,theuser=0,
try: uu = theuser
uu = User.objects.get(id=theuser)
except User.DoesNotExist:
uu = ''
# test to fix bug # test to fix bug
@@ -2555,7 +2545,8 @@ def otwcp_toadmin_view(request,theuser=0,
@user_passes_test(ispromember,login_url="/rowers/paidplans", @user_passes_test(ispromember,login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None) redirect_field_name=None)
def oterankings_view(request,theuser=0, @permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def oterankings_view(request,userid=0,
startdate=timezone.now()-datetime.timedelta(days=365), startdate=timezone.now()-datetime.timedelta(days=365),
enddate=timezone.now(), enddate=timezone.now(),
startdatestring="", startdatestring="",
@@ -2578,23 +2569,11 @@ def oterankings_view(request,theuser=0,
enddate = startdate enddate = startdate
startdate = s startdate = s
if theuser == 0: r = getrequestrower(request,userid=userid)
if 'rowerid' in request.session: theuser = r.user
try:
r = Rower.objects.get(id=request.session['rowerid'])
theuser = r.user.id
except Rower.DoesNotExist:
theuser = request.user.id
else:
theuser = request.user.id
promember=0 promember=0
if not request.user.is_anonymous:
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated and ispromember(request.user)
if result:
promember=1
# get all OTW rows in date range # get all OTW rows in date range
@@ -2668,10 +2647,8 @@ def oterankings_view(request,theuser=0,
raise Http404("Rower doesn't exist") raise Http404("Rower doesn't exist")
try: uu = theuser
uu = User.objects.get(id=theuser)
except User.DoesNotExist:
uu = ''
# test to fix bug # test to fix bug
@@ -2906,6 +2883,7 @@ def oterankings_view(request,theuser=0,
@user_passes_test(ispromember,login_url="/rowers/paidplans", @user_passes_test(ispromember,login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None) redirect_field_name=None)
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def user_multiflex_select(request, def user_multiflex_select(request,
startdatestring="", startdatestring="",
enddatestring="", enddatestring="",
@@ -3112,6 +3090,7 @@ def user_multiflex_select(request,
@user_passes_test(ispromember,login_url="/rowers/paidplans", @user_passes_test(ispromember,login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None) redirect_field_name=None)
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def multiflex_data(request,userid=0, def multiflex_data(request,userid=0,
options={ options={
'includereststrokes':False, 'includereststrokes':False,
@@ -3365,6 +3344,7 @@ def multiflex_data(request,userid=0,
@user_passes_test(ispromember,login_url="/rowers/paidplans", @user_passes_test(ispromember,login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None) redirect_field_name=None)
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def multiflex_view(request,userid=0, def multiflex_view(request,userid=0,
options={ options={
'includereststrokes':False, 'includereststrokes':False,
@@ -3532,6 +3512,7 @@ def multiflex_view(request,userid=0,
@user_passes_test(ispromember,login_url="/rowers/paidplans", @user_passes_test(ispromember,login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None) redirect_field_name=None)
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def user_boxplot_select(request, def user_boxplot_select(request,
startdatestring="", startdatestring="",
enddatestring="", enddatestring="",
@@ -3740,6 +3721,7 @@ def user_boxplot_select(request,
@user_passes_test(ispromember,login_url="/rowers/paidplans", @user_passes_test(ispromember,login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None) redirect_field_name=None)
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def boxplot_view_data(request,userid=0, def boxplot_view_data(request,userid=0,
options={ options={
'includereststrokes':False, 'includereststrokes':False,
@@ -3857,6 +3839,7 @@ def boxplot_view_data(request,userid=0,
@user_passes_test(ispromember,login_url="/rowers/paidplans", @user_passes_test(ispromember,login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None) redirect_field_name=None)
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def boxplot_view(request,userid=0, def boxplot_view(request,userid=0,
options={ options={
'includereststrokes':False, 'includereststrokes':False,
@@ -3975,7 +3958,8 @@ def boxplot_view(request,userid=0,
# Cumulative stats page # Cumulative stats page
@user_passes_test(ispromember,login_url="/rowers/paidplans",message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",redirect_field_name=None) @user_passes_test(ispromember,login_url="/rowers/paidplans",message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",redirect_field_name=None)
def cumstats(request,theuser=0, @permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def cumstats(request,userid=0,
startdate=timezone.now()-datetime.timedelta(days=30), startdate=timezone.now()-datetime.timedelta(days=30),
enddate=timezone.now(), enddate=timezone.now(),
deltadays=-1, deltadays=-1,
@@ -3988,7 +3972,7 @@ def cumstats(request,theuser=0,
'rankingonly':False, 'rankingonly':False,
}): }):
r = getrequestrower(request,userid=theuser) r = getrequestrower(request,userid=userid)
theuser = r.user theuser = r.user
if 'waterboattype' in request.session: if 'waterboattype' in request.session:
@@ -4106,7 +4090,7 @@ def cumstats(request,theuser=0,
options = { options = {
'modality': modality, 'modality': modality,
'theuser': theuser.id, 'userid': theuser.id,
'waterboattype':waterboattype, 'waterboattype':waterboattype,
'startdatestring':startdatestring, 'startdatestring':startdatestring,
'enddatestring':enddatestring, 'enddatestring':enddatestring,
@@ -4326,6 +4310,7 @@ def agegrouprecordview(request,sex='male',weightcategory='hwt',
# message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", # message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
# redirect_field_name=None) # redirect_field_name=None)
@login_required @login_required
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def alerts_view(request,userid=0): def alerts_view(request,userid=0):
r = getrequestrower(request,userid=userid) r = getrequestrower(request,userid=userid)
@@ -4360,6 +4345,7 @@ def alerts_view(request,userid=0):
@user_passes_test(ispromember, login_url="/rowers/paidplans", @user_passes_test(ispromember, login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None) redirect_field_name=None)
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def alert_create_view(request,userid=0): def alert_create_view(request,userid=0):
r = getrequestrower(request,userid=userid) r = getrequestrower(request,userid=userid)
FilterFormSet = formset_factory(ConditionEditForm, formset=BaseConditionFormSet,extra=1) FilterFormSet = formset_factory(ConditionEditForm, formset=BaseConditionFormSet,extra=1)
@@ -4438,6 +4424,7 @@ def alert_create_view(request,userid=0):
# alert report view # alert report view
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def alert_report_view(request,id=0,userid=0,nperiod=0): def alert_report_view(request,id=0,userid=0,nperiod=0):
r = getrequestrower(request,userid=userid) r = getrequestrower(request,userid=userid)
if userid == 0: if userid == 0:
@@ -4495,6 +4482,7 @@ def alert_report_view(request,id=0,userid=0,nperiod=0):
@user_passes_test(ispromember, login_url="/rowers/paidplans", @user_passes_test(ispromember, login_url="/rowers/paidplans",
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality", message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
redirect_field_name=None) redirect_field_name=None)
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def alert_edit_view(request,id=0,userid=0): def alert_edit_view(request,id=0,userid=0):
r = getrequestrower(request,userid=userid) r = getrequestrower(request,userid=userid)
+12 -11
View File
@@ -6,13 +6,12 @@ from rowers.views.statements import *
# Export workout to TCX and send to user's email address # Export workout to TCX and send to user's email address
@login_required() @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_tcxemail_view(request,id=0): def workout_tcxemail_view(request,id=0):
r = getrower(request.user) r = getrower(request.user)
w = get_workout(id) w = get_workout(id)
if not checkworkoutuser(request.user,w):
raise PermissionDenied("Access denied")
row = rdata(w.csvfilename) row = rdata(w.csvfilename)
@@ -169,13 +168,12 @@ def course_kmldownload_view(request,id=0):
# Export workout to GPX and send to user's email address # Export workout to GPX and send to user's email address
@login_required() @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_gpxemail_view(request,id=0): def workout_gpxemail_view(request,id=0):
r = getrower(request.user) r = getrower(request.user)
w = get_workout(id) w = get_workout(id)
if not checkworkoutuser(request.user,w):
raise PermissionDenied("Access denied")
row = rdata(w.csvfilename) row = rdata(w.csvfilename)
@@ -236,14 +234,13 @@ def workouts_summaries_email_view(request):
# Get Workout CSV file and send it to user's email address # Get Workout CSV file and send it to user's email address
@login_required() @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_csvemail_view(request,id=0): def workout_csvemail_view(request,id=0):
r = getrower(request.user) r = getrower(request.user)
w = get_workout(id) w = get_workout(id)
if not checkworkoutuser(request.user,w):
raise PermissionDenied("Access denied")
rowdata = rdata(w.csvfilename) rowdata = rdata(w.csvfilename)
code = str(uuid4()) code = str(uuid4())
@@ -252,8 +249,11 @@ def workout_csvemail_view(request,id=0):
rowdate = rowdata.rowdatetime rowdate = rowdata.rowdatetime
starttimeunix = arrow.get(rowdate).timestamp starttimeunix = arrow.get(rowdate).timestamp
df = rowdata.df df = rowdata.df
df[' ElapsedTime (sec)'] = df['TimeStamp (sec)'] try:
df['TimeStamp (sec)'] = df['TimeStamp (sec)'] + starttimeunix df[' ElapsedTime (sec)'] = df['TimeStamp (sec)']
df['TimeStamp (sec)'] = df['TimeStamp (sec)'] + starttimeunix
except KeyError:
pass
response = HttpResponse(df.to_csv()) response = HttpResponse(df.to_csv())
response['Content-Disposition'] = 'attachment; filename="%s"' % filename response['Content-Disposition'] = 'attachment; filename="%s"' % filename
@@ -264,6 +264,7 @@ def workout_csvemail_view(request,id=0):
# Get Workout CSV file and send it to user's email address # Get Workout CSV file and send it to user's email address
@login_required() @login_required()
@permission_required('rower.is_staff',fn=get_user_by_userid,raise_exception=True)
def workout_csvtoadmin_view(request,id=0): def workout_csvtoadmin_view(request,id=0):
message = "" message = ""
r = getrower(request.user) r = getrower(request.user)
+214 -223
View File
@@ -13,7 +13,7 @@ def default(o):
# Send workout to TP # Send workout to TP
@login_required() @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_tp_upload_view(request,id=0): def workout_tp_upload_view(request,id=0):
message = "" message = ""
@@ -25,39 +25,35 @@ def workout_tp_upload_view(request,id=0):
return HttpResponseRedirect("/rowers/me/tpauthorize/") return HttpResponseRedirect("/rowers/me/tpauthorize/")
# ready to upload. Hurray # ready to upload. Hurray
w = get_workout_permitted(request.user,id) w = get_workout_by_opaqueid(request,id)
r = w.user r = w.user
if (checkworkoutuser(request.user,w)):
tcxfile = tpstuff.createtpworkoutdata(w)
if tcxfile:
res,reason,status_code,headers = tpstuff.uploadactivity(
r.tptoken,tcxfile,
name=w.name
)
if res == 0:
message = "Upload to TrainingPeaks failed with status code "+str(status_code)+": "+reason
try:
os.remove(tcxfile)
except WindowsError:
pass
messages.error(request,message) tcxfile = tpstuff.createtpworkoutdata(w)
if tcxfile:
else: # res != 0 res,reason,status_code,headers = tpstuff.uploadactivity(
w.uploadedtotp = res r.tptoken,tcxfile,
w.save() name=w.name
)
if res == 0:
message = "Upload to TrainingPeaks failed with status code "+str(status_code)+": "+reason
try:
os.remove(tcxfile) os.remove(tcxfile)
messages.info(request,'Uploaded to TrainingPeaks') except WindowsError:
pass
else: # no tcxfile
message = "Upload to TrainingPeaks failed"
w.uploadedtotp = -1
w.save()
messages.error(request,message) messages.error(request,message)
else: # not allowed to upload else: # res != 0
message = "You are not allowed to export this workout to TP" w.uploadedtotp = res
w.save()
os.remove(tcxfile)
messages.info(request,'Uploaded to TrainingPeaks')
else: # no tcxfile
message = "Upload to TrainingPeaks failed"
w.uploadedtotp = -1
w.save()
messages.error(request,message) messages.error(request,message)
url = reverse(r.defaultlandingpage, url = reverse(r.defaultlandingpage,
@@ -70,7 +66,7 @@ def workout_tp_upload_view(request,id=0):
# Send workout to Strava # Send workout to Strava
# abundance of error logging here because there were/are some bugs # abundance of error logging here because there were/are some bugs
@login_required() @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_strava_upload_view(request,id=0): def workout_strava_upload_view(request,id=0):
message = "" message = ""
r = getrower(request.user) r = getrower(request.user)
@@ -88,93 +84,93 @@ def workout_strava_upload_view(request,id=0):
# ready to upload. Hurray # ready to upload. Hurray
w = get_workout_permitted(request.user,id) w = get_workout_permitted(request.user,id)
r = w.user r = w.user
if (checkworkoutuser(request.user,w)):
try: try:
tcxfile,tcxmessg = stravastuff.createstravaworkoutdata(w) tcxfile,tcxmessg = stravastuff.createstravaworkoutdata(w)
if tcxfile: if tcxfile:
with open(tcxfile,'rb') as f: with open(tcxfile,'rb') as f:
try:
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
except TypeError:
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
if w.workouttype in mytypes.rowtypes:
activity_type = r.stravaexportas
else:
try: try:
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com' activity_type = mytypes.stravamapping[w.workouttype]
except TypeError: except KeyError:
newnotes = 'from '+w.workoutsource+' via rowsandall.com' activity_type = 'Ride'
if w.workouttype in mytypes.rowtypes:
activity_type = r.stravaexportas
else:
try:
activity_type = mytypes.stravamapping[w.workouttype]
except KeyError:
activity_type = 'Ride'
res,mes = stravastuff.handle_stravaexport(
f,w.name,
r.stravatoken,
description=newnotes,
activity_type=activity_type)
if res==0:
messages.error(request,mes)
w.uploadedtostrava = -1
w.save()
try:
os.remove(tcxfile)
except WindowsError:
pass
url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
})
response = HttpResponseRedirect(url)
return response
res,mes = stravastuff.handle_stravaexport(
f,w.name,
r.stravatoken,
description=newnotes,
activity_type=activity_type)
if res==0:
messages.error(request,mes)
w.uploadedtostrava = -1
w.save()
try: try:
w.uploadedtostrava = res os.remove(tcxfile)
w.save() except WindowsError:
try: pass
os.remove(tcxfile) url = reverse(r.defaultlandingpage,
except WindowsError: kwargs = {
pass 'id':encoder.encode_hex(w.id),
url = reverse('workout_edit_view',kwargs={'id':w.id}) })
response = HttpResponseRedirect(url)
return response
try:
w.uploadedtostrava = res
w.save()
try:
os.remove(tcxfile)
except WindowsError:
pass
url = reverse('workout_edit_view',kwargs={'id':w.id})
messages.info(request,mes) messages.info(request,mes)
except: except:
with open("media/stravaerrors.log","a") as errorlog: with open("media/stravaerrors.log","a") as errorlog:
errorstring = str(sys.exc_info()[0]) errorstring = str(sys.exc_info()[0])
timestr = strftime("%Y%m%d-%H%M%S") timestr = strftime("%Y%m%d-%H%M%S")
errorlog.write(timestr+errorstring+"\r\n") errorlog.write(timestr+errorstring+"\r\n")
errorlog.write("views.py line 826\r\n") errorlog.write("views.py line 826\r\n")
message = 'Error: '+errorstring message = 'Error: '+errorstring
messages.error(request,message) messages.error(request,message)
else: # No tcxfile else: # No tcxfile
message = "Strava Data error "+tcxmessg message = "Strava Data error "+tcxmessg
messages.error(request,message)
w.uploadedtostrava = -1
w.save()
url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
})
response = HttpResponseRedirect(url)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
}
)
response = HttpResponseRedirect(url)
except ActivityUploadFailed as e:
message = "Strava Upload error: %s" % e
messages.error(request,message) messages.error(request,message)
w.uploadedtostrava = -1 w.uploadedtostrava = -1
w.save() w.save()
os.remove(tcxfile)
url = reverse(r.defaultlandingpage, url = reverse(r.defaultlandingpage,
kwargs = { kwargs = {
'id':encoder.encode_hex(w.id), 'id':encoder.encode_hex(w.id),
}) })
response = HttpResponseRedirect(url) response = HttpResponseRedirect(url)
return response
url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
}
)
response = HttpResponseRedirect(url)
except ActivityUploadFailed as e:
message = "Strava Upload error: %s" % e
messages.error(request,message)
w.uploadedtostrava = -1
w.save()
os.remove(tcxfile)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
})
response = HttpResponseRedirect(url)
return response
# Upload workout to Concept2 logbook # Upload workout to Concept2 logbook
@login_required() @login_required()
@@ -206,7 +202,7 @@ def workout_c2_upload_view(request,id=0):
return response return response
# Upload workout to RunKeeper # Upload workout to RunKeeper
@login_required() @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_runkeeper_upload_view(request,id=0): def workout_runkeeper_upload_view(request,id=0):
message = "" message = ""
w = get_workout(id) w = get_workout(id)
@@ -219,48 +215,45 @@ def workout_runkeeper_upload_view(request,id=0):
# ready to upload. Hurray # ready to upload. Hurray
if (checkworkoutuser(request.user,w)):
data = runkeeperstuff.createrunkeeperworkoutdata(w)
if not data:
message = "Data error"
messages.error(request,message)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':id,
})
return HttpResponseRedirect(url)
authorizationstring = str('Bearer ' + thetoken) data = runkeeperstuff.createrunkeeperworkoutdata(w)
headers = {'Authorization': authorizationstring, if not data:
'user-agent': 'sanderroosendaal', message = "Data error"
'Content-Type': 'application/vnd.com.runkeeper.NewFitnessActivity+json',
'Content-Length':'nnn'}
url = "https://api.runkeeper.com/fitnessActivities"
response = requests.post(url,headers=headers,data=json.dumps(data,default=default))
# check for duplicate error first
if (response.status_code == 409 ):
message = "Duplicate error"
messages.error(request,message)
w.uploadedtorunkeeper = -1
w.save()
elif (response.status_code == 201 or response.status_code==200):
runkeeperid = runkeeperstuff.getidfromresponse(response)
w.uploadedtorunkeeper = runkeeperid
w.save()
url = reverse('workout_edit_view',
kwargs={'id':encoder.encode_hex(w.id)})
return HttpResponseRedirect(url)
else:
s = response
message = "Something went wrong in workout_runkeeper_upload_view: %s - %s" % (s.reason,s.text)
messages.error(request,message)
else:
message = "You are not authorized to upload this workout"
messages.error(request,message) messages.error(request,message)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':id,
})
return HttpResponseRedirect(url)
authorizationstring = str('Bearer ' + thetoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/vnd.com.runkeeper.NewFitnessActivity+json',
'Content-Length':'nnn'}
url = "https://api.runkeeper.com/fitnessActivities"
response = requests.post(url,headers=headers,data=json.dumps(data,default=default))
# check for duplicate error first
if (response.status_code == 409 ):
message = "Duplicate error"
messages.error(request,message)
w.uploadedtorunkeeper = -1
w.save()
elif (response.status_code == 201 or response.status_code==200):
runkeeperid = runkeeperstuff.getidfromresponse(response)
w.uploadedtorunkeeper = runkeeperid
w.save()
url = reverse('workout_edit_view',
kwargs={'id':encoder.encode_hex(w.id)})
return HttpResponseRedirect(url)
else:
s = response
message = "Something went wrong in workout_runkeeper_upload_view: %s - %s" % (s.reason,s.text)
messages.error(request,message)
url = reverse(r.defaultlandingpage, url = reverse(r.defaultlandingpage,
kwargs = { kwargs = {
@@ -270,7 +263,7 @@ def workout_runkeeper_upload_view(request,id=0):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
# Upload workout to Underarmour # Upload workout to Underarmour
@login_required() @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def workout_underarmour_upload_view(request,id=0): def workout_underarmour_upload_view(request,id=0):
message = "" message = ""
w = get_workout(id) w = get_workout(id)
@@ -283,47 +276,44 @@ def workout_underarmour_upload_view(request,id=0):
# ready to upload. Hurray # ready to upload. Hurray
if (checkworkoutuser(request.user,w)):
data = underarmourstuff.createunderarmourworkoutdata(w)
if not data:
message = "Data error"
messages.error(request,message)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
})
return HttpResponseRedirect(url)
authorizationstring = str('Bearer ' + thetoken) data = underarmourstuff.createunderarmourworkoutdata(w)
headers = {'Authorization': authorizationstring, if not data:
'Api-Key': UNDERARMOUR_CLIENT_KEY, message = "Data error"
'user-agent': 'sanderroosendaal', messages.error(request,message)
'Content-Type': 'application/json', url = reverse(r.defaultlandingpage,
} kwargs = {
'id':encoder.encode_hex(w.id),
})
return HttpResponseRedirect(url)
url = "https://api.ua.com/v7.1/workout/" authorizationstring = str('Bearer ' + thetoken)
response = requests.post(url,headers=headers,data=json.dumps(data,default=default)) headers = {'Authorization': authorizationstring,
'Api-Key': UNDERARMOUR_CLIENT_KEY,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json',
}
url = "https://api.ua.com/v7.1/workout/"
response = requests.post(url,headers=headers,data=json.dumps(data,default=default))
# check for duplicate error first # check for duplicate error first
if (response.status_code == 409 ): if (response.status_code == 409 ):
message = "Duplicate error" message = "Duplicate error"
messages.error(request,message) messages.error(request,message)
w.uploadedtounderarmour = -1 w.uploadedtounderarmour = -1
w.save() w.save()
elif (response.status_code == 201 or response.status_code==200): elif (response.status_code == 201 or response.status_code==200):
underarmourid = underarmourstuff.getidfromresponse(response) underarmourid = underarmourstuff.getidfromresponse(response)
w.uploadedtounderarmour = underarmourid w.uploadedtounderarmour = underarmourid
w.save() w.save()
url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)}) url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)})
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
else:
s = response
message = "Something went wrong in workout_underarmour_upload_view: %s " % s.reason
messages.error(request,message)
else: else:
message = "You are not authorized to upload this workout" s = response
message = "Something went wrong in workout_underarmour_upload_view: %s " % s.reason
messages.error(request,message) messages.error(request,message)
url = reverse(r.defaultlandingpage, url = reverse(r.defaultlandingpage,
@@ -334,7 +324,7 @@ def workout_underarmour_upload_view(request,id=0):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
# Upload workout to SportTracks # Upload workout to SportTracks
@login_required() @permission_required('workout.change_workout',fn=get_workout_by_opaqueid)
def workout_sporttracks_upload_view(request,id=0): def workout_sporttracks_upload_view(request,id=0):
message = "" message = ""
# ready to upload. Hurray # ready to upload. Hurray
@@ -347,49 +337,46 @@ def workout_sporttracks_upload_view(request,id=0):
return HttpResponseRedirect("/rowers/me/sporttracksauthorize/") return HttpResponseRedirect("/rowers/me/sporttracksauthorize/")
if (checkworkoutuser(request.user,w)):
data = sporttracksstuff.createsporttracksworkoutdata(w)
if not data: data = sporttracksstuff.createsporttracksworkoutdata(w)
message = "Data error"
messages.error(request,message)
url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
})
return HttpResponseRedirect(url)
authorizationstring = str('Bearer ' + thetoken) if not data:
headers = {'Authorization': authorizationstring, message = "Data error"
'user-agent': 'sanderroosendaal', messages.error(request,message)
'Content-Type': 'application/json'} url = reverse(r.defaultlandingpage,
kwargs = {
'id':encoder.encode_hex(w.id),
})
return HttpResponseRedirect(url)
url = "https://api.sporttracks.mobi/api/v2/fitnessActivities.json" authorizationstring = str('Bearer ' + thetoken)
response = requests.post(url,headers=headers,data=json.dumps(data,default=default)) headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://api.sporttracks.mobi/api/v2/fitnessActivities.json"
response = requests.post(url,headers=headers,data=json.dumps(data,default=default))
# check for duplicate error first # check for duplicate error first
if (response.status_code == 409 ): if (response.status_code == 409 ):
message = "Duplicate error" message = "Duplicate error"
messages.error(request,message) messages.error(request,message)
w.uploadedtosporttracks = -1 w.uploadedtosporttracks = -1
w.save() w.save()
elif (response.status_code == 201 or response.status_code==200): elif (response.status_code == 201 or response.status_code==200):
s= response.json() s= response.json()
sporttracksid = sporttracksstuff.getidfromresponse(response) sporttracksid = sporttracksstuff.getidfromresponse(response)
w.uploadedtosporttracks = sporttracksid w.uploadedtosporttracks = sporttracksid
w.save() w.save()
message = "Upload to SportTracks was successful" message = "Upload to SportTracks was successful"
messages.info(request,message) messages.info(request,message)
url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)}) url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)})
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
else:
s = response
message = "Something went wrong in workout_sporttracks_upload_view: %s" % s.reason
messages.error(request,message)
else: else:
message = "You are not authorized to upload this workout" s = response
message = "Something went wrong in workout_sporttracks_upload_view: %s" % s.reason
messages.error(request,message) messages.error(request,message)
url = reverse(r.defaultlandingpage, url = reverse(r.defaultlandingpage,
@@ -899,10 +886,11 @@ def rower_process_testcallback(request):
# The page where you select which Strava workout to import # The page where you select which Strava workout to import
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def workout_stravaimport_view(request,message="",userid=0): def workout_stravaimport_view(request,message="",userid=0):
r = getrequestrower(request,userid=userid) r = getrequestrower(request,userid=userid)
if r.user != request.user: #if r.user != request.user:
messages.info(request,"You cannot import other people's workouts from Strava") # messages.info(request,"You cannot import other people's workouts from Strava")
try: try:
thetoken = strava_open(request.user) thetoken = strava_open(request.user)
@@ -998,6 +986,7 @@ def workout_stravaimport_view(request,message="",userid=0):
# The page where you select which RunKeeper workout to import # The page where you select which RunKeeper workout to import
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def workout_runkeeperimport_view(request,message="",userid=0): def workout_runkeeperimport_view(request,message="",userid=0):
res = runkeeperstuff.get_runkeeper_workout_list(request.user) res = runkeeperstuff.get_runkeeper_workout_list(request.user)
if (res.status_code != 200): if (res.status_code != 200):
@@ -1053,6 +1042,7 @@ def workout_runkeeperimport_view(request,message="",userid=0):
# The page where you select which RunKeeper workout to import # The page where you select which RunKeeper workout to import
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def workout_underarmourimport_view(request,message="",userid=0): def workout_underarmourimport_view(request,message="",userid=0):
res = underarmourstuff.get_underarmour_workout_list(request.user) res = underarmourstuff.get_underarmour_workout_list(request.user)
if (res.status_code != 200): if (res.status_code != 200):
@@ -1103,6 +1093,7 @@ def workout_underarmourimport_view(request,message="",userid=0):
# the page where you select which Polar workout to Import # the page where you select which Polar workout to Import
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def workout_polarimport_view(request,userid=0): def workout_polarimport_view(request,userid=0):
exercises = polarstuff.get_polar_workouts(request.user) exercises = polarstuff.get_polar_workouts(request.user)
workouts = [] workouts = []
@@ -1160,6 +1151,7 @@ def workout_polarimport_view(request,userid=0):
# The page where you select which SportTracks workout to import # The page where you select which SportTracks workout to import
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def workout_sporttracksimport_view(request,message="",userid=0): def workout_sporttracksimport_view(request,message="",userid=0):
@@ -1304,14 +1296,15 @@ def workout_getc2workout_all(request,page=1,message=""):
# List of workouts available on Concept2 logbook - for import # List of workouts available on Concept2 logbook - for import
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def workout_c2import_view(request,page=1,userid=0,message=""): def workout_c2import_view(request,page=1,userid=0,message=""):
r = getrequestrower(request,userid=userid) r = getrequestrower(request,userid=userid)
if r.user != request.user: # if r.user != request.user:
messages.info(request,"You cannot import other people's workouts from Concept2") # messages.info(request,"You cannot import other people's workouts from Concept2")
r = getrower(request.user) # r = getrower(request.user)
try: try:
thetoken = c2_open(request.user) thetoken = c2_open(request.user)
@@ -1613,5 +1606,3 @@ def workout_getstravaworkout_next(request):
url = reverse('workouts_view') url = reverse('workouts_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
+2 -120
View File
@@ -74,123 +74,10 @@ def errormessage_view(request,errormessage='aap'):
"result":1, "result":1,
}) })
@login_required()
def deactivate_user(request):
pk = request.user.id
user = User.objects.get(pk=pk)
user_form = DeactivateUserForm(instance=user)
if request.user.is_authenticated and request.user.id == user.id:
if request.method == "POST":
user_form = DeactivateUserForm(request.POST, instance=user)
if user_form.is_valid():
if not user_form.cleaned_data['is_active']:
r = Rower.objects.get(user=user)
if r.paidplan is not None and r.paidplan.paymentprocessor == 'braintree':
try:
subscriptions = braintreestuff.find_subscriptions(r)
for subscription in subscriptions:
success, themessages,errormessages = braintreestuff.cancel_subscription(r,id)
for message in themessages:
messages.info(request,message)
except ProcessorCustomerError:
pass
r.paidplan = None
r.teamplanexpires = timezone.now()
r.planexpires = timezone.now()
r.clubsize = 0
r.rowerplan = 'basic'
r.save()
deactivate_user = user_form.save(commit=False)
user.is_active = False
user.save()
deactivate_user.save()
# url = reverse(auth_views.logout_then_login)
url = '/logout/?next=/login'
return HttpResponseRedirect(url)
return render(request, "userprofile_deactivate.html", {
"user_form": user_form,
})
else:
raise PermissionDenied
@login_required()
def user_gdpr_optin(request):
r = getrower(request.user)
r.gdproptin = False
r.gdproptindate = None
r.save()
nexturl = request.GET.get('next','/rowers/list-workouts/')
if r.gdproptin:
return HttpResponseRedirect(nexturl)
return render(request,'gdpr_optin.html',{
"next": nexturl
})
@login_required()
def user_gdpr_confirm(request):
r = getrower(request.user)
r.gdproptin = True
r.gdproptindate = timezone.now()
r.save()
nexturl = request.GET.get('next','/rowers/list-workouts/')
return HttpResponseRedirect(nexturl)
@login_required()
def remove_user(request):
pk = request.user.id
user = User.objects.get(pk=pk)
user_form = DeleteUserForm(instance=user)
if request.user.is_authenticated and request.user.id == user.id:
if request.method == "POST":
user_form = DeleteUserForm(request.POST,instance=user)
if user_form.is_valid():
cd = user_form.cleaned_data
name = user.first_name+' '+user.last_name
email = user.email
r = Rower.objects.get(user=user)
if r.paidplan is not None and r.paidplan.paymentprocessor == 'braintree':
try:
subscriptions = braintreestuff.find_subscriptions(r)
for subscription in subscriptions:
success, themessages,errormessages = braintreestuff.cancel_subscription(r,id)
for message in themessages:
messages.info(request,message)
except:
pass
if cd['delete_user']:
# delete workouts
Workout.objects.filter(user=user.rower).delete()
# delete planned sessions
PlannedSession.objects.filter(manager=user).delete()
user.delete()
res = myqueue(queuehigh,
handle_sendemail_userdeleted,
name, email)
url = '/logout/?next=/login'
# url = reverse(auth_views.logout_then_login)
return HttpResponseRedirect(url)
return render(request, "userprofile_delete.html", {
"user_form": user_form,
})
else:
raise PermissionDenied
# Shows analysis page # Shows analysis page
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def analysis_view(request,userid=0): def analysis_view(request,userid=0):
r = getrequestrower(request,userid=userid) r = getrequestrower(request,userid=userid)
return render(request, return render(request,
@@ -203,6 +90,7 @@ def analysis_view(request,userid=0):
# Shows laboratory page # Shows laboratory page
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def laboratory_view(request,userid=0): def laboratory_view(request,userid=0):
r = getrequestrower(request,userid=userid) r = getrequestrower(request,userid=userid)
return render(request, return render(request,
@@ -212,9 +100,3 @@ def laboratory_view(request,userid=0):
'rower':r, 'rower':r,
} }
) )
+1 -1
View File
@@ -647,6 +647,7 @@ def freecoach_register_view(request):
'next':nextpage,}) 'next':nextpage,})
@login_required() @login_required()
@permission_required('rower.is_staff',fn=get_user_by_userid,raise_exception=True)
def transactions_view(request): def transactions_view(request):
if not request.user.is_staff: if not request.user.is_staff:
raise PermissionDenied("Not Allowed") raise PermissionDenied("Not Allowed")
@@ -673,4 +674,3 @@ def transactions_view(request):
{ {
'dateform':dateform 'dateform':dateform
}) })
+205 -209
View File
@@ -5,27 +5,11 @@ from __future__ import unicode_literals
from rowers.views.statements import * from rowers.views.statements import *
@login_required() @login_required
@permission_required('plannedsession.view_session',fn=get_session_by_pk,raise_exception=True)
def plannedsession_comment_view(request,id=0,userid=0): def plannedsession_comment_view(request,id=0,userid=0):
r = getrequestplanrower(request,userid=userid) r = getrequestplanrower(request,userid=userid)
ps = get_object_or_404(PlannedSession,pk=id)
try:
ps = PlannedSession.objects.get(id=id)
except PlannedSession.DoesNotExist:
raise Http404("Planned Session does not exist")
m = ps.manager
mm = Rower.objects.get(user=m)
if ps.manager != request.user and ps.sessiontype not in ['race','indoorrace']:
if r.rowerplan == 'coach' and r not in ps.rower.all():
teams = Team.objects.filter(manager=request.user)
members = Rower.objects.filter(team__in=teams).distinct()
teamusers = [m.user for m in members]
if ps.manager not in teamusers:
raise PermissionDenied("You do not have access to this session")
elif r not in ps.rower.all():
raise PermissionDenied("You do not have access to this session")
comments = PlannedSessionComment.objects.filter(plannedsession=ps).order_by("created") comments = PlannedSessionComment.objects.filter(plannedsession=ps).order_by("created")
@@ -171,7 +155,7 @@ def plannedsession_comment_view(request,id=0,userid=0):
}) })
# Cloning sessions # Cloning sessions
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/", @user_passes_test(can_plan,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan", message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) redirect_field_name=None)
def plannedsession_multiclone_view( def plannedsession_multiclone_view(
@@ -322,7 +306,7 @@ def plannedsession_multiclone_view(
) )
# Individual user creates training for himself # Individual user creates training for himself
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/", @user_passes_test(can_plan,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan", message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) redirect_field_name=None)
def plannedsession_create_view(request, def plannedsession_create_view(request,
@@ -332,9 +316,6 @@ def plannedsession_create_view(request,
r = getrequestplanrower(request,userid=userid) r = getrequestplanrower(request,userid=userid)
startdate,enddate = get_dates_timeperiod(request,startdatestring=startdatestring, startdate,enddate = get_dates_timeperiod(request,startdatestring=startdatestring,
enddatestring=enddatestring) enddatestring=enddatestring)
@@ -477,7 +458,7 @@ def plannedsession_create_view(request,
'timeperiod':timeperiod, 'timeperiod':timeperiod,
}) })
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/", @user_passes_test(can_plan,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan", message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) redirect_field_name=None)
def plannedsession_multicreate_view(request, def plannedsession_multicreate_view(request,
@@ -613,7 +594,7 @@ def plannedsession_multicreate_view(request,
return render(request,'plannedsession_multicreate.html',context) return render(request,'plannedsession_multicreate.html',context)
# Manager creates sessions for entire team # Manager creates sessions for entire team
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/", @user_passes_test(can_plan,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan", message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) redirect_field_name=None)
def plannedsession_teamcreate_view(request, def plannedsession_teamcreate_view(request,
@@ -704,7 +685,9 @@ def plannedsession_teamcreate_view(request,
enddatestring = enddate.strftime('%Y-%m-%d') enddatestring = enddate.strftime('%Y-%m-%d')
url += '?when='+startdatestring+'/'+enddatestring url += '?when='+startdatestring+'/'+enddatestring
return HttpResponseRedirect(url) next = request.GET.get('next', url)
return HttpResponseRedirect(next)
else: else:
timeperiod = startdate.strftime('%Y-%m-%d')+'/'+enddate.strftime('%Y-%m-%d') timeperiod = startdate.strftime('%Y-%m-%d')+'/'+enddate.strftime('%Y-%m-%d')
breadcrumbs = [ breadcrumbs = [
@@ -786,21 +769,16 @@ def plannedsession_teamcreate_view(request,
}) })
# Manager edits sessions for entire team # Manager edits sessions for entire team
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/", @user_passes_test(can_plan,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan", message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) redirect_field_name=None)
@permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True)
def plannedsession_teamedit_view(request, def plannedsession_teamedit_view(request,
sessionid=0,userid=0): id=0,userid=0):
r = getrequestplanrower(request,userid=userid) r = getrequestplanrower(request,userid=userid)
ps = get_object_or_404(PlannedSession,pk=id)
try:
ps = PlannedSession.objects.get(id=sessionid)
except PlannedSession.DoesNotExist:
raise Http404("This session doesn't exist")
if not ps.manager == request.user:
raise PermissionDenied("You are not the manager of this session")
teams = Team.objects.filter(manager=request.user) teams = Team.objects.filter(manager=request.user)
teamchoices = [(team.id, team.name) for team in teams] teamchoices = [(team.id, team.name) for team in teams]
@@ -890,15 +868,15 @@ def plannedsession_teamedit_view(request,
url = reverse(plannedsession_teamedit_view, url = reverse(plannedsession_teamedit_view,
kwargs = { kwargs = {
'sessionid':sessionid, 'id':id,
}) })
startdatestring = startdate.strftime('%Y-%m-%d') startdatestring = startdate.strftime('%Y-%m-%d')
enddatestring = enddate.strftime('%Y-%m-%d') enddatestring = enddate.strftime('%Y-%m-%d')
url += '?when='+startdatestring+'/'+enddatestring url += '?when='+startdatestring+'/'+enddatestring
next = request.GET.get('next', url)
return HttpResponseRedirect(url) return HttpResponseRedirect(next)
else: else:
sessioncreateform = PlannedSessionForm(instance=ps) sessioncreateform = PlannedSessionForm(instance=ps)
sessionteamselectform = PlannedSessionTeamForm( sessionteamselectform = PlannedSessionTeamForm(
@@ -1238,6 +1216,7 @@ def plannedsessions_print_view(request,userid=0):
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def plannedsessions_manage_view(request,userid=0, def plannedsessions_manage_view(request,userid=0,
initialsession=0): initialsession=0):
@@ -1245,8 +1224,6 @@ def plannedsessions_manage_view(request,userid=0,
if request.is_ajax(): if request.is_ajax():
is_ajax = True is_ajax = True
r = getrequestrower(request,userid=userid) r = getrequestrower(request,userid=userid)
startdate,enddate = get_dates_timeperiod(request) startdate,enddate = get_dates_timeperiod(request)
@@ -1402,7 +1379,8 @@ def plannedsessions_manage_view(request,userid=0,
# Clone an existing planned session # Clone an existing planned session
# need clarity on cloning behavior time shift # need clarity on cloning behavior time shift
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/", @permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True)
@user_passes_test(can_plan,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan", message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) redirect_field_name=None)
def plannedsession_clone_view(request,id=0,userid=0): def plannedsession_clone_view(request,id=0,userid=0):
@@ -1420,13 +1398,7 @@ def plannedsession_clone_view(request,id=0,userid=0):
except IndexError: except IndexError:
trainingplan = None trainingplan = None
try: ps = get_object_or_404(PlannedSession,pk=id)
ps = PlannedSession.objects.get(id=id)
except PlannedSession.DoesNotExist:
raise Http404("Planned Session does not exist")
if ps.manager != request.user:
raise PermissionDenied("You are not allowed to clone this planned session")
rowers = ps.rower.all() rowers = ps.rower.all()
teams = ps.team.all() teams = ps.team.all()
@@ -1465,12 +1437,13 @@ def plannedsession_clone_view(request,id=0,userid=0):
enddatestring = enddate.strftime('%Y-%m-%d') enddatestring = enddate.strftime('%Y-%m-%d')
url += '?when='+startdatestring+'/'+enddatestring url += '?when='+startdatestring+'/'+enddatestring
next = request.GET.get('next', url)
return HttpResponseRedirect(url) return HttpResponseRedirect(next)
# Clone an existing planned session # Clone an existing planned session
# need clarity on cloning behavior time shift # need clarity on cloning behavior time shift
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/", @permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True)
@user_passes_test(can_plan,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan", message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) redirect_field_name=None)
def plannedsession_teamclone_view(request,id=0): def plannedsession_teamclone_view(request,id=0):
@@ -1497,13 +1470,7 @@ def plannedsession_teamclone_view(request,id=0):
except IndexError: except IndexError:
trainingplan = None trainingplan = None
try: ps = get_object_or_404(PlannedSession,pk=id)
ps = PlannedSession.objects.get(id=id)
except PlannedSession.DoesNotExist:
raise Http404("Planned Session does not exist")
if ps.manager != request.user:
raise PermissionDenied("You are not allowed to clone this planned session")
ps.pk = None ps.pk = None
@@ -1522,7 +1489,7 @@ def plannedsession_teamclone_view(request,id=0):
url = reverse(plannedsession_teamedit_view, url = reverse(plannedsession_teamedit_view,
kwargs = { kwargs = {
'sessionid':ps.id, 'id':ps.id,
'userid':r.user.id, 'userid':r.user.id,
} }
) )
@@ -1531,54 +1498,16 @@ def plannedsession_teamclone_view(request,id=0):
enddatestring = enddate.strftime('%Y-%m-%d') enddatestring = enddate.strftime('%Y-%m-%d')
url += '?when='+startdatestring+'/'+enddatestring url += '?when='+startdatestring+'/'+enddatestring
next = request.GET.get('next', url)
return HttpResponseRedirect(next)
return HttpResponseRedirect(url) @permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True)
@user_passes_test(can_plan, login_url="/rowers/paidplans/",
@user_passes_test(hasplannedsessions, login_url="/rowers/paidplans/", message="This functionality requires a Coach or Self-Coach plan",
message="This functionality requires a Coach or Self-Coach plan", redirect_field_name=None)
redirect_field_name=None) def plannedsession_templateedit_view(request,id=0):
def plannedsession_totemplate_view(request,id=0): r = getrequestrower(request)
r = getrequestplanrower(request)
startdate, enddate = get_dates_timeperiod(request)
try:
ps = PlannedSession.objects.get(id=id)
except PlannedSession.DoesNotExist:
raise Http404("Planned Session does not exist")
if ps.manager != request.user:
raise PermissionDenied("You can only store your own sessions as a template")
ps.pk = None
ps.id = None
ps.is_template = True
ps.startdate = datetime.date(1970,1,1)
ps.enddate = datetime.date(1970,1,1)
ps.save()
url = reverse(plannedsession_create_view,kwargs={'userid':r.user.id})
startdatestring = startdate.strftime('%Y-%m-%d')
enddatestring = enddate.strftime('%Y-%m-%d')
url += '?when='+startdatestring+'/'+enddatestring
return HttpResponseRedirect(url)
# Edit an existing planned session
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def plannedsession_edit_view(request,id=0,userid=0):
r = getrequestplanrower(request,userid=userid)
startdate,enddate = get_dates_timeperiod(request) startdate,enddate = get_dates_timeperiod(request)
try: try:
trainingplan = TrainingPlan.objects.filter( trainingplan = TrainingPlan.objects.filter(
startdate__lte = startdate, startdate__lte = startdate,
@@ -1598,10 +1527,131 @@ def plannedsession_edit_view(request,id=0,userid=0):
if ps.sessiontype in ['race','indoorrace']: if ps.sessiontype in ['race','indoorrace']:
raise PermissionDenied("You are not allowed to edit this planned session because it is a race") raise PermissionDenied("You are not allowed to edit this planned session because it is a race")
if not ps.is_template:
ps.pk = None
ps.id = None
ps.is_template = True
ps.startdate = datetime.date(1970,1,1)
ps.enddate = datetime.date(1970,1,1)
ps.team.clear()
ps.save()
sessioncreateform = PlannedSessionTemplateForm(instance=ps)
if request.method == 'POST':
sessioncreateform = PlannedSessionTemplateForm(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_templateedit_view,
kwargs = {
'id':int(ps.id),
})
startdatestring = startdate.strftime('%Y-%m-%d')
enddatestring = enddate.strftime('%Y-%m-%d')
url += '?when='+startdatestring+'/'+enddatestring
next = request.GET.get('next', url)
return HttpResponseRedirect(next)
breadcrumbs = [
{
'url': reverse(plannedsessions_view),
'name': 'Sessions'
},
{
'url':reverse(plannedsession_templateedit_view,
kwargs={
'id':id,
}
),
'name': 'Edit'
}
]
sessiontemplates = PlannedSession.objects.filter(manager=request.user,is_template=True)
return render(request,'plannedsessiontemplateedit.html',
{
'teams':get_my_teams(request.user),
'plan': trainingplan,
'breadcrumbs': breadcrumbs,
'form': sessioncreateform,
'active':'nav-plan',
'thesession': ps,
'sessiontemplates': sessiontemplates,
'rower': r,
})
@permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True)
@user_passes_test(can_plan, login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def plannedsession_totemplate_view(request,id=0):
r = getrequestplanrower(request)
startdate, enddate = get_dates_timeperiod(request)
ps = get_object_or_404(PlannedSession,pk=id)
ps.pk = None
ps.id = None
ps.is_template = True
ps.startdate = datetime.date(1970,1,1)
ps.enddate = datetime.date(1970,1,1)
ps.save()
url = reverse(plannedsession_create_view,kwargs={'userid':r.user.id})
startdatestring = startdate.strftime('%Y-%m-%d')
enddatestring = enddate.strftime('%Y-%m-%d')
url += '?when='+startdatestring+'/'+enddatestring
next = request.GET.get('next', url)
return HttpResponseRedirect(next)
# Edit an existing planned session
@permission_required('plannedsession.change_session',fn=get_session_by_pk,raise_exception=True)
@user_passes_test(can_plan,login_url="/rowers/paidplans/",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def plannedsession_edit_view(request,id=0,userid=0):
r = getrequestplanrower(request,userid=userid)
startdate,enddate = get_dates_timeperiod(request)
try:
trainingplan = TrainingPlan.objects.filter(
startdate__lte = startdate,
rowers = r,
enddate__gte = enddate)[0]
except IndexError:
trainingplan = None
ps = get_object_or_404(PlannedSession,pk=id)
if ps.team.all() or ps.rower.all().count()>1: if ps.team.all() or ps.rower.all().count()>1:
url = reverse(plannedsession_teamedit_view, url = reverse(plannedsession_teamedit_view,
kwargs={ kwargs={
'sessionid':id, 'id':id,
}) })
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@@ -1697,38 +1747,29 @@ def plannedsession_edit_view(request,id=0,userid=0):
}) })
@login_required() @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def plannedsession_detach_view(request,id=0,psid=0): def plannedsession_detach_view(request,id=0,psid=0):
r = getrequestrower(request) r = getrequestrower(request)
try:
ps = PlannedSession.objects.get(id=psid) ps = get_object_or_404(PlannedSession,pk=psid)
except PlannedSession.DoesNotExist:
raise Http404("Planned Session does not exist")
w = get_workout(id) w = get_workout(id)
if (checkworkoutuser(request.user,w)==False):
return HttpResponseForbidden("Permission Error")
remove_workout_plannedsession(w,ps) remove_workout_plannedsession(w,ps)
url = reverse(plannedsession_view,kwargs={'id':psid}) url = reverse(plannedsession_view,kwargs={'id':psid})
next = request.GET.get('next', url)
return HttpResponseRedirect(url) return HttpResponseRedirect(next)
@login_required() @login_required()
@permission_required('plannedsession.view_session',fn=get_session_by_pk,raise_exception=True)
def plannedsession_view(request,id=0,userid=0): def plannedsession_view(request,id=0,userid=0):
r = getrequestplanrower(request,userid=userid) r = getrequestplanrower(request,userid=userid)
ps = get_object_or_404(PlannedSession,pk=id)
try:
ps = PlannedSession.objects.get(id=id)
except PlannedSession.DoesNotExist:
raise Http404("Planned Session does not exist")
if ps.sessiontype in ['race','indoorrace']: if ps.sessiontype in ['race','indoorrace']:
url = reverse('virtualevent_view', url = reverse('virtualevent_view',
@@ -1745,16 +1786,6 @@ def plannedsession_view(request,id=0,userid=0):
m = ps.manager m = ps.manager
mm = Rower.objects.get(user=m) mm = Rower.objects.get(user=m)
if ps.manager != request.user:
if 'coach' in r.rowerplan and r not in ps.rower.all():
teams = Team.objects.filter(manager=request.user)
members = Rower.objects.filter(team__in=teams).distinct()
teamusers = [m.user for m in members]
if ps.manager not in teamusers:
raise PermissionDenied("You do not have access to this session")
elif r not in ps.rower.all():
raise PermissionDenied("You do not have access to this session")
resultsdict = get_session_metrics(ps) resultsdict = get_session_metrics(ps)
resultsdict = pd.DataFrame(resultsdict).transpose().to_dict() resultsdict = pd.DataFrame(resultsdict).transpose().to_dict()
@@ -1958,24 +1989,23 @@ class PlannedSessionDelete(DeleteView):
w.save() w.save()
url = reverse(plannedsessions_view) url = reverse(plannedsessions_view)
next = self.request.GET.get('next',url)
return url return next
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super(PlannedSessionDelete, self).get_object(*args, **kwargs) obj = super(PlannedSessionDelete, self).get_object(*args, **kwargs)
m = Rower.objects.get(user=obj.manager) if not can_delete_session(self.request.user,obj):
if not checkaccessuser(self.request.user,m):
raise PermissionDenied('You are not allowed to delete this planned session') raise PermissionDenied('You are not allowed to delete this planned session')
return obj return obj
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans", @user_passes_test(can_plan,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan", message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) redirect_field_name=None)
def rower_create_trainingplan(request,userid=0): def rower_create_trainingplan(request,id=0):
therower = getrequestrower(request,userid=id)
therower = getrequestrower(request,userid=userid)
theuser = therower.user theuser = therower.user
themanager = getrower(request.user) themanager = getrower(request.user)
@@ -2041,7 +2071,8 @@ def rower_create_trainingplan(request,userid=0):
p.save() p.save()
for athlete in athletes: for athlete in athletes:
p.rowers.add(athlete) if can_plan_user(request.user,athlete):
p.rowers.add(athlete)
targets = TrainingTarget.objects.filter( targets = TrainingTarget.objects.filter(
@@ -2076,12 +2107,12 @@ def rower_create_trainingplan(request,userid=0):
breadcrumbs = [ breadcrumbs = [
{ {
'url':reverse(plannedsessions_view, 'url':reverse(plannedsessions_view,
kwargs={'userid':userid}), kwargs={'userid':id}),
'name': 'Plan' 'name': 'Plan'
}, },
{ {
'url':reverse(rower_create_trainingplan, 'url':reverse(rower_create_trainingplan,
kwargs={'userid':userid}), kwargs={'id':id}),
'name': 'Manage Plans and Targets' 'name': 'Manage Plans and Targets'
} }
] ]
@@ -2099,40 +2130,27 @@ def rower_create_trainingplan(request,userid=0):
'old_targets':old_targets, 'old_targets':old_targets,
}) })
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans", @user_passes_test(can_plan,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan", message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) redirect_field_name=None)
@permission_required('target.delete_target',fn=get_target_by_pk,raise_exception=True)
def rower_delete_trainingtarget(request,id=0): def rower_delete_trainingtarget(request,id=0):
try:
target = TrainingTarget.objects.get(id=id)
except TrainingPlan.DoesNotExist:
raise Http404("Training Plan Does Not Exist")
if checkaccessuser(request.user,target.manager): target = get_object_or_404(TrainingTarget,pk=id)
target.delete() target.delete()
messages.info(request,"We have deleted the training target")
else:
raise PermissionDenied("Access denied")
url = reverse(rower_create_trainingplan) url = reverse(rower_create_trainingplan)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans", @user_passes_test(can_plan,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan", message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) redirect_field_name=None)
@permission_required('target.delete_plan',fn=get_plan_by_pk,raise_exception=True)
def rower_delete_trainingplan(request,id=0): def rower_delete_trainingplan(request,id=0):
try: plan = get_object_or_404(TrainingPlan,pk=id)
plan = TrainingPlan.objects.get(id=id) plan.delete()
except TrainingPlan.DoesNotExist:
raise Http404("Training Plan Does Not Exist")
if checkaccessuser(request.user,plan.manager):
plan.delete()
messages.info(request,"We have deleted the training plan")
else:
raise PermissionDenied("Access denied")
url = reverse(rower_create_trainingplan) url = reverse(rower_create_trainingplan)
@@ -2145,7 +2163,7 @@ class TrainingPlanDelete(DeleteView):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super(TrainingPlanDelete, self).get_object(*args, **kwargs) obj = super(TrainingPlanDelete, self).get_object(*args, **kwargs)
if not checkaccessuser(self.request.user,obj.manager): if not can_delete_plan(self.request.user,obj):
raise PermissionDenied('You are not allowed to delete this training plan') raise PermissionDenied('You are not allowed to delete this training plan')
return obj return obj
@@ -2211,7 +2229,7 @@ class MicroCycleDelete(DeleteView):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super(MicroCycleDelete, self).get_object(*args, **kwargs) obj = super(MicroCycleDelete, self).get_object(*args, **kwargs)
if not checkaccessuser(self.request.user,obj.plan.plan.plan.manager): if not can_delete_cycle(self.request.user,obj):
raise PermissionDenied('You are not allowed to delete this training plan cycle') raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj return obj
@@ -2273,7 +2291,7 @@ class MesoCycleDelete(DeleteView):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super(MesoCycleDelete, self).get_object(*args, **kwargs) obj = super(MesoCycleDelete, self).get_object(*args, **kwargs)
if not checkaccessuser(self.request.user,obj.plan.plan.manager): if not can_delete_cycle(self.request.user,obj):
raise PermissionDenied('You are not allowed to delete this training plan cycle') raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj return obj
@@ -2327,13 +2345,13 @@ class MacroCycleDelete(DeleteView):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super(MacroCycleDelete, self).get_object(*args, **kwargs) obj = super(MacroCycleDelete, self).get_object(*args, **kwargs)
if not checkaccessuser(self.request.user,obj.plan.manager): if not can_delete_cycle(self.request.user,obj):
raise PermissionDenied('You are not allowed to delete this training plan cycle') raise PermissionDenied('You are not allowed to delete this training plan cycle')
return obj return obj
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans", @user_passes_test(can_plan,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan", message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) redirect_field_name=None)
def rower_trainingplan_execution_view(request, def rower_trainingplan_execution_view(request,
@@ -2350,7 +2368,7 @@ def rower_trainingplan_execution_view(request,
plan = TrainingPlan.objects.get(id=id) plan = TrainingPlan.objects.get(id=id)
except TrainingPlan.DoesNotExist: except TrainingPlan.DoesNotExist:
raise Http404("Training Plan Does Not Exist") raise Http404("Training Plan Does Not Exist")
if not checkaccessuser(request.user,plan.manager): if not is_coach_user(request.user,plan.manager.user):
if request.user.rower not in plan.rowers.all(): if request.user.rower not in plan.rowers.all():
raise PermissionDenied("Access denied") raise PermissionDenied("Access denied")
@@ -2423,9 +2441,11 @@ def rower_trainingplan_execution_view(request,
) )
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans", #@user_passes_test(can_plan,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan", # message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) # redirect_field_name=None)
@login_required()
@permission_required('plan.view_plan',fn=get_plan_by_pk,raise_exception=True)
def rower_trainingplan_view(request, def rower_trainingplan_view(request,
id=0, id=0,
userid=0, userid=0,
@@ -2436,18 +2456,9 @@ def rower_trainingplan_view(request,
startdate,enddate = get_dates_timeperiod(request) startdate,enddate = get_dates_timeperiod(request)
plan = get_object_or_404(TrainingPlan,pk=id)
try:
plan = TrainingPlan.objects.get(id=id)
except TrainingPlan.DoesNotExist:
raise Http404("Training Plan Does Not Exist")
r = getrequestrower(request,userid=userid) r = getrequestrower(request,userid=userid)
if not checkaccessuser(request.user,plan.manager):
if request.user.rower not in plan.rowers.all():
raise PermissionDenied("Access denied")
createmacrofillers(plan) createmacrofillers(plan)
macrocycles = TrainingMacroCycle.objects.filter( macrocycles = TrainingMacroCycle.objects.filter(
plan=plan, plan=plan,
@@ -2570,10 +2581,7 @@ class TrainingMacroCycleUpdate(UpdateView):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super(TrainingMacroCycleUpdate, self).get_object(*args, **kwargs) obj = super(TrainingMacroCycleUpdate, self).get_object(*args, **kwargs)
if obj.plan.manager is not None and self.request.user.rower != obj.plan.manager: if not can_change_cycle(self.request.user,obj):
raise PermissionDenied('You are not allowed to edit this training plan cycle')
if not checkaccessuser(self.request.user,obj.plan.manager):
raise PermissionDenied('You are not allowed to edit this training plan cycle') raise PermissionDenied('You are not allowed to edit this training plan cycle')
else: else:
obj.type = 'userdefined' obj.type = 'userdefined'
@@ -2644,7 +2652,7 @@ class TrainingMesoCycleUpdate(UpdateView):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super(TrainingMesoCycleUpdate, self).get_object(*args, **kwargs) obj = super(TrainingMesoCycleUpdate, self).get_object(*args, **kwargs)
if obj.plan.plan.manager is not None and self.request.user.rower != obj.plan.plan.manager: if not can_change_cycle(self.request.user,obj):
raise PermissionDenied('You are not allowed to edit this training plan cycle') raise PermissionDenied('You are not allowed to edit this training plan cycle')
else: else:
@@ -2722,7 +2730,7 @@ class TrainingMicroCycleUpdate(UpdateView):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super(TrainingMicroCycleUpdate, self).get_object(*args, **kwargs) obj = super(TrainingMicroCycleUpdate, self).get_object(*args, **kwargs)
if obj.plan.plan.plan.manager is not None and self.request.user.rower != obj.plan.plan.plan.manager: if not can_change_cycle(self.request.user,obj):
raise PermissionDenied('You are not allowed to edit this training plan cycle') raise PermissionDenied('You are not allowed to edit this training plan cycle')
@@ -2787,10 +2795,8 @@ class TrainingPlanUpdate(UpdateView):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super(TrainingPlanUpdate, self).get_object(*args, **kwargs) obj = super(TrainingPlanUpdate, self).get_object(*args, **kwargs)
if obj.manager is not None and self.request.user.rower != obj.manager: if not can_change_plan(self.request.user,obj):
raise PermissionDenied('You are not allowed to edit this training plan cycle') raise PermissionDenied('You are not allowed to edit this training plan cycle')
if obj.manager.rowerplan not in ['coach','freecoach','plan']:
raise PermissionDenied('You are not allowed to edit this training plan')
return obj return obj
@@ -2839,24 +2845,19 @@ class TrainingTargetUpdate(UpdateView):
def get_object(self, *args, **kwargs): def get_object(self, *args, **kwargs):
obj = super(TrainingTargetUpdate, self).get_object(*args, **kwargs) obj = super(TrainingTargetUpdate, self).get_object(*args, **kwargs)
if obj.manager is not None and self.request.user.rower != obj.manager: if not can_change_target(self.request.user,obj):
raise PermissionDenied('You are not allowed to edit this training plan cycle') raise PermissionDenied('You are not allowed to edit this training plan cycle')
return obj return obj
from rowers.utils import allsundays from rowers.utils import allsundays
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans", @user_passes_test(can_plan,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan", message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) redirect_field_name=None)
@permission_required('cycle.change_cycle',fn=get_meso_by_pk,raise_exception=True)
def planmesocyclebyweek(request,id=0,userid=0): def planmesocyclebyweek(request,id=0,userid=0):
try: cycle = get_object_or_404(TrainingMesoCycle,pk=id)
cycle = TrainingMesoCycle.objects.get(id=id)
except TrainingMesoCycle.DoesNotExist:
raise Http404("Training Cycle does not exist")
if not checkaccessuser(request.user,cycle.plan.plan.manager):
raise PermissionDenied("You are not allowed to do this")
micros = TrainingMicroCycle.objects.filter(plan=cycle) micros = TrainingMicroCycle.objects.filter(plan=cycle)
for m in micros: for m in micros:
@@ -2901,17 +2902,12 @@ def planmesocyclebyweek(request,id=0,userid=0):
from rowers.utils import allmonths from rowers.utils import allmonths
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans", @user_passes_test(can_plan,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan", message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None) redirect_field_name=None)
@permission_required('cycle.change_cycle',fn=get_macro_by_pk,raise_exception=True)
def planmacrocyclebymonth(request,id=0,userid=0): def planmacrocyclebymonth(request,id=0,userid=0):
try: cycle = get_object_or_404(TrainingMacroCycle,pk=id)
cycle = TrainingMacroCycle.objects.get(id=id)
except TrainingMacroCycle.DoesNotExist:
raise Http404("Training Cycle does not exist")
if not checkaccessuser(request.user,cycle.plan.manager):
raise PermissionDenied("You are not allowed to do this")
mesos = TrainingMesoCycle.objects.filter(plan=cycle) mesos = TrainingMesoCycle.objects.filter(plan=cycle)
for m in mesos: for m in mesos:
+26 -69
View File
@@ -7,7 +7,6 @@ from rowers.views.statements import *
# List Courses # List Courses
@login_required()
def courses_view(request): def courses_view(request):
r = getrower(request.user) r = getrower(request.user)
@@ -81,17 +80,12 @@ def course_map_view(request,id=0):
@login_required() @login_required()
@permission_required('course.change_course',fn=get_course_by_pk,raise_exception=True)
def course_replace_view(request,id=0): def course_replace_view(request,id=0):
try: course = get_object_or_404(GeoCourse,pk=id)
course = GeoCourse.objects.get(id=id)
except GeoCourse.DoesNotExist:
return Http404("Course doesn't exist")
r = getrower(request.user) r = getrower(request.user)
if course.manager != r:
raise PermissionDenied("Access denied")
thecourses = GeoCourse.objects.filter(manager=r).exclude(id=id) thecourses = GeoCourse.objects.filter(manager=r).exclude(id=id)
if request.method == 'POST': if request.method == 'POST':
@@ -143,17 +137,12 @@ def course_replace_view(request,id=0):
'form':form}) 'form':form})
@login_required() @login_required()
@permission_required('course.delete_course',fn=get_course_by_pk,raise_exception=True)
def course_delete_view(request,id=0): def course_delete_view(request,id=0):
try: course = get_object_or_404(GeoCourse,pk=id)
course = GeoCourse.objects.get(id=id)
except GeoCourse.DoesNotExist:
return Http404("Course doesn't exist")
r = getrower(request.user) r = getrower(request.user)
if course.manager != r:
raise PermissionDenied("Access denied")
ps = PlannedSession.objects.filter(course=course) ps = PlannedSession.objects.filter(course=course)
nosessions = len(ps) == 0 nosessions = len(ps) == 0
@@ -165,17 +154,12 @@ def course_delete_view(request,id=0):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@login_required() @login_required()
@permission_required('course.change_course',fn=get_course_by_pk,raise_exception=True)
def course_edit_view(request,id=0): def course_edit_view(request,id=0):
try: course = get_object_or_404(GeoCourse,pk=id)
course = GeoCourse.objects.get(id=id)
except GeoCourse.DoesNotExist:
return Http404("Course doesn't exist")
r = getrower(request.user) r = getrower(request.user)
if course.manager != r:
raise PermissionDenied("Access denied")
ps = PlannedSession.objects.filter(course=course) ps = PlannedSession.objects.filter(course=course)
nosessions = len(ps) == 0 nosessions = len(ps) == 0
@@ -266,11 +250,10 @@ def course_view(request,id=0):
) )
@login_required() @login_required()
@permission_required('racelogo.delete_logo',fn=get_logo_by_pk,raise_exception=True)
def logo_delete_view(request,id=0): def logo_delete_view(request,id=0):
try: logo = get_object_or_404(RaceLogo,pk=id)
logo = RaceLogo.objects.get(id=id)
except RaceLogo.DoesNotExist:
raise Http404("Logo doesn't exist")
if logo.user == request.user: if logo.user == request.user:
logo.delete() logo.delete()
@@ -281,28 +264,18 @@ def logo_delete_view(request,id=0):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@login_required() @login_required()
@permission_required('virtualevent.change_race',fn=get_virtualevent_by_pk,raise_exception=True)
def virtualevent_setlogo_view(request,id=0,logoid=0): def virtualevent_setlogo_view(request,id=0,logoid=0):
try: race = get_object_or_404(VirtualRace,pk=id)
race = VirtualRace.objects.get(id=id) logo = get_object_or_404(RaceLogo,pk=logoid)
except VirtualRace.DoesNotExist:
raise Http404("Race doesn't exist")
try: otherlogos = race.logos.all()
logo = RaceLogo.objects.get(id=logoid) for otherlogo in otherlogos:
except RaceLogo.DoesNotExist: otherlogo.race.remove(race)
raise Http404("Logo doesn't exist")
if logo.user == request.user and race.manager == request.user:
otherlogos = race.logos.all()
for otherlogo in otherlogos:
otherlogo.race.remove(race)
logo.race.add(race) logo.race.add(race)
logo.save() logo.save()
else:
message = "You do not own this race or this image"
messages.error(request,message)
url = reverse('virtualevent_view', url = reverse('virtualevent_view',
kwargs={'id':id}) kwargs={'id':id})
@@ -318,10 +291,7 @@ def virtualevent_uploadimage_view(request,id=0):
r = getrower(request.user) r = getrower(request.user)
try: race = get_object_or_404(VirtualRace,pk=id)
race = VirtualRace.objects.get(id=id)
except VirtualRace.DoesNotExist:
raise Http404("Race doesn't exist")
logos = RaceLogo.objects.filter(user=request.user).order_by("-creationdatetime") logos = RaceLogo.objects.filter(user=request.user).order_by("-creationdatetime")
@@ -576,17 +546,12 @@ def virtualevents_view(request):
) )
@login_required() @login_required()
def virtualevent_disqualify_view(request,raceid=0,recordid=0): @permission_required('virtualevent.change_race',fn=get_virtualevent_by_pk,raise_exception=True)
def virtualevent_disqualify_view(request,id=0,recordid=0):
r = getrower(request.user) r = getrower(request.user)
race = get_object_or_404(VirtualRace,pk=id)
try:
race = VirtualRace.objects.get(id=raceid)
except VirtualRace.DoesNotExist:
raise Http404("Virtual Race does not exist")
if r.user != race.manager:
raise PermissionDenied("Access denied")
if race.sessiontype == 'race': if race.sessiontype == 'race':
recordobj = VirtualRaceResult recordobj = VirtualRaceResult
@@ -1872,15 +1837,12 @@ def virtualevent_create_view(request):
}) })
@login_required() @login_required()
@permission_required('virtualevent.change_race',fn=get_virtualevent_by_pk,raise_exception=True)
def virtualevent_edit_view(request,id=0): def virtualevent_edit_view(request,id=0):
r = getrower(request.user) r = getrower(request.user)
race = get_object_or_404(VirtualRace,pk=id)
try:
race = VirtualRace.objects.get(id=id)
if race.manager != request.user:
raise PermissionDenied("Access denied")
except VirtualRace.DoesNotExist:
raise Http404("Virtual Race does not exist")
start_time = race.start_time start_time = race.start_time
start_date = race.startdate start_date = race.startdate
@@ -1970,15 +1932,10 @@ def virtualevent_edit_view(request,id=0):
}) })
@login_required() @login_required()
@permission_required('virtualevent.change_race',fn=get_virtualevent_by_pk,raise_exception=True)
def indoorvirtualevent_edit_view(request,id=0): def indoorvirtualevent_edit_view(request,id=0):
r = getrower(request.user) r = getrower(request.user)
race = get_object_or_404(VirtualRace,pk=id)
try:
race = VirtualRace.objects.get(id=id)
if race.manager != request.user:
raise PermissionDenied("Access denied")
except VirtualRace.DoesNotExist:
raise Http404("Virtual Race does not exist")
start_time = race.start_time start_time = race.start_time
start_date = race.startdate start_date = race.startdate
+107 -98
View File
@@ -19,6 +19,7 @@ from django.db.models import Q
from django import template from django import template
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import get_object_or_404
from matplotlib.backends.backend_agg import FigureCanvas from matplotlib.backends.backend_agg import FigureCanvas
import gc import gc
from pyparsing import ParseException from pyparsing import ParseException
@@ -31,10 +32,22 @@ from icalendar import Calendar, Event
from functools import reduce from functools import reduce
from rules.contrib.views import PermissionRequiredMixin
import rowers.braintreestuff as braintreestuff import rowers.braintreestuff as braintreestuff
import rowers.payments as payments import rowers.payments as payments
from rowers.opaque import encoder from rowers.opaque import encoder
from rowers.rower_rules import (
ispromember,is_coach_user,is_team_member,is_rower_team_member,
is_workout_user,isplanmember,can_delete_session,
can_view_target,can_change_target,can_delete_target,
can_view_plan,can_change_plan,can_delete_plan,
can_view_cycle,can_change_cycle,can_delete_cycle,
can_add_workout_member,can_plan_user,is_paid_coach,
can_start_trial, can_start_plantrial,can_plan,is_workout_team
)
from django.shortcuts import render from django.shortcuts import render
from django.template.loader import render_to_string from django.template.loader import render_to_string
@@ -97,7 +110,7 @@ from rowers.models import (
TrainingMesoCycleForm, TrainingMicroCycleForm, TrainingMesoCycleForm, TrainingMicroCycleForm,
RaceLogo,RowerBillingAddressForm,PaidPlan, RaceLogo,RowerBillingAddressForm,PaidPlan,
AlertEditForm, ConditionEditForm, AlertEditForm, ConditionEditForm,
PlannedSessionComment,CoachRequest,CoachOffer,checkaccessplanuser, PlannedSessionComment,CoachRequest,CoachOffer,
VideoAnalysis VideoAnalysis
) )
from rowers.models import ( from rowers.models import (
@@ -107,7 +120,7 @@ from rowers.models import (
WorkoutComment,WorkoutCommentForm,RowerExportForm, WorkoutComment,WorkoutCommentForm,RowerExportForm,
CalcAgePerformance, CalcAgePerformance,
PowerTimeFitnessMetric,BlogPost, PowerTimeFitnessMetric,BlogPost,
PlannedSessionForm, PlannedSessionForm,PlannedSessionTemplateForm,
PlannedSessionFormSmall,GeoCourseEditForm,VirtualRace, PlannedSessionFormSmall,GeoCourseEditForm,VirtualRace,
VirtualRaceForm,VirtualRaceResultForm,RowerImportExportForm, VirtualRaceForm,VirtualRaceResultForm,RowerImportExportForm,
IndoorVirtualRaceResultForm,IndoorVirtualRaceResult, IndoorVirtualRaceResultForm,IndoorVirtualRaceResult,
@@ -240,7 +253,7 @@ from rq import Queue,cancel_job
from django.core.cache import cache from django.core.cache import cache
from django_mailbox.models import Message,Mailbox,MessageAttachment from django_mailbox.models import Message,Mailbox,MessageAttachment
from rules.contrib.views import permission_required, objectgetter
# Utility to get stroke data in a JSON response # Utility to get stroke data in a JSON response
class JSONResponse(HttpResponse): class JSONResponse(HttpResponse):
@@ -274,6 +287,49 @@ def getfavorites(r,row):
return favorites,maxfav return favorites,maxfav
def get_logo_by_pk(request,*args,**kwargs):
id = kwargs['id']
return get_object_or_404(RaceLogo,pk=id)
def get_virtualevent_by_pk(request,*args,**kwargs):
id = kwargs['id']
return get_object_or_404(VirtualRace,pk=id)
def get_promember(request,*args,**kwargs):
return request.user
def get_course_by_pk(request,*args,**kwargs):
id = kwargs['id']
return get_object_or_404(GeoCourse,pk=id)
def get_workout_by_opaqueid(request,id,**kwargs):
pk = encoder.decode_hex(id)
return get_object_or_404(Workout,pk=pk)
def get_session_by_pk(request,*args,**kwargs):
id = kwargs['id']
return get_object_or_404(PlannedSession,pk=id)
def get_target_by_pk(request,*args,**kwargs):
id = kwargs['id']
return get_object_or_404(TrainingTarget,pk=id)
def get_plan_by_pk(request,*args,**kwargs):
id = kwargs['id']
return get_object_or_404(TrainingPlan,pk=id)
def get_macro_by_pk(request,*args,**kwargs):
id = kwargs['id']
return get_object_or_404(TrainingMacroCycle,pk=id)
def get_meso_by_pk(request,*args,**kwargs):
id = kwargs['id']
return get_object_or_404(TrainingMesoCycle,pk=id)
def get_micro_by_pk(request,*args,**kwargs):
id = kwargs['id']
return get_object_or_404(TrainingMicroCycle,pk=id)
def get_workout_default_page(request,id): def get_workout_default_page(request,id):
if request.user.is_anonymous: if request.user.is_anonymous:
return reverse('workout_view',kwargs={'id':id}) return reverse('workout_view',kwargs={'id':id})
@@ -284,11 +340,42 @@ def get_workout_default_page(request,id):
else: else:
return reverse('workout_workflow_view',kwargs={'id':id}) return reverse('workout_workflow_view',kwargs={'id':id})
def getrequestrower(request,rowerid=0,userid=0,notpermanent=False): def get_user_by_userid(*args,**kwargs):
request = args[0]
try:
id = kwargs['userid']
except KeyError:
id = request.user.id
if id is not None and int(id) == 0:
id = request.user.id
u = get_object_or_404(User,pk=id)
return u
def get_user_by_id(*args,**kwargs):
request = args[0]
try:
id = args[1]
except IndexError:
try:
id = kwargs['id']
except KeyError:
id = request.user.id
return get_object_or_404(User,pk=id)
def get_rower_by_id(request,id):
u = User.objects.get(id=id)
return u.rower
def getrequestrower(request,rowerid=0,userid=0,notpermanent=False):
userid = int(userid) userid = int(userid)
rowerid = int(rowerid) rowerid = int(rowerid)
if userid == 0:
userid = request.user.id
if notpermanent == False: if notpermanent == False:
if rowerid == 0 and 'rowerid' in request.session: if rowerid == 0 and 'rowerid' in request.session:
rowerid = request.session['rowerid'] rowerid = request.session['rowerid']
@@ -300,16 +387,25 @@ def getrequestrower(request,rowerid=0,userid=0,notpermanent=False):
if rowerid != 0: if rowerid != 0:
r = Rower.objects.get(id=rowerid) r = Rower.objects.get(id=rowerid)
u = r.user
elif userid != 0: elif userid != 0:
u = User.objects.get(id=userid) u = User.objects.get(id=userid)
r = getrower(u) r = getrower(u)
elif request.user.is_anonymous:
return None
else: else:
r = getrower(request.user) r = getrower(request.user)
u = r.user
except Rower.DoesNotExist: except Rower.DoesNotExist:
raise Http404("Rower doesn't exist") raise Http404("Rower doesn't exist")
if not checkaccessuser(request.user,r): if r.user == request.user:
return r
if userid != 0 and not is_rower_team_member(request.user,u.rower):
request.session['rowerid'] = request.user.rower.id
raise PermissionDenied("You have no access to this user") raise PermissionDenied("You have no access to this user")
if notpermanent == False: if notpermanent == False:
@@ -342,7 +438,8 @@ def getrequestplanrower(request,rowerid=0,userid=0,notpermanent=False):
except Rower.DoesNotExist: except Rower.DoesNotExist:
raise Http404("Rower doesn't exist") raise Http404("Rower doesn't exist")
if not checkaccessplanuser(request.user,r): if r.user != request.user and not can_plan_user(request.user,r ):
request.session['rowerid'] = r.id
raise PermissionDenied("You have no access to this user") raise PermissionDenied("You have no access to this user")
if notpermanent == False: if notpermanent == False:
@@ -376,21 +473,6 @@ def get_workout(id):
return w return w
def get_workout_permitted(user,id):
w = get_workout(id)
if (checkworkoutuser(user,w)==False):
raise PermissionDenied("Access denied")
return w
def get_workout_permittedview(user,id):
w = get_workout(id)
if (checkworkoutuserview(user,w)==False):
raise PermissionDenied("Access denied")
return w
def getvalue(data): def getvalue(data):
perc = 0 perc = 0
@@ -432,7 +514,6 @@ class SessionTaskListener(threading.Thread):
for item in self.pubsub.listen(): for item in self.pubsub.listen():
if item['data'] == "KILL": if item['data'] == "KILL":
self.pubsub.unsubscribe() self.pubsub.unsubscribe()
print(self, "unsubscribed and finished")
break break
else: else:
self.work(item) self.work(item)
@@ -773,9 +854,9 @@ def get_stored_tasks_status(request):
return taskstatus return taskstatus
@login_required() @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)
def get_thumbnails(request,id): def get_thumbnails(request,id):
row = get_workout_permitted(request.user,id) row = get_workout_by_opaqueid(request,id)
r = getrower(request.user) r = getrower(request.user)
@@ -860,27 +941,6 @@ def get_blog_posts_old(request):
@login_required()
def get_testscript(request,id):
row = get_workout_permitted(request.user,id)
r = getrower(request.user)
object = {
"script":"""
<div id="id_script">
<script>alert("hi")</script>
</div>
""",
"div":"""
<div id="id_div">
Hoi
</div>
"""
}
return JSONResponse([object,object])
@login_required() @login_required()
def session_jobs_view(request): def session_jobs_view(request):
taskstatus = get_stored_tasks_status(request) taskstatus = get_stored_tasks_status(request)
@@ -923,10 +983,10 @@ def rowhascoordinates(row):
def rdata(file,rower=rrower()): def rdata(file,rower=rrower()):
try: try:
res = rrdata(csvfile=file,rower=rower) res = rrdata(csvfile=file,rower=rower)
except (IOError, IndexError, EOFError): except (IOError, IndexError, EOFError,FileNotFoundError):
try: try:
res = rrdata(csvfile=file+'.gz',rower=rower) res = rrdata(csvfile=file+'.gz',rower=rower)
except (IOError, IndexError, EOFError): except (IOError, IndexError, EOFError,FileNotFoundError):
res = 0 res = 0
return res return res
@@ -989,10 +1049,6 @@ from rowers.utils import (
import rowers.datautils as datautils import rowers.datautils as datautils
from rowers.models import (
checkworkoutuser,checkaccessuser,checkviewworkouts,checkworkoutuserview
)
# Check if a user is a Coach member # Check if a user is a Coach member
def iscoachmember(user): def iscoachmember(user):
if not user.is_anonymous: if not user.is_anonymous:
@@ -1008,56 +1064,9 @@ def iscoachmember(user):
return result return result
def cancreateteam(user):
if user.is_anonymous:
return False
try:
r = Rower.objects.get(user=user)
except Rower.DoesNotExist:
r = Rower(user=user)
r.save()
if user.is_authenticated and ('coach' in r.rowerplan):
return True
elif user.is_athenticated() and r.rowerplan in ['plan','pro']:
ts = Team.objects.filter(manager=user)
if len(otherteams) >= 1:
return False
# 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=='freecoach' or r.rowerplan=='plan')
if not result and r.plantrialexpires:
result = user.is_authenticated and r.plantrialexpires >= datetime.date.today()
else:
result = False
return result
from rowers.utils import ProcessorCustomerError from rowers.utils import ProcessorCustomerError
from rowers.utils import isprorower
# Check if a user is a Pro member
def ispromember(user):
if user and 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 isprorower(r)
else:
result = False
return result
# More User/Rower utils # More User/Rower utils
def add_defaultfavorites(r): def add_defaultfavorites(r):
+41 -59
View File
@@ -5,9 +5,16 @@ from __future__ import unicode_literals
from rowers.views.statements import * from rowers.views.statements import *
from rowers.rower_rules import *
def get_team_by_pk(request,team_id):
return get_object_or_404(Team,pk=team_id)
@login_required() @login_required()
def team_view(request,id=0,userid=0): @permission_required('teams.view_team',fn=get_team_by_pk,raise_exception=True)
def team_view(request,team_id=0,userid=0):
ismember = 0 ismember = 0
hasrequested = 0 hasrequested = 0
r = getrequestrower(request,userid=userid) r = getrequestrower(request,userid=userid)
@@ -15,15 +22,10 @@ def team_view(request,id=0,userid=0):
myteams, memberteams, otherteams = get_teams(request) myteams, memberteams, otherteams = get_teams(request)
teams.remove_expired_invites() teams.remove_expired_invites()
t = get_object_or_404(Team,pk=team_id)
#if r.rowerplan == 'basic' and t.manager.rower.rowerplan != 'coach':
try: # raise PermissionDenied("You need to be on a Paid Plan to see or join this team")
t = Team.objects.get(id=id)
except Team.DoesNotExist:
raise Http404("Team doesn't exist")
if r.rowerplan == 'basic' and t.manager.rower.rowerplan != 'coach':
raise PermissionDenied("You need to be on a Paid Plan to see or join this team")
q = User.objects.filter(rower__isnull=False,rower__team__in=myteams).distinct().exclude(rower__team__name=t.name) q = User.objects.filter(rower__isnull=False,rower__team__in=myteams).distinct().exclude(rower__team__name=t.name)
@@ -83,7 +85,7 @@ def team_view(request,id=0,userid=0):
'name': 'Groups' 'name': 'Groups'
}, },
{ {
'url':reverse(team_view,kwargs={'id':id}), 'url':reverse(team_view,kwargs={'team_id':team_id}),
'name': t.name 'name': t.name
} }
] ]
@@ -119,7 +121,7 @@ def team_leaveconfirm_view(request,id=0):
'name': 'Groups' 'name': 'Groups'
}, },
{ {
'url':reverse(team_view,kwargs={'id':id}), 'url':reverse(team_view,kwargs={'team_id':id}),
'name': t.name 'name': t.name
}, },
{ {
@@ -435,11 +437,12 @@ def team_requestmembership_view(request,teamid,userid):
r = getrequestrower(request,userid=userid) r = getrequestrower(request,userid=userid)
if t.manager.rower.rowerplan in ['plan','pro'] and r.rowerplan == 'basic': #if t.manager.rower.rowerplan in ['plan','pro'] and r.rowerplan == 'basic':
if not can_join_team(r.user,t):
messages.error(request, messages.error(request,
"You have to be on a paid plan (Pro or higher) to join this team. As a basic user you can only join teams managed by users on the Coach plan.") "You have to be on a paid plan (Pro or higher) to join this team. As a basic user you can only join teams managed by users on the Coach plan.")
url = reverse('paidplans') url = reverse('paidplans_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
res,text = teams.create_request(t,userid) res,text = teams.create_request(t,userid)
@@ -450,7 +453,7 @@ def team_requestmembership_view(request,teamid,userid):
url = reverse('team_view',kwargs={ url = reverse('team_view',kwargs={
'id':int(teamid), 'team_id':int(teamid),
}) })
@@ -571,7 +574,7 @@ def rower_invitations_view(request,code=None,message='',successmessage=''):
messages.info(request,text) messages.info(request,text)
teamid=res teamid=res
url = reverse(team_view,kwargs={ url = reverse(team_view,kwargs={
'id':teamid, 'team_id':teamid,
}) })
else: else:
messages.error(request,text) messages.error(request,text)
@@ -585,11 +588,10 @@ def rower_invitations_view(request,code=None,message='',successmessage=''):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@login_required() @login_required()
def team_edit_view(request,id=0): @permission_required('teams.change_team',fn=get_team_by_pk,raise_exception=True)
try: def team_edit_view(request, team_id=0):
t = Team.objects.get(id=id) t = get_object_or_404(Team,pk=team_id)
except Team.DoesNotExist:
raise Http404("Team does not exist")
if request.method == 'POST': if request.method == 'POST':
teamcreateform = TeamForm(request.POST,instance=t) teamcreateform = TeamForm(request.POST,instance=t)
@@ -609,7 +611,7 @@ def team_edit_view(request,id=0):
url = reverse(team_view, url = reverse(team_view,
kwargs={ kwargs={
'id':int(id), 'team_id':int(team_id),
} }
) )
@@ -627,11 +629,11 @@ def team_edit_view(request,id=0):
'name': 'Groups' 'name': 'Groups'
}, },
{ {
'url':reverse(team_view,kwargs={'id':id}), 'url':reverse(team_view,kwargs={'team_id':team_id}),
'name': t.name 'name': t.name
}, },
{ {
'url':reverse(team_edit_view,kwargs={'id':id}), 'url':reverse(team_edit_view,kwargs={'team_id':team_id}),
'name': 'Edit' 'name': 'Edit'
} }
] ]
@@ -648,19 +650,11 @@ def team_edit_view(request,id=0):
'team':t, 'team':t,
}) })
#@user_passes_test(cancreateteam,login_url="/rowers/paidplans",redirect_field_name=None)
@login_required() @login_required()
@user_passes_test(can_add_team,login_url="/rowers/paidplans",redirect_field_name=None)
def team_create_view(request): def team_create_view(request):
r = getrequestrower(request) r = getrequestrower(request)
if r.rowerplan == 'basic':
if r.protrialexpires < timezone.now().date() and r.plantrialexpires < timezone.now().date():
messages.error(request,"You must upgrade to Pro or higher to create teams/training groups")
url = reverse('paidplans')
return HttpResponseRedirect(url)
if request.method == 'POST': if request.method == 'POST':
teamcreateform = TeamForm(request.POST) teamcreateform = TeamForm(request.POST)
if teamcreateform.is_valid(): if teamcreateform.is_valid():
@@ -675,7 +669,7 @@ def team_create_view(request):
if not res: if not res:
messages.error(request,message) messages.error(request,message)
url = reverse('paidplans') url = reverse('paidplans_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
url = reverse('rower_teams_view') url = reverse('rower_teams_view')
@@ -708,15 +702,11 @@ def team_create_view(request):
'breadcrumbs':breadcrumbs, 'breadcrumbs':breadcrumbs,
}) })
@login_required() #@login_required()
def team_deleteconfirm_view(request,id): @permission_required('teams.delete_team',fn=get_team_by_pk,raise_exception=True)
def team_deleteconfirm_view(request,team_id):
r = getrower(request.user) r = getrower(request.user)
try: t = get_object_or_404(Team,pk=team_id)
t = Team.objects.get(id=id)
except Team.DoesNotExist:
raise Http404("This team doesn't exist")
if t.manager != request.user:
raise PermissionDenied("You are not allowed to delete this team")
myteams, memberteams, otherteams = get_teams(request) myteams, memberteams, otherteams = get_teams(request)
@@ -726,11 +716,11 @@ def team_deleteconfirm_view(request,id):
'name': 'Groups' 'name': 'Groups'
}, },
{ {
'url':reverse(team_view,kwargs={'id':id}), 'url':reverse(team_view,kwargs={'team_id':team_id}),
'name': t.name 'name': t.name
}, },
{ {
'url':reverse(team_deleteconfirm_view,kwargs={'id':id}), 'url':reverse(team_deleteconfirm_view,kwargs={'team_id':team_id}),
'name': 'Leave' 'name': 'Leave'
} }
] ]
@@ -745,14 +735,10 @@ def team_deleteconfirm_view(request,id):
}) })
@login_required() @login_required()
def team_delete_view(request,id): @permission_required('teams.delete_team',fn=get_team_by_pk,raise_exception=True)
def team_delete_view(request,team_id):
r = getrower(request.user) r = getrower(request.user)
try: t = get_object_or_404(Team,pk=team_id)
t = Team.objects.get(id=id)
except Team.DoesNotExist:
raise Http404("This team doesn't exist")
if t.manager != request.user:
raise PermissionDenied("You are not allowed to delete this team")
teams.remove_team(t.id) teams.remove_team(t.id)
@@ -761,14 +747,10 @@ def team_delete_view(request,id):
return response return response
@login_required() @login_required()
def team_members_stats_view(request,id): @permission_required('teams.change_team',fn=get_team_by_pk,raise_exception=True)
def team_members_stats_view(request,team_id):
r = getrower(request.user) r = getrower(request.user)
try: t = get_object_or_404(Team,pk=team_id)
t = Team.objects.get(id=id)
except Team.DoesNotExist:
raise Http404("This team doesn't exist")
if t.manager != request.user:
raise PermissionDenied("You are not allowed to see this page")
members = Rower.objects.filter(team=t).order_by("user__last_name","user__first_name") members = Rower.objects.filter(team=t).order_by("user__last_name","user__first_name")
@@ -782,11 +764,11 @@ def team_members_stats_view(request,id):
'name': 'Groups' 'name': 'Groups'
}, },
{ {
'url':reverse(team_view,kwargs={'id':id}), 'url':reverse(team_view,kwargs={'team_id':team_id}),
'name': t.name 'name': t.name
}, },
{ {
'url':reverse(team_members_stats_view,kwargs={'id':id}), 'url':reverse(team_members_stats_view,kwargs={'team_id':team_id}),
'name': 'Members Stats' 'name': 'Members Stats'
} }
] ]
+120 -2
View File
@@ -5,6 +5,120 @@ from __future__ import unicode_literals
from rowers.views.statements import * from rowers.views.statements import *
@login_required()
def deactivate_user(request):
pk = request.user.id
user = User.objects.get(pk=pk)
user_form = DeactivateUserForm(instance=user)
if request.user.is_authenticated and request.user.id == user.id:
if request.method == "POST":
user_form = DeactivateUserForm(request.POST, instance=user)
if user_form.is_valid():
if not user_form.cleaned_data['is_active']:
r = Rower.objects.get(user=user)
if r.paidplan is not None and r.paidplan.paymentprocessor == 'braintree':
try:
subscriptions = braintreestuff.find_subscriptions(r)
for subscription in subscriptions:
success, themessages,errormessages = braintreestuff.cancel_subscription(r,id)
for message in themessages:
messages.info(request,message)
except ProcessorCustomerError:
pass
r.paidplan = None
r.teamplanexpires = timezone.now()
r.planexpires = timezone.now()
r.clubsize = 0
r.rowerplan = 'basic'
r.save()
deactivate_user = user_form.save(commit=False)
user.is_active = False
user.save()
deactivate_user.save()
# url = reverse(auth_views.logout_then_login)
url = '/logout/?next=/login'
return HttpResponseRedirect(url)
return render(request, "userprofile_deactivate.html", {
"user_form": user_form,
})
else:
raise PermissionDenied
@login_required()
def user_gdpr_optin(request):
r = getrower(request.user)
r.gdproptin = False
r.gdproptindate = None
r.save()
nexturl = request.GET.get('next','/rowers/list-workouts/')
if r.gdproptin:
return HttpResponseRedirect(nexturl)
return render(request,'gdpr_optin.html',{
"next": nexturl
})
@login_required()
def user_gdpr_confirm(request):
r = getrower(request.user)
r.gdproptin = True
r.gdproptindate = timezone.now()
r.save()
nexturl = request.GET.get('next','/rowers/list-workouts/')
return HttpResponseRedirect(nexturl)
@login_required()
def remove_user(request):
pk = request.user.id
user = User.objects.get(pk=pk)
user_form = DeleteUserForm(instance=user)
if request.user.is_authenticated and request.user.id == user.id:
if request.method == "POST":
user_form = DeleteUserForm(request.POST,instance=user)
if user_form.is_valid():
cd = user_form.cleaned_data
name = user.first_name+' '+user.last_name
email = user.email
r = Rower.objects.get(user=user)
if r.paidplan is not None and r.paidplan.paymentprocessor == 'braintree':
try:
subscriptions = braintreestuff.find_subscriptions(r)
for subscription in subscriptions:
success, themessages,errormessages = braintreestuff.cancel_subscription(r,id)
for message in themessages:
messages.info(request,message)
except:
pass
if cd['delete_user']:
# delete workouts
Workout.objects.filter(user=user.rower).delete()
# delete planned sessions
PlannedSession.objects.filter(manager=user).delete()
user.delete()
res = myqueue(queuehigh,
handle_sendemail_userdeleted,
name, email)
url = '/logout/?next=/login'
# url = reverse(auth_views.logout_then_login)
return HttpResponseRedirect(url)
return render(request, "userprofile_delete.html", {
"user_form": user_form,
})
else:
raise PermissionDenied
@login_required() @login_required()
def survey(request): def survey(request):
@@ -34,7 +148,7 @@ def survey(request):
def start_trial_view(request): def start_trial_view(request):
r = getrower(request.user) r = getrower(request.user)
if r.protrialexpires > datetime.date(1970,1,1): if not can_start_trial(request.user):
messages.error(request,'You do not qualify for a trial') messages.error(request,'You do not qualify for a trial')
url = '/rowers/paidplans' url = '/rowers/paidplans'
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@@ -68,7 +182,7 @@ def start_trial_view(request):
def start_plantrial_view(request): def start_plantrial_view(request):
r = getrower(request.user) r = getrower(request.user)
if r.plantrialexpires > datetime.date(1970,1,1): if not can_start_plantrial(request.user):
messages.error(request,'You do not qualify for a trial') messages.error(request,'You do not qualify for a trial')
url = '/rowers/paidplans' url = '/rowers/paidplans'
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@@ -101,6 +215,7 @@ def start_plantrial_view(request):
# Page where user can manage his favorite charts # Page where user can manage his favorite charts
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def rower_favoritecharts_view(request,userid=0): def rower_favoritecharts_view(request,userid=0):
message = '' message = ''
successmessage = '' successmessage = ''
@@ -168,6 +283,7 @@ def rower_favoritecharts_view(request,userid=0):
# page where user sets his export settings # page where user sets his export settings
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def rower_exportsettings_view(request,userid=0): def rower_exportsettings_view(request,userid=0):
r = getrequestrower(request,userid=userid) r = getrequestrower(request,userid=userid)
if request.method == 'POST': if request.method == 'POST':
@@ -204,6 +320,7 @@ def rower_exportsettings_view(request,userid=0):
# Page where user can set his details # Page where user can set his details
# Add email address to form so user can change his email address # Add email address to form so user can change his email address
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def rower_edit_view(request,rowerid=0,userid=0,message=""): def rower_edit_view(request,rowerid=0,userid=0,message=""):
r = getrequestrower(request,rowerid=rowerid,userid=userid,notpermanent=True) r = getrequestrower(request,rowerid=rowerid,userid=userid,notpermanent=True)
@@ -299,6 +416,7 @@ def rower_edit_view(request,rowerid=0,userid=0,message=""):
# Page where user can set his details # Page where user can set his details
# Add email address to form so user can change his email address # Add email address to form so user can change his email address
@login_required() @login_required()
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
def rower_prefs_view(request,userid=0,message=""): def rower_prefs_view(request,userid=0,message=""):
r = getrequestrower(request,userid=userid,notpermanent=True) r = getrequestrower(request,userid=userid,notpermanent=True)
File diff suppressed because it is too large Load Diff
+19 -2
View File
@@ -4,6 +4,7 @@ from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
import requests import requests
from requests.exceptions import ConnectionError
import json import json
from lxml import objectify,etree from lxml import objectify,etree
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@@ -32,7 +33,10 @@ def get_weather_data(long,lat,unixtime):
url = "https://api.darksky.net/forecast/"+FORECAST_IO_KEY+"/" url = "https://api.darksky.net/forecast/"+FORECAST_IO_KEY+"/"
url += str(long)+","+str(lat)+","+str(unixtime) url += str(long)+","+str(lat)+","+str(unixtime)
s = requests.get(url) try:
s = requests.get(url)
except ConnectionError:
return 0
if s.ok: if s.ok:
return s.json() return s.json()
@@ -58,7 +62,11 @@ def get_metar_data(airportcode,unixtime):
if s.ok: if s.ok:
doc = etree.fromstring(s.content) try:
doc = etree.fromstring(s.content)
except AttributeError:
message = 'METAR data content error'
return [0,0,message,'',0]
lengte = len(doc.xpath('data/METAR/station_id')) lengte = len(doc.xpath('data/METAR/station_id'))
idnr = int(lengte/2) idnr = int(lengte/2)
try: try:
@@ -98,6 +106,8 @@ def get_metar_data(airportcode,unixtime):
# Get wind data (and translate from knots to m/s) # Get wind data (and translate from knots to m/s)
def get_wind_data(lat,long,unixtime): def get_wind_data(lat,long,unixtime):
data = get_weather_data(lat,long,unixtime) data = get_weather_data(lat,long,unixtime)
summary = ''
temperature = 20
if data: if data:
try: try:
# we are getting wind in mph # we are getting wind in mph
@@ -107,11 +117,13 @@ def get_wind_data(lat,long,unixtime):
windspeed = 0 windspeed = 0
windbearing = 0 windbearing = 0
try: try:
airports = data['flags']['madis-stations'] airports = data['flags']['madis-stations']
except KeyError: except KeyError:
airports = ['unknown'] airports = ['unknown']
try: try:
temperature = data['currently']['temperature'] temperature = data['currently']['temperature']
# Temp is given in Fahrenheit, so convert to Celsius for Europeans # Temp is given in Fahrenheit, so convert to Celsius for Europeans
@@ -121,6 +133,7 @@ def get_wind_data(lat,long,unixtime):
temperature = 'unknown' temperature = 'unknown'
temperaturec = 'unknown' temperaturec = 'unknown'
try: try:
summary = data['currently']['summary'] summary = data['currently']['summary']
except KeyError: except KeyError:
@@ -128,6 +141,10 @@ def get_wind_data(lat,long,unixtime):
else: else:
windspeed = 0 windspeed = 0
windbearing = 0 windbearing = 0
summary = 'unknown'
airports = ['unknown']
temparature = 'unknown'
temperaturec = 'unknown'
message = 'Not able to get weather data' message = 'Not able to get weather data'
# apply Hellman's coefficient for neutral air above human # apply Hellman's coefficient for neutral air above human
+2
View File
@@ -73,11 +73,13 @@ INSTALLED_APPS = [
'tz_detect', 'tz_detect',
'django_social_share', 'django_social_share',
'django_countries', 'django_countries',
'rules',
] ]
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
'oauth2_provider.backends.OAuth2Backend', 'oauth2_provider.backends.OAuth2Backend',
# Uncomment following if you want to access the admin # Uncomment following if you want to access the admin
'rules.permissions.ObjectPermissionBackend',
'django.contrib.auth.backends.ModelBackend', 'django.contrib.auth.backends.ModelBackend',
) )
+1 -1
View File
@@ -66,7 +66,7 @@ urlpatterns += [
{'next_page': '/'}, {'next_page': '/'},
name='logout',), name='logout',),
re_path(r'^rowers/',include('rowers.urls')), re_path(r'^rowers/',include('rowers.urls')),
re_path(r'^survey/',include('survey.urls')), # re_path(r'^survey/',include('survey.urls')),
# re_path(r'^cvkbrno/',include('cvkbrno.urls')), # re_path(r'^cvkbrno/',include('cvkbrno.urls')),
# re_path(r'^admin/rq/',include('django_rq_dashboard.urls')), # re_path(r'^admin/rq/',include('django_rq_dashboard.urls')),
re_path(r'^call\_back',rowersviews.rower_process_callback), re_path(r'^call\_back',rowersviews.rower_process_callback),
+1 -1
View File
@@ -246,7 +246,7 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if user.rower.protrialexpires and user.rower.protrialexpires|is_future_date %} {% if user.rower.protrialexpires and user.rower.protrialexpires|is_future_date %}
{% if user.rower.plantrialexpires and user.rower.rowerplan != 'plan' %} {% if user.rower.plantrialexpires and user.rower.plantrialexpires|is_future_date and user.rower.rowerplan != 'plan' %}
<li class="grid_4"> <li class="grid_4">
<p class="successmessage"> <p class="successmessage">
{{ user.rower.protrialexpires|date_dif|ddays }} days left of your Self-Coach trial - Would you like to <a href="/rowers/paidplans/">upgrade now?</a> {{ user.rower.protrialexpires|date_dif|ddays }} days left of your Self-Coach trial - Would you like to <a href="/rowers/paidplans/">upgrade now?</a>