diff --git a/rowers/admin.py b/rowers/admin.py index 5be9aa7f..4c2c861c 100644 --- a/rowers/admin.py +++ b/rowers/admin.py @@ -7,7 +7,7 @@ from .models import ( Team,TeamInvite,TeamRequest, WorkoutComment,C2WorldClassAgePerformance,PlannedSession, GeoCourse,GeoPolygon,GeoPoint,VirtualRace,VirtualRaceResult, - PaidPlan + PaidPlan,IndoorVirtualRaceResult, ) # Register your models here so you can use them in the Admin module @@ -123,6 +123,10 @@ class VirtualRaceResultAdmin(admin.ModelAdmin): list_display = ('race','userid','username','boattype','age','weightcategory') search_fields = ['race__name','username'] +class IndoorVirtualRaceResultAdmin(admin.ModelAdmin): + list_display = ('race','userid','username','boatclass','age','weightcategory') + search_fields = ['race__name','username'] + class PaidPlanAdmin(admin.ModelAdmin): list_display = ('name','shortname','price','paymenttype','paymentprocessor','clubsize','external_id') @@ -142,4 +146,5 @@ admin.site.register(PlannedSession,PlannedSessionAdmin) admin.site.register(GeoCourse, GeoCourseAdmin) admin.site.register(VirtualRace, VirtualRaceAdmin) admin.site.register(VirtualRaceResult, VirtualRaceResultAdmin) +admin.site.register(IndoorVirtualRaceResult, IndoorVirtualRaceResultAdmin) admin.site.register(PaidPlan,PaidPlanAdmin) diff --git a/rowers/c2stuff.py b/rowers/c2stuff.py index dd345736..8a82b6a5 100644 --- a/rowers/c2stuff.py +++ b/rowers/c2stuff.py @@ -572,8 +572,10 @@ def get_workout(user,c2id): if 'workout' in data: if 'splits' in data['workout']: splitdata = data['workout']['splits'] - if 'intervals' in data['workout']: + elif 'intervals' in data['workout']: splitdata = data['workout']['intervals'] + else: + splitdata = None # Check if workout has stroke data, and get the stroke data diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 45914354..db7cd1fe 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -36,9 +36,12 @@ from rowingdata import ( SpeedCoach2Parser, FITParser, fitsummarydata, RitmoTimeParser,KinoMapParser, make_cumvalues,cumcpdata,ExcelTemplate, - summarydata, get_file_type, + summarydata, get_file_type, ) +from rowingdata.csvparsers import HumonParser + + from rowers.metrics import axes,calc_trimp,rowingmetrics from rowers.models import strokedatafields @@ -1160,6 +1163,7 @@ parsers = { 'ergstick': ErgStickParser, 'fit': FITParser, 'ergdata': ErgDataParser, + 'humon': HumonParser, } def parsenonpainsled(fileformat,f2,summary): diff --git a/rowers/templates/list_workouts.html b/rowers/templates/list_workouts.html index 73c5e5f3..7a42b7c2 100644 --- a/rowers/templates/list_workouts.html +++ b/rowers/templates/list_workouts.html @@ -221,9 +221,32 @@ {% endif %} -
-
- Also check out our instructional videos at - http://rowsandall.com/rowers/videos -
This website is a labor of love "by rowers, for rowers".
diff --git a/rowers/tests/c2jsonintervaldata.txt b/rowers/tests/c2jsonintervaldata.txt
new file mode 100644
index 00000000..3c37d306
--- /dev/null
+++ b/rowers/tests/c2jsonintervaldata.txt
@@ -0,0 +1,64 @@
+{
+ "date": "2017-05-01 14:33:00",
+ "timezone": "Australia/Melbourne",
+ "workout_type": "VariableInterval",
+ "type": "rower",
+ "weight_class": "H",
+ "time": 16800,
+ "distance": 6721,
+ "rest_distance": 236,
+ "rest_time": 2700,
+ "calories_total": 427,
+ "drag_factor": 175,
+ "stroke_count": 996,
+ "stroke_rate": 33,
+ "workout": {
+ "intervals": [
+ {
+ "type": "time",
+ "time": 2400,
+ "distance": 1011,
+ "rest_time": 600,
+ "rest_distance": 43,
+ "stroke_rate": 35,
+ "calories_total": 68
+ },
+ {
+ "type": "time",
+ "time": 3000,
+ "distance": 1229,
+ "rest_time": 600,
+ "rest_distance": 59,
+ "stroke_rate": 34,
+ "calories_total": 80
+ },
+ {
+ "type": "time",
+ "time": 3000,
+ "distance": 1190,
+ "rest_time": 600,
+ "rest_distance": 59,
+ "stroke_rate": 33,
+ "calories_total": 75
+ },
+ {
+ "type": "time",
+ "time": 2400,
+ "distance": 971,
+ "rest_time": 750,
+ "rest_distance": 44,
+ "stroke_rate": 34,
+ "calories_total": 62
+ },
+ {
+ "type": "time",
+ "time": 6000,
+ "distance": 2320,
+ "rest_time": 150,
+ "rest_distance": 31,
+ "stroke_rate": 32,
+ "calories_total": 142
+ }
+ ]
+ }
+}
diff --git a/rowers/tests/c2jsonsplitdata.txt b/rowers/tests/c2jsonsplitdata.txt
new file mode 100644
index 00000000..ff6f9815
--- /dev/null
+++ b/rowers/tests/c2jsonsplitdata.txt
@@ -0,0 +1,47 @@
+{
+ "date": "2017-05-16 17:24:00",
+ "timezone": "US/Pacific",
+ "workout_type": "FixedTimeSplits",
+ "type": "rower",
+ "weight_class": "H",
+ "time": 6000,
+ "distance": 1789,
+ "stroke_count": 314,
+ "drag_factor": 134,
+ "stroke_rate": 31,
+ "calories_total": 90,
+ "workout": {
+ "splits": [
+ {
+ "time": 1200,
+ "calories_total": 18,
+ "stroke_rate": 33,
+ "distance": 354
+ },
+ {
+ "time": 1200,
+ "calories_total": 18,
+ "stroke_rate": 31,
+ "distance": 355
+ },
+ {
+ "time": 1200,
+ "calories_total": 18,
+ "stroke_rate": 32,
+ "distance": 357
+ },
+ {
+ "time": 1200,
+ "calories_total": 18,
+ "stroke_rate": 31,
+ "distance": 363
+ },
+ {
+ "time": 1200,
+ "calories_total": 18,
+ "stroke_rate": 30,
+ "distance": 361
+ }
+ ]
+ }
+}
diff --git a/rowers/tests/test_plans.py b/rowers/tests/test_plans.py
index de2fce3d..1a23bd23 100644
--- a/rowers/tests/test_plans.py
+++ b/rowers/tests/test_plans.py
@@ -1,3 +1,319 @@
#from __future__ import print_function
from statements import *
nu = datetime.datetime.now()
+
+import rowers.plannedsessions as plannedsessions
+
+class TrainingPlanTest(TestCase):
+ def setUp(self):
+ self.u = UserFactory()
+
+ self.r = Rower.objects.create(user=self.u,
+ birthdate=faker.profile()['birthdate'],
+ gdproptin=True,
+ gdproptindate=timezone.now(),
+ rowerplan='coach')
+
+ self.c = Client()
+ self.user_workouts = WorkoutFactory.create_batch(5, user=self.r)
+ self.factory = RequestFactory()
+ self.password = faker.word()
+ self.u.set_password(self.password)
+ self.u.save()
+
+ def tearDown(self):
+ for workout in self.user_workouts:
+ try:
+ os.remove(workout.csvfilename)
+ except (IOError, WindowsError):
+ pass
+
+ def test_createplan(self):
+ login = self.c.login(username=self.u.username, password=self.password)
+ self.assertTrue(login)
+
+ url = '/rowers/createplan/'
+ response = self.c.get(url)
+ self.assertEqual(response.status_code,200)
+
+ # add target
+
+ targetdate = (nu+datetime.timedelta(days=300))
+
+ form_data = {
+ 'name': faker.word(),
+ 'date': targetdate.strftime("%Y-%m-%d"),
+ 'notes': faker.text(),
+ }
+
+ targetform = TrainingTargetForm(form_data,user=self.u)
+ self.assertTrue(targetform.is_valid())
+
+ response = self.c.post(url,form_data)
+ self.assertEqual(response.status_code,200)
+
+ form_data = {
+ 'name': faker.word(),
+ 'target': '1',
+ 'startdate': nu.strftime("%Y-%m-%d"),
+ 'enddate': targetdate.strftime("%Y-%m-%d"),
+ 'active': True,
+ }
+
+ newplanform = TrainingPlanForm(form_data,user=self.u)
+ self.assertTrue(newplanform.is_valid())
+
+ response = self.c.post(url,form_data)
+ self.assertEqual(response.status_code,200)
+
+ urlplan = '/rowers/plan/1/'
+ response = self.c.get(urlplan)
+ self.assertEqual(response.status_code,200)
+
+ html = BeautifulSoup(response.content,'html.parser')
+ urls = [a['href'] for a in html.find_all('a')]
+
+ for url in urls:
+ if 'macrocycle' in url and 'delete' not in url:
+ macrourl = url
+ print macrourl
+ response = self.c.get(macrourl)
+ self.assertEqual(response.status_code,200)
+
+ form_data = {
+ 'name':faker.word(),
+ 'startdate':nu.strftime("%Y-%m-%d"),
+ 'enddate':targetdate.strftime("%Y-%m-%d"),
+ 'notes':faker.text(),
+ }
+
+ macroform = TrainingMacroCycleForm(form_data)
+ self.assertTrue(macroform.is_valid())
+
+ response = self.c.post(macrourl,form_data,follow=True)
+ self.assertEqual(response.status_code,200)
+ self.assertRedirects(response,
+ expected_url='/rowers/plan/1/macro/3/',
+ status_code=302,target_status_code=200)
+
+ response = self.c.get(urlplan)
+ self.assertEqual(response.status_code,200)
+
+ html = BeautifulSoup(response.content,'html.parser')
+ urls = [a['href'] for a in html.find_all('a')]
+
+ for url in urls:
+ if 'planbymonths' in url:
+ print url
+ response = self.c.get(url,follow=True)
+ self.assertEqual(response.status_code,200)
+
+ response = self.c.get('/rowers/plan/1/macro/3/')
+ self.assertEqual(response.status_code,200)
+
+ html = BeautifulSoup(response.content,'html.parser')
+ urls = [a['href'] for a in html.find_all('a')]
+
+ tested = False
+
+ for url in urls:
+ if 'planbyweeks' in url and not tested:
+ print url
+ response = self.c.get(url,follow=True)
+ self.assertEqual(response.status_code,200)
+ tested = True
+
+ # add test for creating new sessions
+ def sessions_create(self):
+ login = self.c.login(username=self.u.username, password=self.password)
+ self.assertTrue(login)
+
+ url = '/rowers/sessions/create/'
+
+ startdate = nu.date()
+ enddate = (nu+datetime.timedelta(days=3)).date()
+ preferreddate = startdate
+
+ response = self.c.get(url)
+ self.assertEqual(response.status_code,200)
+
+ post_data = {
+ 'comment': faker.text(),
+ 'criterium': 'none',
+ 'enddate': enddate.strftime("%Y-%m-%d"),
+ 'preferreddate': preferreddate.strftime("%Y-%m-%d"),
+ 'startdate': startdate.strftime("%Y-%m-%d"),
+ 'sessionmode':'time',
+ 'sessiontype':'session',
+ 'sessionunit':'min',
+ 'sessionvalue': '60',
+ 'name': faker.word(),
+ }
+
+ form = PlannedSessionForm(post_data)
+ self.assertEqual(form.is_valid())
+
+ response = self.c.post(url,post_data)
+ self.assertEqual(response.status_code,200)
+
+
+
+class SessionLinkTest(TestCase):
+ def setUp(self):
+ self.u = UserFactory()
+
+ self.r = Rower.objects.create(user=self.u,
+ birthdate=faker.profile()['birthdate'],
+ gdproptin=True,
+ gdproptindate=timezone.now(),
+ rowerplan='coach')
+
+ self.c = Client()
+ self.user_workouts = WorkoutFactory.create_batch(5, user=self.r)
+ self.factory = RequestFactory()
+ self.password = faker.word()
+ self.u.set_password(self.password)
+ self.u.save()
+
+ for w in self.user_workouts:
+ startdatetime = w.startdatetime
+
+ startdate = (startdatetime-datetime.timedelta(days=1)).date()
+ enddate = (startdatetime+datetime.timedelta(days=1)).date()
+ preferreddate = startdatetime.date()
+
+ ps = SessionFactory(startdate=startdate,enddate=enddate,
+ sessiontype='session',
+ sessionmode = 'time',
+ criterium = 'none',
+ sessionvalue = 60,
+ sessionunit='min',
+ preferreddate=preferreddate,
+ manager=self.u,
+ )
+
+
+ ps.save()
+ result = plannedsessions.add_rower_session(self.r,ps)
+
+
+ def tearDown(self):
+ for workout in self.user_workouts:
+ try:
+ os.remove(workout.csvfilename)
+ except (IOError, WindowsError):
+ pass
+
+ def test_plannedsessions(self):
+ login = self.c.login(username=self.u.username, password=self.password)
+ self.assertTrue(login)
+
+
+ w = self.user_workouts[0]
+ startdatetime = w.startdatetime
+
+ startdate = (startdatetime-datetime.timedelta(days=1)).date()
+ enddate = (startdatetime+datetime.timedelta(days=1)).date()
+
+ url = '/rowers/sessions/'
+ response = self.c.get(url)
+ self.assertEqual(response.status_code,200)
+
+ post_data = {
+ 'startdate':startdate.strftime("%Y-%m-%d"),
+ 'enddate': enddate.strftime("%Y-%m-%d"),
+ }
+
+ response = self.c.post(url,post_data)
+ self.assertEqual(response.status_code,200)
+
+ url2 = '/rowers/sessions/manage/'
+ response = self.c.post(url2,post_data)
+ self.assertEqual(response.status_code,200)
+
+ pss = PlannedSession.objects.filter(startdate=startdate,enddate=enddate)
+
+ self.assertTrue(len(pss)>0)
+
+ ps = pss[0]
+
+ post_data = {
+ 'plannedsession': str(ps.id),
+ 'workouts':[str(w.id)],
+ }
+
+ plannedsessionstuple = []
+ for ps in pss:
+ sessiontpl = (ps.id,ps.__unicode__())
+ plannedsessionstuple.append(sessiontpl)
+
+ plannedsessionstuple = tuple(plannedsessionstuple)
+
+
+ workoutdata = {}
+ workoutdata['initial'] = []
+
+ choices = []
+ for w in self.user_workouts:
+ wtpl = (w.id,w.__unicode__())
+ choices.append(wtpl)
+
+ workoutdata['choices'] = tuple(choices)
+
+ form = PlannedSessionSelectForm(plannedsessionstuple,post_data)
+
+ self.assertTrue(form.is_valid())
+
+ form = WorkoutSessionSelectForm(workoutdata,post_data)
+ self.assertTrue(form.is_valid())
+
+ response = self.c.post(url2,post_data)
+ self.assertEqual(response.status_code,200)
+
+
+ urlsession = '/rowers/sessions/{id}/'.format(id=ps.id)
+
+ response = self.c.get(urlsession)
+ self.assertEqual(response.status_code,200)
+
+ url = '/rowers/sessions/coach/'
+ response = self.c.get(url)
+ self.assertEqual(response.status_code,200)
+
+ def test_multiplesessions(self):
+ login = self.c.login(username=self.u.username, password=self.password)
+ self.assertTrue(login)
+
+ pss = PlannedSession.objects.all()
+
+ earliestdate = min([ps.startdate for ps in pss])
+ latestdate = max([ps.enddate for ps in pss])
+
+ url = '/rowers/sessions/multicreate/?when={be}/{en}'.format(
+ be = earliestdate.strftime("%Y-%m-%d"),
+ en = latestdate.strftime("%Y-%m-%d"),
+ )
+
+ response = self.c.get(url)
+ self.assertEqual(response.status_code,200)
+
+ url = '/rowers/sessions/multicreate/user/{userid}/extra/1/?when={be}/{en}'.format(
+ be = earliestdate.strftime("%Y-%m-%d"),
+ en = latestdate.strftime("%Y-%m-%d"),
+ userid = self.u.id
+ )
+
+ response = self.c.get(url)
+ self.assertEqual(response.status_code,200)
+
+ url = '/rowers/sessions/multiclone/user/{userid}/?when={be}/{en}'.format(
+ be = earliestdate.strftime("%Y-%m-%d"),
+ en = latestdate.strftime("%Y-%m-%d"),
+ userid = self.u.id
+ )
+
+ response = self.c.get(url)
+ self.assertEqual(response.status_code,200)
+
+
+
diff --git a/rowers/tests/test_rowerplans.py b/rowers/tests/test_rowerplans.py
new file mode 100644
index 00000000..e9be5c6d
--- /dev/null
+++ b/rowers/tests/test_rowerplans.py
@@ -0,0 +1,64 @@
+from statements import *
+nu = datetime.datetime.now()
+
+from rowers.views import hasplannedsessions,iscoachmember,ispromember
+
+class TrialsTest(TestCase):
+ def setUp(self):
+ self.u = UserFactory()
+
+ self.r = Rower.objects.create(user=self.u,
+ birthdate=faker.profile()['birthdate'],
+ gdproptin=True,
+ gdproptindate=timezone.now(),
+ rowerplan='basic')
+
+ self.c = Client()
+ self.user_workouts = WorkoutFactory.create_batch(5, user=self.r)
+ self.factory = RequestFactory()
+ self.password = faker.word()
+ self.u.set_password(self.password)
+ self.u.save()
+
+ def tearDown(self):
+ for workout in self.user_workouts:
+ try:
+ os.remove(workout.csvfilename)
+ except (IOError, WindowsError):
+ pass
+
+ def test_basictrial(self):
+ self.assertEqual(hasplannedsessions(self.u),False)
+ self.assertEqual(iscoachmember(self.u),False)
+ self.assertEqual(ispromember(self.u),False)
+
+ self.r.protrialexpires = (nu+datetime.timedelta(days=10)).date()
+ self.r.save()
+
+ self.assertEqual(hasplannedsessions(self.u),False)
+ self.assertEqual(iscoachmember(self.u),False)
+ self.assertEqual(ispromember(self.u),True)
+
+ self.r.plantrialexpires = (nu+datetime.timedelta(days=10)).date()
+ self.r.save()
+
+ self.assertEqual(hasplannedsessions(self.u),True)
+ self.assertEqual(iscoachmember(self.u),False)
+ self.assertEqual(ispromember(self.u),True)
+
+ def test_protrial(self):
+ self.r.rowerplan = 'pro'
+ self.r.save()
+ self.assertEqual(hasplannedsessions(self.u),False)
+ self.assertEqual(iscoachmember(self.u),False)
+ self.assertEqual(ispromember(self.u),True)
+
+ self.r.plantrialexpires = (nu+datetime.timedelta(days=10)).date()
+ self.r.save()
+
+ self.assertEqual(hasplannedsessions(self.u),True)
+ self.assertEqual(iscoachmember(self.u),False)
+ self.assertEqual(ispromember(self.u),True)
+
+
+
diff --git a/rowers/tests/testdata/testdata.tcx b/rowers/tests/testdata/testdata.tcx
index 28c4ef15..144ed946 100644
--- a/rowers/tests/testdata/testdata.tcx
+++ b/rowers/tests/testdata/testdata.tcx
@@ -2502,7 +2502,11 @@
+<<<<<<< HEAD