Private
Public Access
1
0

Merge branch 'release/v6.45'

This commit is contained in:
Sander Roosendaal
2018-04-20 14:03:58 +02:00
38 changed files with 8295 additions and 113 deletions
+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?><gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" creator="Oregon 400t" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd"><metadata><link href="http://www.garmin.com"><text>Garmin International</text></link><time>2018-03-17T12:59:13</time></metadata><trk><name>Export by rowingdata</name>
</trk></gpx>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TrainingCenterDatabase xmlns="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2">
<Activities>
<Activity Sport="Other">
<Id>2015-03-28T20:45:15.000Z</Id>
<Creator xsi:type="Device_t" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>Empty File</Name>
<UnitId>0</UnitId>
<ProductID>0</ProductID>
</Creator>
</Activity>
</Activities>
</TrainingCenterDatabase>
+4 -1
View File
@@ -6,7 +6,7 @@ from .models import (
Rower, Workout,GraphImage,FavoriteChart,SiteAnnouncement, Rower, Workout,GraphImage,FavoriteChart,SiteAnnouncement,
Team,TeamInvite,TeamRequest, Team,TeamInvite,TeamRequest,
WorkoutComment,C2WorldClassAgePerformance,PlannedSession, WorkoutComment,C2WorldClassAgePerformance,PlannedSession,
GeoCourse,GeoPolygon,GeoPoint, GeoCourse,GeoPolygon,GeoPoint,VirtualRace,
) )
# Register your models here so you can use them in the Admin module # Register your models here so you can use them in the Admin module
@@ -60,6 +60,8 @@ class GeoCourseAdmin(admin.ModelAdmin):
inlines = (GeoPolygonInline,) inlines = (GeoPolygonInline,)
class VirtualRaceAdmin(admin.ModelAdmin):
list_display = ('manager','name','startdate','country')
admin.site.unregister(User) admin.site.unregister(User)
admin.site.register(User,UserAdmin) admin.site.register(User,UserAdmin)
@@ -75,3 +77,4 @@ admin.site.register(C2WorldClassAgePerformance,
C2WorldClassAgePerformanceAdmin) C2WorldClassAgePerformanceAdmin)
admin.site.register(PlannedSession,PlannedSessionAdmin) admin.site.register(PlannedSession,PlannedSessionAdmin)
admin.site.register(GeoCourse, GeoCourseAdmin) admin.site.register(GeoCourse, GeoCourseAdmin)
admin.site.register(VirtualRace, VirtualRaceAdmin)
+19
View File
@@ -16,6 +16,7 @@ import xml.etree.ElementTree as et
import pandas as pd import pandas as pd
import numpy as np import numpy as np
from timezonefinder import TimezoneFinder
import dataprep import dataprep
from rowers.utils import geo_distance from rowers.utils import geo_distance
@@ -38,6 +39,24 @@ class InvalidTrajectoryError(Exception):
def __str__(self): def __str__(self):
return repr(self.value) return repr(self.value)
def get_course_timezone(course):
polygons = GeoPolygon.objects.filter(course = course)
points = GeoPoint.objects.filter(polygon = polygons[0])
lat = points[0].latitude
lon = points[0].longitude
tf = TimezoneFinder()
try:
timezone_str = tf.timezone_at(lng=lon,lat=lat)
except ValueError:
timezone_str = 'UTC'
if timezone_str is None:
timezone_str = tf.closest_timezone_at(lng=lon,lat=lat)
if timezone_str is None:
timezone_str = 'UTC'
return timezone_str
def polygon_to_path(polygon): def polygon_to_path(polygon):
points = GeoPoint.objects.filter(polygon=polygon).order_by("order_in_poly") points = GeoPoint.objects.filter(polygon=polygon).order_by("order_in_poly")
+4 -29
View File
@@ -309,6 +309,8 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True,
# clean data remove zeros and negative values # clean data remove zeros and negative values
# bring metrics which have negative values to positive domain # bring metrics which have negative values to positive domain
if datadf.empty:
return datadf
try: try:
datadf['catch'] = -datadf['catch'] datadf['catch'] = -datadf['catch']
except KeyError: except KeyError:
@@ -807,34 +809,7 @@ def create_row_df(r,distance,duration,startdatetime,
return (id, message) return (id, message)
from utils import totaltime_sec_to_string
def totaltime_sec_to_string(totaltime):
hours = int(totaltime / 3600.)
if hours > 23:
message = 'Warning: The workout duration was longer than 23 hours. '
hours = 23
minutes = int((totaltime - 3600. * hours) / 60.)
if minutes > 59:
minutes = 59
if not message:
message = 'Warning: there is something wrong with the workout duration'
seconds = int(totaltime - 3600. * hours - 60. * minutes)
if seconds > 59:
seconds = 59
if not message:
message = 'Warning: there is something wrong with the workout duration'
tenths = int(10 * (totaltime - 3600. * hours - 60. * minutes - seconds))
if tenths > 9:
tenths = 9
if not message:
message = 'Warning: there is something wrong with the workout duration'
duration = "%s:%s:%s.%s" % (hours, minutes, seconds, tenths)
return duration
# Processes painsled CSV file to database # Processes painsled CSV file to database
def save_workout_database(f2, r, dosmooth=True, workouttype='rower', def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
@@ -1676,7 +1651,7 @@ def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True):
try: try:
f = row.df['TimeStamp (sec)'].diff().mean() f = row.df['TimeStamp (sec)'].diff().mean()
except AttributeError: except (AttributeError,KeyError) as e:
f = 0 f = 0
if f != 0 and not np.isnan(f): if f != 0 and not np.isnan(f):
+47
View File
@@ -695,6 +695,19 @@ class WorkoutSessionSelectForm(forms.Form):
widget = forms.CheckboxSelectMultiple, widget = forms.CheckboxSelectMultiple,
) )
class WorkoutRaceSelectForm(forms.Form):
def __init__(self, workoutdata, *args, **kwargs):
super(WorkoutRaceSelectForm, self).__init__(*args, **kwargs)
self.fields['workouts'] = forms.ChoiceField(
label='Workouts',
choices = workoutdata['choices'],
initial=workoutdata['initial'],
widget=forms.RadioSelect,
)
class PlannedSessionTeamForm(forms.Form): class PlannedSessionTeamForm(forms.Form):
team = forms.ModelMultipleChoiceField( team = forms.ModelMultipleChoiceField(
queryset=Team.objects.all(), queryset=Team.objects.all(),
@@ -717,4 +730,38 @@ class PlannedSessionTeamMemberForm(forms.Form):
self.fields['members'].queryset = thesession.rower.all() self.fields['members'].queryset = thesession.rower.all()
from rowers.models import VirtualRace,GeoCourse
def get_countries():
countries = VirtualRace.objects.order_by('country').values_list('country').distinct()
countries = tuple([(c[0],c[0]) for c in countries])
countries = countries+(('All','All'),)
return countries
class VirtualRaceSelectForm(forms.Form):
regattatypechoices = (
('upcoming','Upcoming Races'),
('ongoing','Ongoing Races'),
('previous','Previous Races'),
('my','My Races'),
('all','All Races'),
)
regattatype = forms.ChoiceField(
label='Type',
choices = regattatypechoices,
initial = 'upcoming',
)
country = forms.ChoiceField(
label='Country',
choices = get_countries()
)
def __init__(self, *args, **kwargs):
super(VirtualRaceSelectForm, self).__init__(*args, **kwargs)
self.fields['country'] = forms.ChoiceField(
choices = get_countries(),initial='All'
)
+7 -1
View File
@@ -774,7 +774,10 @@ def interactive_histoall(theworkouts):
if rowdata.empty: if rowdata.empty:
return "","No Valid Data Available","","" return "","No Valid Data Available","",""
try:
histopwr = rowdata['power'].values histopwr = rowdata['power'].values
except KeyError:
return "","No power data","",""
if len(histopwr) == 0: if len(histopwr) == 0:
return "","No valid data available","","" return "","No valid data available","",""
@@ -3468,8 +3471,11 @@ def thumbnail_flex_chart(rowdata,id=0,promember=0,
plot.yaxis.axis_label = 'Y' plot.yaxis.axis_label = 'Y'
try:
yrange1 = Range1d(start=yaxminima[yparam1],end=yaxmaxima[yparam1]) yrange1 = Range1d(start=yaxminima[yparam1],end=yaxmaxima[yparam1])
except KeyError:
yrange1 = Range1d(start=yparam1.min(), end=yparam1.max())
plot.y_range = yrange1 plot.y_range = yrange1
if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'):
+123 -11
View File
@@ -10,7 +10,7 @@ from django.dispatch import receiver
from django.forms.widgets import SplitDateTimeWidget from django.forms.widgets import SplitDateTimeWidget
from django.forms.extras.widgets import SelectDateWidget from django.forms.extras.widgets import SelectDateWidget
from django.forms.formsets import BaseFormSet from django.forms.formsets import BaseFormSet
from django.contrib.admin.widgets import AdminDateWidget from django.contrib.admin.widgets import AdminDateWidget,AdminTimeWidget,AdminSplitDateTime
from datetimewidget.widgets import DateTimeWidget from datetimewidget.widgets import DateTimeWidget
from django.core.validators import validate_email from django.core.validators import validate_email
import os import os
@@ -422,18 +422,19 @@ def course_length(course):
return int(totaldist) return int(totaldist)
# Extension of User with rowing specific data sexcategories = (
class Rower(models.Model):
weightcategories = (
('hwt','heavy-weight'),
('lwt','light-weight'),
)
sexcategories = (
('male','male'), ('male','male'),
('female','female'), ('female','female'),
('not specified','not specified'), ('not specified','not specified'),
) )
weightcategories = (
('hwt','heavy-weight'),
('lwt','light-weight'),
)
# Extension of User with rowing specific data
class Rower(models.Model):
stravatypes = ( stravatypes = (
('Ride','Ride'), ('Ride','Ride'),
@@ -1056,6 +1057,44 @@ class PlannedSession(models.Model):
super(PlannedSession,self).save(*args, **kwargs) super(PlannedSession,self).save(*args, **kwargs)
from django.core.validators import RegexValidator,validate_email
class VirtualRace(PlannedSession):
has_registration = models.BooleanField(default=False)
registration_closure = models.DateTimeField(blank=True,null=True)
evaluation_closure = models.DateTimeField(blank=True,null=True)
start_time = models.TimeField(blank=True,null=True)
end_time = models.TimeField(blank=True,null=True)
country = models.CharField(max_length=100,blank=True)
timezone = models.CharField(default='UTC',
choices=timezones,
max_length=100)
phone_regex = RegexValidator(
regex=r'^\+?1?\d{9,15}$',
message="Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed."
)
contact_phone = models.CharField(validators=[phone_regex], max_length=17, blank=True)
contact_email = models.EmailField(max_length=254,
validators=[validate_email],blank=True)
def __unicode__(self):
name = self.name
startdate = self.startdate
enddate = self.enddate
stri = u'Virtual Race {n}'.format(
n = name,
)
return stri
# Date input utility # Date input utility
class DateInput(forms.DateInput): class DateInput(forms.DateInput):
input_type = 'date' input_type = 'date'
@@ -1093,6 +1132,49 @@ class PlannedSessionForm(ModelForm):
super(PlannedSessionForm, self).__init__(*args, **kwargs) super(PlannedSessionForm, self).__init__(*args, **kwargs)
self.fields['course'].queryset = GeoCourse.objects.all().order_by("country","name") self.fields['course'].queryset = GeoCourse.objects.all().order_by("country","name")
class VirtualRaceForm(ModelForm):
course = forms.ModelChoiceField(queryset = GeoCourse.objects, empty_label=None)
registration_closure = forms.SplitDateTimeField(widget=AdminSplitDateTime(),required=False)
evaluation_closure = forms.SplitDateTimeField(widget=AdminSplitDateTime(),required=False)
class Meta:
model = VirtualRace
fields = [
'name',
'startdate',
'start_time',
'enddate',
'end_time',
'has_registration',
'registration_closure',
'evaluation_closure',
'course',
'comment',
'contact_phone',
'contact_email',
]
dateTimeOptions = {
'format': 'yyyy-mm-dd',
'autoclose': True,
}
widgets = {
'comment': forms.Textarea,
'startdate': AdminDateWidget(),
'enddate': AdminDateWidget(),
'start_time': AdminTimeWidget(),
'end_time': AdminTimeWidget(),
'registration_closure':AdminSplitDateTime(),
'evaluation_closure':AdminSplitDateTime(),
}
def __init__(self,*args,**kwargs):
super(VirtualRaceForm, self).__init__(*args, **kwargs)
self.fields['course'].queryset = GeoCourse.objects.all().order_by("country","name")
class PlannedSessionFormSmall(ModelForm): class PlannedSessionFormSmall(ModelForm):
class Meta: class Meta:
@@ -1124,12 +1206,12 @@ class PlannedSessionFormSmall(ModelForm):
'manager': forms.HiddenInput(), 'manager': forms.HiddenInput(),
} }
boattypes = types.boattypes
# Workout # Workout
class Workout(models.Model): class Workout(models.Model):
workouttypes = types.workouttypes workouttypes = types.workouttypes
workoutsources = types.workoutsources workoutsources = types.workoutsources
boattypes = types.boattypes
privacychoices = types.privacychoices privacychoices = types.privacychoices
user = models.ForeignKey(Rower) user = models.ForeignKey(Rower)
@@ -1242,6 +1324,36 @@ def auto_delete_strokedata_on_delete(sender, instance, **kwargs):
conn.close() conn.close()
engine.dispose() engine.dispose()
# Virtual Race results (for keeping results when workouts are deleted)
class VirtualRaceResult(models.Model):
user = models.ForeignKey(Rower)
teamname = models.CharField(max_length=20,verbose_name = 'Team Name',
blank=True,null=True)
username = models.CharField(max_length=150)
workout = models.ForeignKey(Workout,blank=True,null=True)
weightcategory = models.CharField(default="hwt",max_length=10,
choices=weightcategories,
verbose_name='Weight Category')
race = models.ForeignKey(VirtualRace)
duration = models.TimeField(default=datetime.time(1,0))
boattype = models.CharField(choices=boattypes,max_length=40,
default='1x',
verbose_name = 'Boat Type'
)
coursecompleted = models.BooleanField(default=False)
sex = models.CharField(default="not specified",
max_length=30,
choices=sexcategories,
verbose_name='Gender')
age = models.IntegerField(null=True)
class VirtualRaceResultForm(ModelForm):
class Meta:
model = VirtualRaceResult
fields = ['teamname','weightcategory','boattype','age']
from rowers.metrics import rowingmetrics from rowers.metrics import rowingmetrics
strokedatafields = { strokedatafields = {
+310 -3
View File
@@ -7,8 +7,8 @@ import time
from django.db import IntegrityError from django.db import IntegrityError
import uuid import uuid
from django.conf import settings from django.conf import settings
import pytz
from utils import myqueue from utils import myqueue,calculate_age,totaltime_sec_to_string
import django_rq import django_rq
queue = django_rq.get_queue('default') queue = django_rq.get_queue('default')
@@ -18,7 +18,7 @@ queuehigh = django_rq.get_queue('low')
from rowers.models import ( from rowers.models import (
Rower, Workout,Team, Rower, Workout,Team,
GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle, GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle,
TrainingPlan,PlannedSession, TrainingPlan,PlannedSession,VirtualRaceResult
) )
import metrics import metrics
@@ -86,6 +86,30 @@ def timefield_to_seconds_duration(t):
return duration return duration
def get_virtualrace_times(virtualrace):
geocourse = GeoCourse.objects.get(id = virtualrace.course.id)
timezone_str = courses.get_course_timezone(geocourse)
startdatetime = datetime.datetime.combine(
virtualrace.startdate,virtualrace.start_time)
enddatetime = datetime.datetime.combine(
virtualrace.enddate,virtualrace.end_time)
startdatetime = pytz.timezone(timezone_str).localize(
startdatetime
)
enddatetime = pytz.timezone(timezone_str).localize(
enddatetime
)
return {
'startdatetime':startdatetime,
'enddatetime':enddatetime,
'evaluation_closure':virtualrace.evaluation_closure,
'registration_closure':virtualrace.registration_closure,
}
def get_session_metrics(ps): def get_session_metrics(ps):
rowers = ps.rower.all() rowers = ps.rower.all()
rscore = [] rscore = []
@@ -401,6 +425,13 @@ def get_sessions(r,startdate=date.today(),
return sps return sps
def get_my_session_ids(r):
sps = PlannedSession.objects.filter(
rower__in=[r]
).order_by("preferreddate","startdate","enddate")
return [ps.id for ps in sps]
def get_workouts_session(r,ps): def get_workouts_session(r,ps):
ws = Workout.objects.filter(user=r,plannedsession=ps) ws = Workout.objects.filter(user=r,plannedsession=ps)
@@ -416,3 +447,279 @@ def update_plannedsession(ps,cd):
ps.save() ps.save()
return 1,'Planned Session Updated' return 1,'Planned Session Updated'
def update_virtualrace(ps,cd):
for attr, value in cd.items():
if attr == 'comment':
value.replace("\r\n", "&#10");
value.replace("\n", "&#10");
setattr(ps, attr, value)
# correct times
course = cd['course']
geocourse = GeoCourse.objects.get(id= course.id)
timezone_str = courses.get_course_timezone(geocourse)
startdatetime = datetime.combine(cd['startdate'],cd['start_time'])
enddatetime = datetime.combine(cd['enddate'],cd['end_time'])
startdatetime = pytz.timezone(timezone_str).localize(
startdatetime
)
enddatetime = pytz.timezone(timezone_str).localize(
enddatetime
)
ps.evaluation_closure = pytz.timezone(timezone_str).localize(
ps.evaluation_closure.replace(tzinfo=None)
)
try:
ps.registration_closure = pytz.timezone(timezone_str).localize(
ps.registration_closure.replace(tzinfo=None)
)
except AttributeError:
pass
ps.timezone = timezone_str
ps.save()
return 1,'Virtual Race Updated'
def race_rower_status(r,race):
ws = Workout.objects.filter(user=r,plannedsession=race)
is_complete = is_session_complete_ws(ws,race)[1]
has_registered = r in race.rower.all()
return is_complete,has_registered
def race_can_edit(r,race):
if r.user != race.manager:
return False
else:
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
if timezone.now()<startdatetime:
return True
else:
return False
return False
def race_can_submit(r,race):
if r not in race.rower.all():
return False
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
evaluation_closure = race.evaluation_closure
if timezone.now() > startdatetime and timezone.now() < evaluation_closure:
is_complete,has_registered = race_rower_status(r,race)
if is_complete == 'not done':
return True
else:
return False
else:
return False
return False
def race_can_resubmit(r,race):
if r not in race.rower.all():
return False
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
evaluation_closure = race.evaluation_closure
if timezone.now() > startdatetime and timezone.now() < evaluation_closure:
is_complete,has_registered = race_rower_status(r,race)
if is_complete in ['partial','completed']:
return True
else:
return False
else:
return False
return False
def race_can_withdraw(r,race):
if r not in race.rower.all():
return False
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
registration_closure = race.registration_closure
if registration_closure is not None and registration_closure != '':
if timezone.now() > registration_closure:
return False
elif timezone.now() > startdatetime:
return False
elif timezone.now() > startdatetime:
return False
return True
def race_can_register(r,race):
if r in race.rower.all():
return False
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
registration_closure = race.registration_closure
if registration_closure is not None and registration_closure != '':
if timezone.now() > registration_closure:
return False
elif timezone.now() > startdatetime:
return False
elif timezone.now() > startdatetime:
return False
return True
def add_rower_race(r,race):
race.rower.add(r)
race.save()
return 1
def remove_rower_race(r,race):
race.rower.remove(r)
records = VirtualRaceResult.objects.filter(user=r,
workout__isnull=True,
race=race)
for r in records:
r.delete()
return 1
# Low Level functions - to be called by higher level methods
def add_workout_race(ws,race,r):
result = 0
comments = []
errors = []
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
end_time = race.end_time
end_date = race.enddate
enddatetime = datetime.combine(end_date,end_time)
enddatetime = pytz.timezone(race.timezone).localize(
enddatetime
)
# check if all sessions have same date
dates = [w.date for w in ws]
if (not all(d == dates[0] for d in dates)) and race.sessiontype not in ['challenge','cycletarget']:
errors.append('For tests and training sessions, selected workouts must all be done on the same date')
return result,comments,errors
if len(ws)>1 and race.sessiontype == 'test':
errors.append('For tests, you can only attach one workout')
return result,comments,errors
ids = [w.id for w in ws]
ids = list(set(ids))
if len(ids)>1 and race.sessiontype in ['test','coursetest']:
errors.append('For tests, you can only attach one workout')
return result,comments,errors
# start adding sessions
for w in ws:
if w.startdatetime>=startdatetime and w.startdatetime<=enddatetime:
w.plannedsession = race
w.save()
result += 1
comments.append('Your result has been submitted')
else:
errors.append('Workout %i did not match the race window' % w.id)
return result,comments,errors
if result>0:
username = r.user.first_name+' '+r.user.last_name
if r.birthdate:
age = calculate_age(r.birthdate)
else:
age = None
(
coursetime,
coursemeters,
coursecompleted
) = courses.get_time_course(ws,race.course)
if not coursecompleted:
errors.append('Your trajectory did not match the race course')
return result,comments,errors
duration = totaltime_sec_to_string(coursetime)
records = VirtualRaceResult.objects.filter(
user=r,
race=race
)
record = records[0]
if ws[0].boattype != record.boattype:
errors.append('Your workout boat type did not match the boat type you registered')
return result,comments,errors
if ws[0].weightcategory != record.weightcategory:
errors.append('Your workout weight category did not match the weight category you registered')
return result,comments, errors
record.coursecompleted=coursecompleted
record.workout=ws[0]
record.duration = duration
record.save()
return result,comments,errors
def delete_race_result(workout,race):
results = VirtualRaceResult.objects.filter(workout=workout,race=race)
for r in results:
r.delete()
+1 -1
View File
@@ -75,7 +75,7 @@
<p>Rowsandall.com tries to be compatible with the most important tools <p>Rowsandall.com tries to be compatible with the most important tools
that rowers use to capture the data (both indoor and OTW). For a full that rowers use to capture the data (both indoor and OTW). For a full
list of currently supported devides/apps, click list of currently supported devides/apps, click
<a href="compatibility">here</a>. <a href="/rowers/compatibility">here</a>.
</div> </div>
+2 -2
View File
@@ -141,9 +141,9 @@
<div class="grid_2 suffix_2 alpha"> <div class="grid_2 suffix_2 alpha">
<p> <p>
{% if user|is_planmember %} {% if user|is_planmember %}
<a class="button blue small" href="/rowers/fitness-progress">Lab</a> <a class="button blue small" href="/rowers/laboratory">The Labs</a>
{% else %} {% else %}
<a class="button blue small" href="/rowers/promembership">Lab</a> <a class="button blue small" href="/rowers/promembership">The Labs</a>
{% endif %} {% endif %}
</p> </p>
<p> <p>
+4 -3
View File
@@ -7,14 +7,15 @@
<h2>Import Compatibility</h2> <h2>Import Compatibility</h2>
<p>Rowsandall.com tries to be compatible with the most important tools <p>Rowsandall.com tries to be compatible with the most important tools
that rowers use to capture the data (both indoor and OTW). For a full that rowers use to capture the data (both indoor and OTW).
list of currently supported devides/apps, click
<a href="compatibility.html">here</a>.
<p>On The Water <p>On The Water
<ul> <ul>
<li> CrewNerd (TCX)</li> <li> CrewNerd (TCX)</li>
<li> Rowing In Motion (TCX)</li> <li> Rowing In Motion (TCX)</li>
<li> BoatCoach </li>
<li> RowingCoach </li>
<li> Quiske RowP</li>
<li> Speedcoach XL (CSV)</li> <li> Speedcoach XL (CSV)</li>
<li> Speedcoach GPS (FIT and CSV)</li></ul></p> <li> Speedcoach GPS (FIT and CSV)</li></ul></p>
+41
View File
@@ -0,0 +1,41 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Rowsandall - Analysis {% endblock %}
{% block content %}
<h1>The Labs</h1>
<p>This is where whacky new ideas are tested</p>
<div class="grid_12 alpha">
<div class="grid_6 suffix_6 alpha">
<img src="/static/img/rivercurrent.jpg" width="400">
</div>
<div class="grid_6 alpha">
<h2>Basic</h2>
</div>
<div class="grid_6 omega">
<h2>Pro</h2>
<div class="grid_2 alpha">
<p>
{% if user|is_planmember %}
<a class="button blue small" href="/rowers/fitness-progress">Power Progress</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">Power Progress</a>
{% endif %}
</p>
<p>
Monitoring power duration evidence from all your workouts. Feel free to explore.
</p>
</div>
</div>
</div>
{% endblock %}
+2 -2
View File
@@ -63,11 +63,11 @@
</table> </table>
{% csrf_token %} {% csrf_token %}
</div> </div>
<div class="grid_2"> <div class="grid_2 alpha">
<input name='daterange' class="button green" type="submit" value="Submit"> </form> <input name='daterange' class="button green" type="submit" value="Submit"> </form>
</div> </div>
{% if user.is_authenticated and user|is_manager %} {% if user.is_authenticated and user|is_manager %}
<div class="grid_2 omega dropdown"> <div class="grid_2 dropdown">
<button class="grid_2 alpha button green small dropbtn"> <button class="grid_2 alpha button green small dropbtn">
{{ rower.user.first_name }} {{ rower.user.last_name }} {{ rower.user.first_name }} {{ rower.user.last_name }}
</button> </button>
+8 -7
View File
@@ -6,11 +6,8 @@
<div class="grid_6 alpha"> <div class="grid_6 alpha">
<h2>Coach and Self-Coach Membership</h2> <h2>Coach and Self-Coach Membership</h2>
<p>You have arrived at this page, because you tried to create a <p>Rowsandall.com's Training Planning functionality
training plan for yourself.</p> is part of the paid "Self-Coach" and "Coach" plans.</p>
<p>This option is restricted to rowers on our "Self-Coach" plan or
coaches on our "Coach" plan.</p>
<p>On the "Self-Coach" plan, you can plan your own sessions.</p> <p>On the "Self-Coach" plan, you can plan your own sessions.</p>
@@ -32,7 +29,8 @@
<div class="grid_6 omega"> <div class="grid_6 omega">
<h2>What training planning functionality do we offer?</h2> <h2>What training planning functionality do we offer?</h2>
<p>Over the spring of 2018, we will gradually expand this functionality. <p>Over the spring of 2018, we are rolling out the first phases
of Training Planning functionality.
Our current roadmap is to deploy the following and more:</li> Our current roadmap is to deploy the following and more:</li>
<p> <p>
@@ -45,12 +43,15 @@
<li><b>Implemented:</b>Track your teams performance against plan. See how well each <li><b>Implemented:</b>Track your teams performance against plan. See how well each
of your team members adhere to their (team or personalized) plan.</li> of your team members adhere to their (team or personalized) plan.</li>
<li><b>Implemented:</b>See test outcomes ranked by performance.</li> <li><b>Implemented:</b>See test outcomes ranked by performance.</li>
<li><b>Implemented:</b>Attach courses to your OTW tests. This advanced functionality <li><b>Implemented:</b>Attach courses to your OTW tests.
This advanced functionality
allows you, for example, to assign "Row the 6km from bridge A to allows you, for example, to assign "Row the 6km from bridge A to
bridge B on Saturday" to your team members. The resulting workout bridge B on Saturday" to your team members. The resulting workout
tracks will be evaluated against the course, and you will receive tracks will be evaluated against the course, and you will receive
a results table for the net time spent between the start and finish a results table for the net time spent between the start and finish
points on the course. It's like a mini head race. points on the course. It's like a mini head race.
<li>Define your own macro, meso and microcycle start and end dates</li>
<li>More to come ... </li>
</ul> </ul>
</p> </p>
+11 -1
View File
@@ -52,7 +52,7 @@
<div id="right" class="grid_6 omega"> <div id="right" class="grid_6 omega">
{% if plannedsession.sessiontype == 'test' or plannedsession.sessiontype == 'coursetest' %} {% if plannedsession.sessiontype == 'test' or plannedsession.sessiontype == 'coursetest' %}
<h1>Ranking</h1> <h1>Ranking</h1>
<table class="listtable shortpadded" width="80%"> <table id="rankingtable" class="listtable shortpadded tablesorter" width="80%">
<thead> <thead>
<tr> <tr>
<th>Nr</th> <th>Nr</th>
@@ -158,4 +158,14 @@
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script type="text/javascript" src="/static/admin/js/jquery.min.js"></script>
<script type="text/javascript" src="/static/admin/js/jquery.tablesorter.min.js"></script>
<script type="text/javascript" src="/static/admin/js/jquery.tablesorter.widgets.js"></script>
<script>
$( document ).ready(function() {
$("#rankingtable").tablesorter();
});
</script>
{% endblock %} {% endblock %}
+3 -1
View File
@@ -135,11 +135,13 @@ You will be taken to the secure PayPal payment site.
<h2>Free Trial</h2> <h2>Free Trial</h2>
<p> <p>
You qualify for a 14 day free trial. No credit card needed. You qualify for a 14 day free trial. No credit card needed.
Try out Pro membership for two weeks. Click the button below to Try out Pro or Self-Coach membership for two weeks. Click the button below to
sign up for the trial. After your trial period expires, you will be sign up for the trial. After your trial period expires, you will be
automatically reset to the Basic plan, unless you upgrade to Pro. automatically reset to the Basic plan, unless you upgrade to Pro.
</p> </p>
<div class="grid_6"><p><a class="button green small" href="/rowers/starttrial">Yes, I want to try Pro membership for 14 days for free. No strings attached.</a></p></div> <div class="grid_6"><p><a class="button green small" href="/rowers/starttrial">Yes, I want to try Pro membership for 14 days for free. No strings attached.</a></p></div>
<div class="grid_6">&nbsp;</div>
<div class="grid_6"><p><a class="button green small" href="/rowers/startplantrial">Yes, I want to try Self-Coach membership for 14 days for free. No strings attached.</a></p></div>
{% endif %} {% endif %}
<h2>Recurring Payment</h2> <h2>Recurring Payment</h2>
+56
View File
@@ -0,0 +1,56 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Submit Race Result{% endblock %}
{% block meta %}
<script type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
</script>
<script type='text/javascript'
src='https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js'>
</script>
<script>
</script>
{% endblock %}
{% block content %}
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<h1>Submit Your Result for {{ race.name }}</h1>
</div>
</div>
<form id="race_submit_form"
method="post">
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<p>Select one of the following workouts that you rowed within the race window</p>
<table width="100%">
<tr>
{% for field in w_form.hidden_fields %}
{{ field }}
{% endfor %}
{% for field in w_form.visible_fields %}
<td>
{{ field }}
</td>
{% endfor %}
</tr>
</table>
</div>
</div>
<div class="grid_2 prefix_2 suffix_8">
{% csrf_token %}
<input class="button green" type="submit" value="Submit">
</div>
</form>
{% endblock %}
{% block scripts %}
{% endblock %}
+210
View File
@@ -0,0 +1,210 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}New Virtual Race{% endblock %}
{% block content %}
<div class="grid_12 alpha">
<h1>{{ race.name }}</h1>
</div>
<div class="grid_12 alpha">
<div class="grid_8 alpha">
<div id="raceinfo">
<h2>Race Information</h2>
<p>
<table class="listtable shortpadded" width="80%">
<tbody>
<tr>
<th>Course</th><td>{{ race.course }}</td>
</tr>
{% if race.has_registration %}
<tr>
<th>Registration closure</th>
<td>{{ race.registration_closure }}</td>
</tr>
{% endif %}
<tr>
<th>Date</th><td>{{ race.startdate }}</td>
</tr>
<tr>
<th>Race Window</th><td>{{ race.startdate }} {{ race.start_time }} to {{ race.enddate }} {{ race.end_time }}</td>
</tr>
<tr>
<th>Results Submission Deadline</th><td>{{ race.evaluation_closure }}</td>
</tr>
<tr>
<th>Organizer</th><td>{{ race.manager.first_name }} {{ race.manager.last_name }}</td>
</tr>
<tr>
<th>Contact Email</th><td>{{ race.contact_email }}</td>
</tr>
<tr>
<th>Contact Phone</th><td>{{ race.contact_phone }}</td>
</tr>
<tr>
<th>Comment</th><td>{{ race.comment }}</td>
</tr>
</tbody>
</table>
</p>
</div>
<div id="registerbuttons">
{% if request.user.is_anonymous %}
<p>
Registered users of rowsandall.com can participate in this event.
</p>
{% else %}
<p>
See race rules below.
</p>
<p>
{% for button in buttons %}
{% if button == 'registerbutton' %}
<a href="/rowers/virtualevent/{{ race.id }}/register" class="button gray small grid_2">Register</a>
{% endif %}
{% if button == 'submitbutton' %}
<a href="/rowers/virtualevent/{{ race.id }}/submit" class="button gray small grid_2">Submit Result</a>
{% endif %}
{% if button == 'resubmitbutton' %}
<a href="/rowers/virtualevent/{{ race.id }}/submit" class="button gray small grid_2">Submit New Result</a>
{% endif %}
{% if button == 'withdrawbutton' %}
<a href="/rowers/virtualevent/{{ race.id }}/withdraw" class="button gray small grid_2">Withdraw</a>
{% endif %}
{% if button == 'editbutton' %}
<a href="/rowers/virtualevent/{{ race.id }}/edit" class="button gray small grid_2">Edit Race</a>
{% endif %}
{% endfor %}
</p>
{% endif %}
</div>
<div id="results">
<h2>Results</h2>
<p>
{% if results or dns %}
<table class="listtable shortpadded" width="100%">
<thead>
<tr>
<th>&nbsp;</th>
<th>Name</th>
<th>Team Name</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>Boat</th>
<th>Raw Time</th>
<th>In Class</th>
<th>Corrected Time</th>
<th>Corrected Place</th>
</tr>
</thead>
<tbody>
{% for result in results %}
<tr>
<td>{{ forloop.counter }}</td>
<td>
<a href="/rowers/workout/{{ result.workout.id }}">
{{ result.username }}</a></td>
<td>{{ result.teamname }}</th>
<td>{{ result.age }}</td>
<td>{{ result.sex }}</td>
<td>{{ result.weightcategory }}</td>
<td>{{ result.boattype }}</td>
<td>{{ result.duration |durationprint:"%H:%M:%S.%f" }}</td>
</tr>
{% endfor %}
{% for result in dns %}
<tr>
<td>&nbsp;</td>
<td>{{ result.username }}</td>
<td>{{ result.teamname }}</td>
<td>{{ result.age }}</td>
<td>{{ result.sex }}</td>
<td>{{ result.weightcategory }}</td>
<td>{{ result.boattype }}</td>
<td>DNS</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
No results yet
{% endif %}
</p>
</div>
<div id="registered">
{% if records %}
<h2>Registered Competitors</h2>
<table class="listtable shortpadded" width="80%">
<thead>
<tr>
<th>Name</th>
<th>Team Name</th>
<th>Age</th>
<th>Weight Category</th>
</tr>
<tbody>
{% for record in records %}
<tr>
<td>{{ record.username }}
<td>{{ record.teamname }}</td>
<td>{{ record.weightcategory }}</td>
<td>{{ record.age }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
<div id="rules">
<h2>Rules</h2>
<p>
As a rowsandall.com user, you can
register to take part in this event.
If the race organizer has set a registration deadline, you must
register before the deadline. Otherwise, it is sufficient to
register before the start of the race window.
You can always withdraw from participating before the registration
deadline or the start of the race window, if no registration
deadline was set.
</p>
<p>
After the start of the race window and before the submission deadline,
you can submit results by linking the race to one of your uploaded
workouts. The workout start time must be within the race window
and your trajectory must pass through the blue polygons on the course
map (in the right order), for your result to be valid.
</p>
<p>
The results table has a link to a page where details of your workout
are shown.
</p>
<p>
Race results are stored permanently and are not deleted when
you delete the respective workout or remove your account.
By registering, you agree with this and the race rules.
</p>
<p>
Virtual Racing on rowsandall.com is honors based. Please be a good
sport, submit real results rowed by you, and make sure you set the
boat type correctly. For (future functionality) age and gender
corrected times, please be sure your gender and birth date are set
correctly in your user settings.
</p>
</div>
</div>
<div class="grid_4 omega">
<h2>Course</h2>
{{ coursediv|safe }}
{{ coursescript|safe }}
</div>
</div>
{% endblock %}
+49
View File
@@ -0,0 +1,49 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}New Virtual Race{% endblock %}
{% block content %}
<div class="grid_12 alpha">
<h1>New Virtual Race</h1>
<div class="grid_8 alpha">
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<div id="formbutton" class="grid_1 prefix_4 suffix_1">
<input class="button green" type="submit" value="Save">
</div>
</form>
</div>
<div class="grid_4 omega">
<p>
<ul>
<li>All times are local times in the race course time zone</li>
<li>Adding a contact phone number and email is not mandatory, but we
strongly recommend it.</li>
<li>If your event has a registration closure deadline, participants
have to enter (and can withdraw) before the registration closure time.</li>
<li>Participants can submit results until the evaluation closure time.</li>
</ul>
</p>
</div>
</div>
{% endblock %}
{% block scripts %}
{% endblock %}
+49
View File
@@ -0,0 +1,49 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Edit Virtual Race{% endblock %}
{% block content %}
<div class="grid_12 alpha">
<h1>Edit Race {{ race.name }}</h1>
<div class="grid_8 alpha">
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<div id="formbutton" class="grid_1 prefix_4 suffix_1">
<input class="button green" type="submit" value="Save">
</div>
</form>
</div>
<div class="grid_4 omega">
<p>
<ul>
<li>All times are local times in the race course time zone</li>
<li>Adding a contact phone number and email is not mandatory, but we
strongly recommend it.</li>
<li>If your event has a registration closure deadline, participants
have to enter (and can withdraw) before the registration closure time.</li>
<li>Participants can submit results until the evaluation closure time.</li>
</ul>
</p>
</div>
</div>
{% endblock %}
{% block scripts %}
{% endblock %}
@@ -0,0 +1,50 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Register for a Virtual Race{% endblock %}
{% block meta %}
<script type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
</script>
<script type='text/javascript'
src='https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js'>
</script>
<script>
</script>
{% endblock %}
{% block content %}
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<h1>Register for {{ race.name }}</h1>
</div>
</div>
<form id="race_register_form"
method="post">
<div class="grid_12 alpha">
<p>If you are participating in a single, we will take the age and weight
value from your user settings. For other boat types, please fill out
crew weight class and average age.
</p>
<div class="grid_6 alpha">
<table width="100%">
{{ form.as_table }}
</table>
</div>
</div>
<div class="grid_2 prefix_2 suffix_8">
{% csrf_token %}
<input class="button green" type="submit" value="Submit">
</div>
</form>
{% endblock %}
{% block scripts %}
{% endblock %}
+59
View File
@@ -0,0 +1,59 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Rowsandall Virtual Racing{% endblock %}
{% block scripts %}
{% endblock %}
{% block content %}
<div class="grid_12 alpha">
<div class="grid_2 alpha suffix_10">
<p>
<a class="button green small" href="/rowers/virtualevent/create">Add Race</a>
</p>
</div>
</div>
<div class="grid_12 alpha">
<form enctype="multipart/form-data" method="post">
<div class="grid_8 alpha">
{{ form.as_table }}
{% csrf_token %}
</div>
<div class="grid_2">
<input name='form' class='button green' type='submit' value="Submit">
</div>
</form>
</div>
<div class="grid_12 alpha">
<p>
<table width="100%" class="listtable shortpadded">
<thead>
<tr>
<th>Date</th>
<th>Event</th>
<th>Country</th>
<th>Course</th>
</tr>
</thead>
<tbody>
{% for race in races %}
<tr>
<td>{{ race.startdate }}</td>
<td><a href="/rowers/virtualevent/{{ race.id }}">{{ race.name }}</a></td>
<td>{{ race.course.country }}</td>
<td>{{ race.course.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</p>
</div>
{% endblock %}
+1 -1
View File
@@ -235,7 +235,7 @@
</p> </p>
<div class="grid_2 alpha"> <div class="grid_2 alpha">
<p> <p>
<a class="button green small" href="recalcsummary">Update Summary</a> <a class="button green small" href="/rowers/workout/{{ workout.id }}/recalcsummary">Update Summary</a>
</p> </p>
</div> </div>
</div> </div>
+2
View File
@@ -86,7 +86,9 @@
<span class="tooltiptext"> <span class="tooltiptext">
<p>rPower: Equivalent steady state power for the duration of the workout.</p> <p>rPower: Equivalent steady state power for the duration of the workout.</p>
<p>Heart Rate Drift: Comparing heart rate normalized for average power for the first and second half of the workout</p> <p>Heart Rate Drift: Comparing heart rate normalized for average power for the first and second half of the workout</p>
<p>TRIMP: TRaining IMPact. A way to combine duration and heart rate into a single number.</p>
<p>rScore: Score based on rPower and workout duration to estimate training effect</p> <p>rScore: Score based on rPower and workout duration to estimate training effect</p>
<p>rScore (HR): Score based on heart rate, designed to give values comparable to rScore. Used instead of rScore for workouts without power data.</p>
</span> </span>
<h2>Other Stats</h2> <h2>Other Stats</h2>
<table width="100%" class="listtable"> <table width="100%" class="listtable">
+7
View File
@@ -5,6 +5,7 @@ import dateutil.parser
import json import json
import datetime import datetime
register = template.Library() register = template.Library()
from rowers.utils import calculate_age
def strfdelta(tdelta): def strfdelta(tdelta):
minutes,seconds = divmod(tdelta.seconds,60) minutes,seconds = divmod(tdelta.seconds,60)
@@ -136,6 +137,11 @@ from rowers.views import hasplannedsessions
def is_planmember(user): def is_planmember(user):
return hasplannedsessions(user) return hasplannedsessions(user)
@register.filter
def get_age(r):
return calculate_age(r.birthdate)
@register.filter @register.filter
def user_teams(user): def user_teams(user):
try: try:
@@ -196,6 +202,7 @@ def team_rowers(user):
return [] return []
@register.filter @register.filter
def verbosetimeperiod(timeperiod): def verbosetimeperiod(timeperiod):
table = { table = {
+3 -1
View File
@@ -1,3 +1,6 @@
from __future__ import print_function
from bs4 import BeautifulSoup
import re
from django.test import TestCase, Client,override_settings 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
@@ -35,7 +38,6 @@ from redis import StrictRedis
redis_connection = StrictRedis() redis_connection = StrictRedis()
class DjangoTestCase(TestCase, MockTestCase): class DjangoTestCase(TestCase, MockTestCase):
def _pre_setup(self): def _pre_setup(self):
MockTestCase.setUp(self) MockTestCase.setUp(self)
+186
View File
@@ -0,0 +1,186 @@
from __future__ import print_function
from bs4 import BeautifulSoup
import re
from django.test import TestCase, Client,override_settings
from django.core.management import call_command
from django.utils.six import StringIO
from django.test.client import RequestFactory
from .views import checkworkoutuser,c2_open
from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage
from rowers.forms import DocumentsForm,CNsummaryForm,RegistrationFormUniqueEmail
import rowers.plots as plots
import rowers.interactiveplots as iplots
import datetime
from rowingdata import rowingdata as rdata
from rowingdata import rower as rrower
from django.utils import timezone
from rowers.rows import handle_uploaded_file
from django.core.files.uploadedfile import SimpleUploadedFile
from time import strftime,strptime,mktime,time,daylight
import os
from rowers.tasks import handle_makeplot
from rowers.utils import serialize_list,deserialize_list
from rowers.c2stuff import C2NoTokenError
from shutil import copyfile
from minimocktest import MockTestCase
import pandas as pd
import json
import numpy as np
from rowers import urls
from rowers.views import error500_view,error404_view,error400_view,error403_view
from dataprep import delete_strokedata
from redis import StrictRedis
redis_connection = StrictRedis()
VERBOSE = True
class TraverseLinksTest(TestCase):
def setUp(self):
self.u = User.objects.create_superuser(
'superuser1',
'superuser1@example.com','pwd')
self.r = Rower.objects.create(user=self.u,gdproptin=True,gdproptindate=timezone.now())
nu = datetime.datetime.now()
self.w = Workout.objects.create(
name='testworkout',workouttype='On-water',
user=self.r,date=nu.strftime('%Y-%m-%d'),
starttime=nu.strftime('%H:%M:%S'),
duration="0:55:00",distance=8000)
self.w2 = Workout.objects.create(
name='testworkout 2',workouttype='On-water',
user=self.r,date=nu.strftime('%Y-%m-%d'),
starttime=nu.strftime('%H:%M:%S'),
duration="0:55:00",distance=8000)
if self.client.login(
username="superuser1", password="pwd"):
if VERBOSE:
print('\nLogin as superuser OK')
else:
raise BaseException('Login failed')
@classmethod
def setUpTestData(cls):
# Initialise your database here as needed
pass
def test_traverse_urls(self):
# Fill these lists as needed with your site specific URLs to check and to avoid
to_traverse_list = ['/rowers/list-workouts']
to_avoid_list = ['^/$', '^$', 'javascript:history\.back()',
'javascript:history\.go\(-1\)', '^mailto:.*',
'.*github\.io.*', 'javascript:.*',
'.*biorow\.com.*','.*facebook.*',
'.*wordpress.*','.*analytics.*','.*freenet.*',
'.*twitter.*','^blog.*',
'.*\d+-\d+-\d+.*',
'.*flexchart/.*',
'.*heroku.*',
'.*oauth.*',
'.*rowingdata.*',
'.*thisisant.*',
'.*garmin.*',
'.*sub7.*',
'.*bitbucket.*',
'.*rathburn.*',
'.*team.*',
'.*concept2.*',
'.*static.*',
'.*authorize.*',
'.*youtu.*',
'.*earth.*',
'.*underarmour.*',
'.*runkeeper.*',
'.*c2list.*',
'.*stravaimport.*',
'.*performancephones.*',
'.*sporttracks.*',
'.*join-select.*',
]
done_list = []
error_list = []
source_of_link = dict()
for link in to_traverse_list:
source_of_link[link] = 'initial'
(to_traverse_list, to_avoid_list, done_list, error_list, source_of_link) = \
self.recurse_into_path(to_traverse_list, to_avoid_list, done_list, error_list, source_of_link)
print('END REACHED\nStats:')
if VERBOSE: print('\nto_traverse_list = ' + str(to_traverse_list))
if VERBOSE: print('\nto_avoid_list = ' + str(to_avoid_list))
if VERBOSE: print('\nsource_of_link = ' + str(source_of_link))
if VERBOSE: print('\ndone_list = ' + str(done_list))
print('Followed ' + str(len(done_list)) + ' links successfully')
print('Avoided ' + str(len(to_avoid_list)) + ' links')
if error_list:
print('!! ' + str(len(error_list)) + ' error(s) : ')
for error in error_list:
print(str(error) + ' found in page ' + source_of_link[error[0]])
print('Errors found traversing links')
assert False
else:
print('No errors')
def recurse_into_path(self, to_traverse_list, to_avoid_list, done_list, error_list, source_of_link):
""" Dives into first item of to_traverse_list
Returns: (to_traverse_list, to_avoid_list, done_list, source_of_link)
"""
if to_traverse_list:
url = to_traverse_list.pop()
if not match_any(url, to_avoid_list):
print('Surfing to ' + str(url) + ', discovered in ' + str(source_of_link[url]))
response = self.client.get(url, follow=True)
if response.status_code == 200:
soup = BeautifulSoup(response.content, 'html.parser')
text = soup.get_text()
for link in soup.find_all('a'):
new_link = link.get('href')
if VERBOSE: print(' Found link: ' + str(new_link))
if match_any(new_link, to_avoid_list):
if VERBOSE: print(' Avoiding it')
elif new_link in done_list:
if VERBOSE: print(' Already done, ignoring')
elif new_link in to_traverse_list:
if VERBOSE: print(' Already in to traverse list, ignoring')
else:
if VERBOSE: print(' New, unknown link: Storing it to traverse later')
source_of_link[new_link] = url
to_traverse_list.append(new_link)
done_list.append(url)
if VERBOSE: print('Done')
else:
error_list.append((url, response.status_code))
to_avoid_list.append(url)
if VERBOSE: print('Diving into next level')
return self.recurse_into_path(to_traverse_list, to_avoid_list, done_list, error_list, source_of_link)
else:
# Nothing to traverse
if VERBOSE: print('Returning to upper level')
return to_traverse_list, to_avoid_list, done_list, error_list, source_of_link
def match_any(my_string, regexp_list):
if my_string:
combined = "(" + ")|(".join(regexp_list) + ")"
return re.match(combined, my_string)
else:
# 'None' as string always matches
return True
+10
View File
@@ -143,6 +143,14 @@ urlpatterns = [
url(r'^u/(?P<userid>\d+)/list-workouts/$',views.workouts_view), url(r'^u/(?P<userid>\d+)/list-workouts/$',views.workouts_view),
url(r'^u/(?P<userid>\d+)/list-workouts/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view), url(r'^u/(?P<userid>\d+)/list-workouts/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view),
url(r'^list-workouts/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view), url(r'^list-workouts/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view),
url(r'^virtualevents$',views.virtualevents_view),
url(r'^virtualevent/create$',views.virtualevent_create_view),
url(r'^virtualevent/(?P<id>\d+)$',views.virtualevent_view),
url(r'^virtualevent/(?P<id>\d+)/edit$',views.virtualevent_edit_view),
url(r'^virtualevent/(?P<id>\d+)/register$',views.virtualevent_register_view),
url(r'^virtualevent/(?P<id>\d+)/withdraw$',views.virtualevent_withdraw_view),
url(r'^virtualevent/(?P<id>\d+)/submit$',
views.virtualevent_submit_result_view),
url(r'^list-workouts/$',views.workouts_view), url(r'^list-workouts/$',views.workouts_view),
url(r'^list-courses/$',views.courses_view), url(r'^list-courses/$',views.courses_view),
url(r'^courses/upload$',views.course_upload_view), url(r'^courses/upload$',views.course_upload_view),
@@ -393,8 +401,10 @@ urlpatterns = [
url(r'^compatibility', TemplateView.as_view(template_name='compatibility.html'),name='about'), url(r'^compatibility', TemplateView.as_view(template_name='compatibility.html'),name='about'),
url(r'^videos', TemplateView.as_view(template_name='videos.html'),name='videos'), url(r'^videos', TemplateView.as_view(template_name='videos.html'),name='videos'),
url(r'^analysis', TemplateView.as_view(template_name='analysis.html'),name='analysis'), url(r'^analysis', TemplateView.as_view(template_name='analysis.html'),name='analysis'),
url(r'^laboratory', TemplateView.as_view(template_name='laboratory.html'),name='laboratory'),
url(r'^promembership', TemplateView.as_view(template_name='promembership.html'),name='promembership'), url(r'^promembership', TemplateView.as_view(template_name='promembership.html'),name='promembership'),
url(r'^starttrial$',views.start_trial_view), url(r'^starttrial$',views.start_trial_view),
url(r'^startplantrial$',views.start_plantrial_view),
url(r'^planmembership', TemplateView.as_view(template_name='planmembership.html'),name='planmembership'), url(r'^planmembership', TemplateView.as_view(template_name='planmembership.html'),name='planmembership'),
url(r'^paypaltest', TemplateView.as_view(template_name='paypaltest.html'),name='paypaltest'), url(r'^paypaltest', TemplateView.as_view(template_name='paypaltest.html'),name='paypaltest'),
url(r'^legal', TemplateView.as_view(template_name='legal.html'),name='legal'), url(r'^legal', TemplateView.as_view(template_name='legal.html'),name='legal'),
+28
View File
@@ -337,3 +337,31 @@ def wavg(group, avg_name, weight_name):
return (d * w).sum() / w.sum() return (d * w).sum() / w.sum()
except ZeroDivisionError: except ZeroDivisionError:
return d.mean() return d.mean()
def totaltime_sec_to_string(totaltime):
hours = int(totaltime / 3600.)
if hours > 23:
message = 'Warning: The workout duration was longer than 23 hours. '
hours = 23
minutes = int((totaltime - 3600. * hours) / 60.)
if minutes > 59:
minutes = 59
if not message:
message = 'Warning: there is something wrong with the workout duration'
seconds = int(totaltime - 3600. * hours - 60. * minutes)
if seconds > 59:
seconds = 59
if not message:
message = 'Warning: there is something wrong with the workout duration'
tenths = int(10 * (totaltime - 3600. * hours - 60. * minutes - seconds))
if tenths > 9:
tenths = 9
if not message:
message = 'Warning: there is something wrong with the workout duration'
duration = "%s:%s:%s.%s" % (hours, minutes, seconds, tenths)
return duration
+563 -32
View File
@@ -34,6 +34,7 @@ from rowers.forms import (
WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement, WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement,
LandingPageForm,PlannedSessionSelectForm,WorkoutSessionSelectForm, LandingPageForm,PlannedSessionSelectForm,WorkoutSessionSelectForm,
PlannedSessionTeamForm,PlannedSessionTeamMemberForm, PlannedSessionTeamForm,PlannedSessionTeamMemberForm,
VirtualRaceSelectForm,WorkoutRaceSelectForm,
) )
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
@@ -65,7 +66,8 @@ from rowers.models import (
Team,TeamForm,TeamInviteForm,TeamInvite,TeamRequest, Team,TeamForm,TeamInviteForm,TeamInvite,TeamRequest,
WorkoutComment,WorkoutCommentForm,RowerExportForm, WorkoutComment,WorkoutCommentForm,RowerExportForm,
CalcAgePerformance,PowerTimeFitnessMetric,PlannedSessionForm, CalcAgePerformance,PowerTimeFitnessMetric,PlannedSessionForm,
PlannedSessionFormSmall,GeoCourseEditForm, PlannedSessionFormSmall,GeoCourseEditForm,VirtualRace,
VirtualRaceForm,VirtualRaceResultForm,
) )
from rowers.models import ( from rowers.models import (
FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet
@@ -146,6 +148,7 @@ import matplotlib.pyplot as plt
from rowers.emails import send_template_email from rowers.emails import send_template_email
from pytz import timezone as tz,utc from pytz import timezone as tz,utc
from timezonefinder import TimezoneFinder
import dateutil import dateutil
import mpld3 import mpld3
from mpld3 import plugins from mpld3 import plugins
@@ -876,6 +879,8 @@ def hasplannedsessions(user):
r.save() r.save()
result = user.is_authenticated() and (r.rowerplan=='coach' or r.rowerplan=='plan') result = user.is_authenticated() and (r.rowerplan=='coach' or r.rowerplan=='plan')
if not result and r.plantrialexpires:
result = user.is_authenticaded() and r.rowerplan=='basic' and r.plantrialexpires >= datetime.date.today()
else: else:
result = False result = False
@@ -1040,6 +1045,35 @@ def start_trial_view(request):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@login_required()
def start_plantrial_view(request):
r = getrower(request.user)
if r.plantrialexpires is not None:
messages.error(request,'You do not qualify for a trial')
url = '/rowers/promembership'
return HttpResponseRedirect(url)
r.plantrialexpires = datetime.date.today()+datetime.timedelta(13)
r.protrialexpires = datetime.date.today()+datetime.timedelta(13)
r.save()
url = reverse(workouts_view)
messages.info(request,'We have started your 14 day trial period')
subject2 = "User started Pro Trial"
message2 = "User Started Pro Trial.\n"
message2 += request.user.email + "\n"
message2 += "User name: "+request.user.username
send_mail(subject2, message2,
'Rowsandall Server <info@rowsandall.com>',
['roosendaalsander@gmail.com'])
return HttpResponseRedirect(url)
# Create workout data from Strava or Concept2 # Create workout data from Strava or Concept2
# data and create the associated Workout object and save it # data and create the associated Workout object and save it
def add_workout_from_strokedata(user,importid,data,strokedata, def add_workout_from_strokedata(user,importid,data,strokedata,
@@ -2681,7 +2715,7 @@ def rower_process_testcallback(request):
@login_required() @login_required()
def histo_all(request,theuser=0, def histo_all(request,theuser=0,
startdate=timezone.now()-datetime.timedelta(days=10), startdate=timezone.now()-datetime.timedelta(days=10),
enddate=timezone.now()+datetime.timedelta(days=1), enddate=timezone.now(),
deltadays=-1, deltadays=-1,
startdatestring="", startdatestring="",
enddatestring="", enddatestring="",
@@ -2713,6 +2747,10 @@ def histo_all(request,theuser=0,
if enddatestring != "": if enddatestring != "":
enddate = iso8601.parse_date(enddatestring) enddate = iso8601.parse_date(enddatestring)
startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
if enddate < startdate: if enddate < startdate:
s = enddate s = enddate
enddate = startdate enddate = startdate
@@ -2863,7 +2901,7 @@ def cum_flex(request,theuser=0,
yparam1='power', yparam1='power',
yparam2='None', yparam2='None',
startdate=timezone.now()-datetime.timedelta(days=10), startdate=timezone.now()-datetime.timedelta(days=10),
enddate=timezone.now()+datetime.timedelta(days=1), enddate=timezone.now(),
deltadays=-1, deltadays=-1,
startdatestring="", startdatestring="",
enddatestring="", enddatestring="",
@@ -3592,7 +3630,7 @@ def rankings_view(request,theuser=0,
# test to fix bug # test to fix bug
startdate = datetime.datetime.combine(startdate,datetime.time()) startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1) #enddate = enddate+datetime.timedelta(days=1)
thedistances = [] thedistances = []
theworkouts = [] theworkouts = []
@@ -3998,7 +4036,7 @@ def rankings_view2(request,theuser=0,
# test to fix bug # test to fix bug
startdate = datetime.datetime.combine(startdate,datetime.time()) startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1) #enddate = enddate+datetime.timedelta(days=1)
thedistances = [] thedistances = []
@@ -4376,7 +4414,7 @@ def otwrankings_view(request,theuser=0,
# test to fix bug # test to fix bug
startdate = datetime.datetime.combine(startdate,datetime.time()) startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1) #enddate = enddate+datetime.timedelta(days=1)
@@ -4652,7 +4690,7 @@ def oterankings_view(request,theuser=0,
# test to fix bug # test to fix bug
startdate = datetime.datetime.combine(startdate,datetime.time()) startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1) #enddate = enddate+datetime.timedelta(days=1)
@@ -5049,7 +5087,7 @@ def workouts_join_select(request,
message='', message='',
successmessage='', successmessage='',
startdate=timezone.now()-datetime.timedelta(days=30), startdate=timezone.now()-datetime.timedelta(days=30),
enddate=timezone.now()+datetime.timedelta(days=1), enddate=timezone.now(),
teamid=0): teamid=0):
try: try:
@@ -5121,7 +5159,7 @@ def workouts_join_select(request,
startdate = datetime.datetime.combine(startdate,datetime.time()) startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1) #enddate = enddate+datetime.timedelta(days=1)
if startdatestring: if startdatestring:
startdate = iso8601.parse_date(startdatestring) startdate = iso8601.parse_date(startdatestring)
@@ -5208,7 +5246,7 @@ def team_comparison_select(request,
message='', message='',
successmessage='', successmessage='',
startdate=timezone.now()-datetime.timedelta(days=30), startdate=timezone.now()-datetime.timedelta(days=30),
enddate=timezone.now()+datetime.timedelta(days=1), enddate=timezone.now(),
teamid=0): teamid=0):
try: try:
@@ -5280,7 +5318,7 @@ def team_comparison_select(request,
startdate = datetime.datetime.combine(startdate,datetime.time()) startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1) #enddate = enddate+datetime.timedelta(days=1)
if startdatestring: if startdatestring:
startdate = iso8601.parse_date(startdatestring) startdate = iso8601.parse_date(startdatestring)
@@ -5452,7 +5490,7 @@ def user_multiflex_select(request,
message='', message='',
successmessage='', successmessage='',
startdate=timezone.now()-datetime.timedelta(days=30), startdate=timezone.now()-datetime.timedelta(days=30),
enddate=timezone.now()+datetime.timedelta(days=1), enddate=timezone.now(),
userid=0): userid=0):
if userid == 0: if userid == 0:
@@ -5544,7 +5582,7 @@ def user_multiflex_select(request,
startdate = datetime.datetime.combine(startdate,datetime.time()) startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1) #enddate = enddate+datetime.timedelta(days=1)
if startdatestring: if startdatestring:
startdate = iso8601.parse_date(startdatestring) startdate = iso8601.parse_date(startdatestring)
@@ -5986,7 +6024,7 @@ def user_boxplot_select(request,
message='', message='',
successmessage='', successmessage='',
startdate=timezone.now()-datetime.timedelta(days=30), startdate=timezone.now()-datetime.timedelta(days=30),
enddate=timezone.now()+datetime.timedelta(days=1), enddate=timezone.now(),
options={ options={
'includereststrokes':False, 'includereststrokes':False,
'workouttypes':['rower','dynamic','slides'], 'workouttypes':['rower','dynamic','slides'],
@@ -6082,7 +6120,7 @@ def user_boxplot_select(request,
startdate = datetime.datetime.combine(startdate,datetime.time()) startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1) #enddate = enddate+datetime.timedelta(days=1)
if startdatestring: if startdatestring:
startdate = iso8601.parse_date(startdatestring) startdate = iso8601.parse_date(startdatestring)
@@ -6380,9 +6418,8 @@ def courses_view(request):
# List Workouts # List Workouts
@login_required() @login_required()
def workouts_view(request,message='',successmessage='', def workouts_view(request,message='',successmessage='',
startdatestring="",enddatestring="", startdatestring='',
startdate=timezone.now()-datetime.timedelta(days=365), enddatestring='',
enddate=timezone.now()+datetime.timedelta(days=1),
teamid=0,rankingonly=False,rowerid=0,userid=0): teamid=0,rankingonly=False,rowerid=0,userid=0):
request.session['referer'] = absolute(request)['PATH'] request.session['referer'] = absolute(request)['PATH']
@@ -6402,13 +6439,26 @@ def workouts_view(request,message='',successmessage='',
if not checkaccessuser(request.user,r): if not checkaccessuser(request.user,r):
raise PermissionDenied("Access denied") raise PermissionDenied("Access denied")
if startdatestring:
startdate = iso8601.parse_date(startdatestring)
else:
startdate = datetime.date.today()-datetime.timedelta(days=365)
if enddatestring:
enddate = iso8601.parse_date(enddatestring)
else:
enddate = datetime.date.today()
startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
if request.method == 'POST': if request.method == 'POST':
dateform = DateRangeForm(request.POST) dateform = DateRangeForm(request.POST)
if dateform.is_valid(): if dateform.is_valid():
startdate = dateform.cleaned_data['startdate'] startdate = dateform.cleaned_data['startdate']
enddate = dateform.cleaned_data['enddate'] enddate = dateform.cleaned_data['enddate']
startdatestring = None
enddatestring = None
else: else:
dateform = DateRangeForm(initial={ dateform = DateRangeForm(initial={
'startdate':startdate, 'startdate':startdate,
@@ -6417,18 +6467,17 @@ def workouts_view(request,message='',successmessage='',
startdate = datetime.datetime.combine(startdate,datetime.time()) startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1) #enddate = enddate+datetime.timedelta(days=1)
if startdatestring:
startdate = iso8601.parse_date(startdatestring)
if enddatestring:
enddate = iso8601.parse_date(enddatestring)
if enddate < startdate: if enddate < startdate:
s = enddate s = enddate
enddate = startdate enddate = startdate
startdate = s startdate = s
startdatestring = startdate.strftime('%Y-%m-%d')
enddatestring = enddate.strftime('%Y-%m-%d')
# start date for the small graph # start date for the small graph
activity_startdate = enddate-datetime.timedelta(days=15) activity_startdate = enddate-datetime.timedelta(days=15)
@@ -6559,7 +6608,7 @@ def workout_comparison_list(request,id=0,message='',successmessage='',
startdate = datetime.datetime.combine(startdate,datetime.time()) startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1) #enddate = enddate+datetime.timedelta(days=1)
if enddate < startdate: if enddate < startdate:
s = enddate s = enddate
@@ -6637,7 +6686,7 @@ def workout_fusion_list(request,id=0,message='',successmessage='',
startdate = datetime.datetime.combine(startdate,datetime.time()) startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1) #enddate = enddate+datetime.timedelta(days=1)
if enddate < startdate: if enddate < startdate:
s = enddate s = enddate
@@ -8382,12 +8431,21 @@ def workout_flexchart3_view(request,*args,**kwargs):
workstrokesonly = request.POST['workstrokesonlysave'] workstrokesonly = request.POST['workstrokesonlysave']
reststrokes = not workstrokesonly reststrokes = not workstrokesonly
r = getrower(request.user) r = getrower(request.user)
try:
r = metrics.yaxmaxima[xparam]
if yparam1 is not None:
r = metrics.yaxmaxima[yparam1]
if yparam2 is not None:
r = metrics.yaxmaxima[yparam2]
f = FavoriteChart(user=r,xparam=xparam, f = FavoriteChart(user=r,xparam=xparam,
yparam1=yparam1,yparam2=yparam2, yparam1=yparam1,yparam2=yparam2,
plottype=plottype,workouttype=workouttype, plottype=plottype,workouttype=workouttype,
reststrokes=reststrokes) reststrokes=reststrokes)
f.save() f.save()
except KeyError:
messages.error(request,'We cannot save the ad hoc metrics in a favorite chart')
if request.method == 'POST' and 'workstrokesonly' in request.POST: if request.method == 'POST' and 'workstrokesonly' in request.POST:
workstrokesonly = request.POST['workstrokesonly'] workstrokesonly = request.POST['workstrokesonly']
if workstrokesonly == 'True': if workstrokesonly == 'True':
@@ -10780,8 +10838,6 @@ def workout_summary_restore_view(request,id,message="",successmessage=""):
intervalstats = rowdata.allstats() intervalstats = rowdata.allstats()
row.summary = intervalstats row.summary = intervalstats
row.save() row.save()
itime,idist,itype = rowdata.intervalstats_values()
nrintervals = len(idist)
# create interactive plot # create interactive plot
try: try:
@@ -10957,7 +11013,10 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
if rowdata == 0: if rowdata == 0:
return HttpResponse("Error: CSV Data File Not Found") return HttpResponse("Error: CSV Data File Not Found")
intervalstats = rowdata.allstats() intervalstats = rowdata.allstats()
try:
itime,idist,itype = rowdata.intervalstats_values() itime,idist,itype = rowdata.intervalstats_values()
except TypeError:
return HttpResponse("Error: CSV Data File Not Found")
nrintervals = len(idist) nrintervals = len(idist)
# create interactive plot # create interactive plot
@@ -11455,7 +11514,6 @@ def rower_edit_view(request,rowerid=0,message=""):
r.sex = sex r.sex = sex
r.birthdate = birthdate r.birthdate = birthdate
if resetbounce and r.emailbounced: if resetbounce and r.emailbounced:
print "aap"
r.emailbounced = False r.emailbounced = False
r.save() r.save()
form = RowerForm(instance=r) form = RowerForm(instance=r)
@@ -12178,7 +12236,7 @@ def plannedsession_multiclone_view(
request,timeperiod='none', request,timeperiod='none',
rowerid=0, rowerid=0,
startdate=timezone.now()-datetime.timedelta(days=30), startdate=timezone.now()-datetime.timedelta(days=30),
enddate=timezone.now()+datetime.timedelta(days=1)): enddate=timezone.now()):
if rowerid==0: if rowerid==0:
r = getrower(request.user) r = getrower(request.user)
@@ -12868,7 +12926,7 @@ def plannedsessions_manage_view(request,timeperiod='thisweek',rowerid=0,
user=r,date__gte=startdate, user=r,date__gte=startdate,
date__lte=enddate date__lte=enddate
).order_by( ).order_by(
"date","id" "date","startdatetime","id"
) )
@@ -13257,3 +13315,476 @@ def plannedsession_deleteconfirm_view(request,id=0):
} }
) )
def virtualevents_view(request):
# default races
races = VirtualRace.objects.filter(
startdate__gte=datetime.date.today()
).order_by("startdate","start_time")
r = getrower(request.user)
if request.method == 'POST':
# process form
form = VirtualRaceSelectForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
country = cd['country']
regattatype = cd['regattatype']
if country == 'All':
countries = VirtualRace.objects.order_by('country').values_list('country').distinct()
else:
countries = [country]
if regattatype == 'upcoming':
races = VirtualRace.objects.filter(
startdate__gte=datetime.date.today(),
country__in=countries
).order_by("startdate","start_time")
elif regattatype == 'previous':
races = VirtualRace.objects.filter(
enddate__lt=datetime.date.today(),
country__in=countries
).order_by("startdate","start_time")
elif regattatype == 'ongoing':
races = VirtualRace.objects.filter(
startdate__lte=datetime.date.today(),
evaluation_closure__gte=datetime.date.today(),
country__in=countries
).order_by("startdate","start_time")
elif regattatype == 'my':
mysessions = get_my_session_ids(r)
races = VirtualRace.objects.filter(
id__in=mysessions,
country__in=countries
).order_by("startdate","start_time")
elif regattatype == 'all':
races = VirtualRace.objects.filter(
country__in=countries
).order_by("startdate","start_time")
else:
form = VirtualRaceSelectForm()
return render(request,'virtualevents.html',
{ 'races':races,
'form':form,
}
)
def virtualevent_view(request,id=0):
if not request.user.is_anonymous():
r = getrower(request.user)
else:
r = None
try:
race = VirtualRace.objects.get(id=id)
except VirtualRace.DoesNotExist:
raise Http404("Virtual Race does not exist")
script,div = course_map(race.course)
buttons = []
if not request.user.is_anonymous():
if race_can_register(r,race):
buttons += ['registerbutton']
if race_can_submit(r,race):
buttons += ['submitbutton']
if race_can_resubmit(r,race):
buttons += ['resubmitbutton']
if race_can_withdraw(r,race):
buttons += ['withdrawbutton']
if race_can_edit(r,race):
buttons += ['editbutton']
results = VirtualRaceResult.objects.filter(
race=race,
workout__isnull=False,
).order_by("duration")
# to-do - add DNS
dns = []
if timezone.now() > race.evaluation_closure:
print "aap"
dns = VirtualRaceResult.objects.filter(
race=race,
workout__isnull=True,
)
print dns[0].username,"noot"
records = VirtualRaceResult.objects.filter(
race=race
)
return render(request,'virtualevent.html',
{
'coursescript':script,
'coursediv':div,
'race':race,
'rower':r,
'results':results,
'buttons':buttons,
'dns':dns,
'records':records,
})
@login_required()
def virtualevent_withdraw_view(request,id=0):
r = getrower(request.user)
try:
race = VirtualRace.objects.get(id=id)
except VirtualRace.DoesNotExist:
raise Http404("Virtual Race does not exist")
if race_can_withdraw(r,race):
remove_rower_race(r,race)
messages.info(request,
"You have successfully withdrawn from this race.")
else:
messages.error(request,"You cannot withdraw from this race")
url = reverse(virtualevent_view,
kwargs = {
'id':race.id
})
return HttpResponseRedirect(url)
@login_required()
def virtualevent_register_view(request,id=0):
r = getrower(request.user)
try:
race = VirtualRace.objects.get(id=id)
except VirtualRace.DoesNotExist:
raise Http404("Virtual Race does not exist")
if not race_can_register(r,race):
messages.error(request,"You cannot register for this race")
url = reverse(virtualevent_view,
kwargs = {
'id':race.id
})
return HttpResponseRedirect(url)
# we're still here
if request.method == 'POST':
# process form
form = VirtualRaceResultForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
teamname = cd['teamname']
boattype = cd['boattype']
if not boattype == '1x':
weightcategory = cd['weightcategory']
age = cd['age']
else:
weightcategory = r.weightcategory
age = calculate_age(r.birthdate)
record = VirtualRaceResult(
user=r,
teamname=teamname,
race=race,
username = u'{f} {l}'.format(
f = r.user.first_name,
l = r.user.last_name
),
weightcategory=weightcategory,
duration=datetime.time(0,0),
boattype=boattype,
coursecompleted=False,
sex=r.sex,
age=age
)
record.save()
add_rower_race(r,race)
messages.info(
request,
"You have successfully registered for this race. Good luck!"
)
url = reverse(virtualevent_view,
kwargs = {
'id':race.id
})
return HttpResponseRedirect(url)
else:
initial = {
'age': calculate_age(r.birthdate),
'weightcategory': r.weightcategory,
}
form = VirtualRaceResultForm(initial=initial)
return render(request,'virtualeventregister.html',
{
'form':form,
'race':race,
'rower':r,
})
@login_required()
def virtualevent_create_view(request):
r = getrower(request.user)
if request.method == 'POST':
racecreateform = VirtualRaceForm(request.POST)
if racecreateform.is_valid():
cd = racecreateform.cleaned_data
startdate = cd['startdate']
start_time = cd['start_time']
enddate = cd['enddate']
end_time = cd['end_time']
comment = cd['comment']
course = cd['course']
name = cd['name']
has_registration = cd['has_registration']
registration_closure = cd['registration_closure']
evaluation_closure = cd['evaluation_closure']
contact_phone = cd['contact_phone']
contact_email = cd['contact_email']
# correct times
geocourse = GeoCourse.objects.get(id= course.id)
timezone_str = courses.get_course_timezone(geocourse)
startdatetime = datetime.datetime.combine(startdate,start_time)
enddatetime = datetime.datetime.combine(enddate,end_time)
startdatetime = pytz.timezone(timezone_str).localize(
startdatetime
)
enddatetime = pytz.timezone(timezone_str).localize(
enddatetime
)
evaluation_closure = pytz.timezone(timezone_str).localize(
evaluation_closure.replace(tzinfo=None)
)
try:
registration_closure = pytz.timezone(
timezone_str
).localize(
registration_closure.replace(tzinfo=None)
)
except AttributeError:
pass
vs = VirtualRace(
name=name,
startdate=startdate,
preferreddate = startdate,
start_time = start_time,
enddate=enddate,
end_time=end_time,
course=geocourse,
comment=comment,
sessiontype = 'coursetest',
timezone=timezone_str,
has_registration=has_registration,
evaluation_closure=evaluation_closure,
registration_closure=registration_closure,
contact_phone=contact_phone,
contact_email=contact_email,
country = course.country,
manager=request.user,
)
vs.save()
url = reverse(virtualevents_view)
return HttpResponseRedirect(url)
else:
racecreateform = VirtualRaceForm()
return render(request,'virtualeventcreate.html',
{
'form':racecreateform,
'rower':r,
})
@login_required()
def virtualevent_edit_view(request,id=0):
r = getrower(request.user)
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_date = race.startdate
startdatetime = datetime.datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
if timezone.now() > startdatetime:
messages.error(request,"You cannot edit a race after the start of the race window")
url = reverse(virtualevent_view,
kwargs={
'id':race.id,
})
if request.method == 'POST':
racecreateform = VirtualRaceForm(request.POST,instance=race)
if racecreateform.is_valid():
cd = racecreateform.cleaned_data
res, message = update_virtualrace(race,cd)
if res:
messages.info(request,message)
else:
messages.error(request,message)
url = reverse(virtualevent_view,
kwargs = {
'id':race.id
})
return HttpResponseRedirect(url)
else:
racecreateform = VirtualRaceForm(instance=race)
return render(request,'virtualeventedit.html',
{
'form':racecreateform,
'rower':r,
'race':race,
})
@login_required()
def virtualevent_submit_result_view(request,id=0):
r = getrower(request.user)
try:
race = VirtualRace.objects.get(id=id)
except VirtualRace.DoesNotExist:
raise Http404("Virtual Race does not exist")
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.datetime.combine(start_date, start_time)
startdatetime = pytz.timezone(race.timezone).localize(startdatetime)
end_time = race.end_time
end_date = race.enddate
enddatetime = datetime.datetime.combine(end_date, end_time)
enddatetime = pytz.timezone(race.timezone).localize(enddatetime)
can_submit = race_can_submit(r,race) or race_can_resubmit(r,race)
if not can_submit:
messages.error(request,'You cannot submit a result to this race')
url = reverse(virtualevent_view,
kwargs = {
'id':id
}
)
return HttpResponseRedirect(url)
ws = Workout.objects.filter(
user=r,
startdatetime__gte=startdatetime,
startdatetime__lte=enddatetime,
).order_by("date","startdatetime","id")
initialworkouts = [w.id for w in Workout.objects.filter(
user=r,plannedsession=race
)]
workoutdata = {}
workoutdata['initial'] = []
choices = []
for w in ws:
wtpl = (w.id, w.__unicode__())
choices.append(wtpl)
if w.id in initialworkouts:
workoutdata['initial'].append(w.id)
workoutdata['choices'] = tuple(choices)
if request.method == 'POST':
w_form = WorkoutRaceSelectForm(workoutdata,request.POST)
if w_form.is_valid():
selectedworkout = w_form.cleaned_data['workouts']
else:
selectedworkout = None
for w in ws:
remove_workout_plannedsession(w,race)
if selectedworkout is not None:
for w in ws:
remove_workout_plannedsession(w,race)
delete_race_result(w,race)
workouts = Workout.objects.filter(id=selectedworkout)
result,comments,errors = add_workout_race(workouts,race,r)
for c in comments:
messages.info(request,c)
for er in errors:
messages.error(request,er)
# redirect to race page
url = reverse(virtualevent_view,
kwargs = {
'id':race.id
})
return HttpResponseRedirect(url)
else:
w_form = WorkoutRaceSelectForm(workoutdata=workoutdata)
return render(request,'race_submit.html',
{
'race':race,
'workouts':ws,
'rower':r,
'w_form':w_form,
})
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+4
View File
@@ -39,6 +39,10 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block challenges %}
<a class="button gray small" href="/rowers/virtualevents">Racing</a>
{% endblock %}
{% block content %} {% block content %}
{% endblock %} {% endblock %}
+10 -2
View File
@@ -158,10 +158,12 @@
{% elif user.rower.rowerplan == 'plan' %} {% elif user.rower.rowerplan == 'plan' %}
<h6 class="graytext">Self-Coach</h6> <h6 class="graytext">Self-Coach</h6>
{% else %} {% else %}
<div class="grid_1"><p><a class="button green small" href="/rowers/promembership">Upgrade to Pro</a></p></div> <div class="grid_1"><p><a class="button green small" href="/rowers/promembership"><b>Upgrade</b></a></p></div>
{% endif %} {% endif %}
{% if user.rower.rowerplan == 'basic' and user.rower.protrialexpires|date_dif == 1 %} {% if user.rower.rowerplan == 'basic' and user.rower.protrialexpires|date_dif == 1 %}
<div class="grid_1"><p><a class="button green small" href="/rowers/promembership">Start Free Pro trial</a></p></div> <div class="grid_1"><p><a class="button green small" href="/rowers/promembership">Start Free Pro trial</a></p></div>
{% elif user.rower.rowerplan == 'basic' and user.rower.plantrialexpires|date_dif == 1 %}
<div class="grid_1"><p><a class="button green small" href="/rowers/promembership">Start Free Plan trial</a></p></div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@@ -243,8 +245,14 @@
</p> </p>
{% 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 %}
<p class="successmessage"> <p class="successmessage">
{{ user.rower.protrialexpires|date_dif|ddays }} days left in Pro trial {{ user.rower.protrialexpires|date_dif|ddays }} days left of your Self-Coach trial - Would you like to <a href="/rowers/planmembership">upgrade now?</a>
{% else %}
<p class="successmessage">
{{ user.rower.protrialexpires|date_dif|ddays }} days left of your Pro trial - Would you like to <a href="/rowers/promembership">upgrade now?</a>
{% endif %}
</p> </p>
{% endif %} {% endif %}
{% if user.rower.emailbounced %} {% if user.rower.emailbounced %}
+4
View File
@@ -62,6 +62,10 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block challenges %}
<a class="button gray small" href="/rowers/virtualevents">Racing</a>
{% endblock %}
{% block body_bottom %} {% block body_bottom %}
</div> </div>