Private
Public Access
1
0

Merge branch 'develop' into feature/py38

This commit is contained in:
Sander Roosendaal
2020-06-22 18:46:37 +02:00
16 changed files with 461 additions and 98 deletions

View File

@@ -3,6 +3,7 @@ apipkg==1.5
appdirs==1.4.3 appdirs==1.4.3
arcgis==1.6.0 arcgis==1.6.0
arrow==0.13.1 arrow==0.13.1
asgiref==3.2.7
asn1crypto==0.24.0 asn1crypto==0.24.0
atomicwrites==1.3.0 atomicwrites==1.3.0
attrs==19.1.0 attrs==19.1.0
@@ -12,6 +13,8 @@ billiard==3.6.0.0
bleach==3.1.0 bleach==3.1.0
bokeh==1.0.4 bokeh==1.0.4
boto==2.49.0 boto==2.49.0
boto3==1.14.7
botocore==1.17.7
braintree==3.55.0 braintree==3.55.0
cairocffi==1.0.2 cairocffi==1.0.2
celery==4.3.0 celery==4.3.0
@@ -53,7 +56,7 @@ django-rest-framework==0.1.0
django-rest-swagger==2.2.0 django-rest-swagger==2.2.0
django-rq==1.3.1 django-rq==1.3.1
django-rq-dashboard==0.3.3 django-rq-dashboard==0.3.3
django-ses==0.8.10 django-ses==1.0.0
django-shell-plus==1.1.7 django-shell-plus==1.1.7
django-social-share==1.3.2 django-social-share==1.3.2
django-suit==0.2.26 django-suit==0.2.26
@@ -96,6 +99,7 @@ itypes==1.1.0
jedi==0.13.3 jedi==0.13.3
jeepney==0.4 jeepney==0.4
Jinja2==2.10 Jinja2==2.10
jmespath==0.10.0
json5==0.8.5 json5==0.8.5
jsonschema==3.0.1 jsonschema==3.0.1
jupyter==1.0.0 jupyter==1.0.0
@@ -107,7 +111,7 @@ jupyterlab-server==0.3.0
keyring==18.0.0 keyring==18.0.0
kiwisolver==1.0.1 kiwisolver==1.0.1
kombu==4.5.0 kombu==4.5.0
llvmlite==0.33.0 llvmlite==0.30.0
lxml==4.3.2 lxml==4.3.2
Markdown==3.0.1 Markdown==3.0.1
MarkupSafe==1.1.1 MarkupSafe==1.1.1
@@ -148,7 +152,7 @@ protobuf==3.11.1
psycopg2==2.8.1 psycopg2==2.8.1
ptyprocess==0.6.0 ptyprocess==0.6.0
py==1.8.0 py==1.8.0
pyarrow==0.17.1 pyarrow==0.15.0
pycairo==1.19.0 pycairo==1.19.0
pycparser==2.19 pycparser==2.19
Pygments==2.3.1 Pygments==2.3.1
@@ -179,6 +183,7 @@ rowingdata==2.9.1
rowingphysics==0.5.0 rowingphysics==0.5.0
rq==0.13.0 rq==0.13.0
rules==2.1 rules==2.1
s3transfer==0.3.3
scipy==1.2.1 scipy==1.2.1
SecretStorage==3.1.1 SecretStorage==3.1.1
Send2Trash==1.5.0 Send2Trash==1.5.0
@@ -188,7 +193,7 @@ simplejson==3.16.0
six==1.12.0 six==1.12.0
soupsieve==1.8 soupsieve==1.8
SQLAlchemy==1.3.1 SQLAlchemy==1.3.1
sqlparse==0.3.0 sqlparse==0.3.1
stravalib==0.10.2 stravalib==0.10.2
termcolor==1.1.0 termcolor==1.1.0
terminado==0.8.1 terminado==0.8.1

View File

@@ -2,7 +2,7 @@ from __future__ import absolute_import
from __future__ import division from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import time
@@ -19,7 +19,7 @@ class InvalidTrajectoryError(Exception):
def __str__(self): def __str__(self):
return repr(self.value) return repr(self.value)
def time_in_path(df,p,maxmin='max',getall=False): def time_in_path(df,p,maxmin='max',getall=False,name='unknown',logfile=None):
if df.empty: if df.empty:
return 0 return 0
@@ -38,25 +38,64 @@ def time_in_path(df,p,maxmin='max',getall=False):
if len(df[b==2]): if len(df[b==2]):
if logfile is not None:
t = time.localtime()
timestamp = time.strftime('%b-%d-%Y_%H%M', t)
with open(logfile,'a') as f:
f.write('\n')
f.write(timestamp)
f.write(' ')
f.write(name)
f.write(' ')
f.write(maxmin)
f.write(' ')
f.write(str(getall))
f.write(' ')
f.write(str(len(df[b==2])))
f.write(' ')
if len(df[b==2])>1:
f.write(' passes found')
else:
f.write(' pass found')
if getall: if getall:
return df[b==2]['time'],df[b==2]['cum_dist'] return df[b==2]['time'],df[b==2]['cum_dist']
else: else:
return df[b==2]['time'].min(),df[b==2]['cum_dist'].min() return df[b==2]['time'].min(),df[b==2]['cum_dist'].min()
if logfile is not None:
t = time.localtime()
timestamp = time.strftime('%b-%d-%Y_%H%M', t)
with open(logfile,'a') as f:
f.write('\n')
f.write(timestamp)
f.write(' ')
f.write(name)
f.write(' ')
f.write(maxmin)
f.write(' ')
f.write(str(getall))
f.write(' ')
f.write(str(len(df[b==2])))
f.write(' ')
f.write(' pass not found')
raise InvalidTrajectoryError("Trajectory doesn't go through path") raise InvalidTrajectoryError("Trajectory doesn't go through path")
return 0 return 0
def coursetime_first(data,paths): def coursetime_first(data,paths,polygons=[],logfile=None):
entrytime = data['time'].max() entrytime = data['time'].max()
entrydistance = data['cum_dist'].max() entrydistance = data['cum_dist'].max()
coursecompleted = False coursecompleted = False
if len(polygons) == 0:
polygons = [(0,str(i)) for i in range(len(paths))]
try: try:
entrytime,entrydistance = time_in_path(data,paths[0],maxmin='max') entrytime,entrydistance = time_in_path(data,paths[0],maxmin='max',name=polygons[0][1],logfile=logfile)
coursecompleted = True coursecompleted = True
except InvalidTrajectoryError: except InvalidTrajectoryError:
entrytime = data['time'].max() entrytime = data['time'].max()
@@ -64,12 +103,15 @@ def coursetime_first(data,paths):
coursecompleted = False coursecompleted = False
return entrytime, entrydistance, coursecompleted return entrytime, entrydistance, coursecompleted
def coursetime_paths(data,paths,finalmaxmin='min'): def coursetime_paths(data,paths,finalmaxmin='min',polygons=[],logfile=None):
entrytime = data['time'].max() entrytime = data['time'].max()
entrydistance = data['cum_dist'].max() entrydistance = data['cum_dist'].max()
coursecompleted = False coursecompleted = False
if len(polygons) == 0:
polygons = [(0,str(i)) for i in range(len(paths))]
# corner case - empty list of paths # corner case - empty list of paths
if len(paths) == 0: if len(paths) == 0:
return 0,True return 0,True
@@ -80,7 +122,7 @@ def coursetime_paths(data,paths,finalmaxmin='min'):
( (
entrytime, entrytime,
entrydistance entrydistance
) = time_in_path(data,paths[0],maxmin=finalmaxmin) ) = time_in_path(data,paths[0],maxmin=finalmaxmin,name = polygons[0][1],logfile=logfile)
coursecompleted = True coursecompleted = True
except InvalidTrajectoryError: except InvalidTrajectoryError:
entrytime = data['time'].max() entrytime = data['time'].max()
@@ -90,7 +132,7 @@ def coursetime_paths(data,paths,finalmaxmin='min'):
if len(paths) > 1: if len(paths) > 1:
try: try:
time,dist = time_in_path(data, paths[0]) time,dist = time_in_path(data, paths[0],name=polygons[0][1],logfile=logfile)
data = data[data['time']>time] data = data[data['time']>time]
data['time'] = data['time']-time data['time'] = data['time']-time
data['cum_dist'] = data['cum_dist']-dist data['cum_dist'] = data['cum_dist']-dist
@@ -98,7 +140,7 @@ def coursetime_paths(data,paths,finalmaxmin='min'):
timenext, timenext,
distnext, distnext,
coursecompleted coursecompleted
) = coursetime_paths(data,paths[1:]) ) = coursetime_paths(data,paths[1:],polygons=polygons[1:],logfile=logfile)
return time+timenext, dist+distnext,coursecompleted return time+timenext, dist+distnext,coursecompleted
except InvalidTrajectoryError: except InvalidTrajectoryError:
entrytime = data['time'].max() entrytime = data['time'].max()

View File

@@ -53,7 +53,7 @@ from django.contrib.staticfiles import finders
def textify(html): def textify(html):
# Remove html tags and continuous whitespaces # Remove html tags and continuous whitespaces
text_only = re.sub('[ \t]+', ' ', strip_tags(html)) text_only = re.sub('[ \t]+', ' ', strip_tags(html))
# Strip single spaces in the beginning of each line # Strip single spaces in the beginning of each line
return text_only.replace('\n ', '\n').strip() return text_only.replace('\n ', '\n').strip()
@@ -61,7 +61,7 @@ def textify(html):
def htmlstripnobr(html): def htmlstripnobr(html):
safe_html = re.sub('[ \t]+', ' ', strip_tags(html)) safe_html = re.sub('[ \t]+', ' ', strip_tags(html))
return safe_html return safe_html
def htmlstrip(html): def htmlstrip(html):
safe_html = re.sub('[ \t]+', ' ', strip_tags(html)) safe_html = re.sub('[ \t]+', ' ', strip_tags(html))
return newlinetobr(safe_html) return newlinetobr(safe_html)
@@ -80,11 +80,17 @@ def send_template_email(from_email,to_email,subject,
text_content = textify(html_content) text_content = textify(html_content)
# html_content = newlinetobr(html_content) # html_content = newlinetobr(html_content)
if 'cc' in kwargs:
if 'bcc' in kwargs and 'cc' in kwargs:
msg = EmailMultiAlternatives(subject, text_content, from_email, to_email,cc=kwargs['cc'],
bcc=kwargs['bcc'])
elif 'bcc' in kwargs:
msg = EmailMultiAlternatives(subject, text_content, from_email, to_email,bcc=kwargs['bcc'])
elif 'cc' in kwargs:
msg = EmailMultiAlternatives(subject, text_content, from_email, to_email,cc=kwargs['cc']) msg = EmailMultiAlternatives(subject, text_content, from_email, to_email,cc=kwargs['cc'])
else: else:
msg = EmailMultiAlternatives(subject, text_content, from_email, to_email) msg = EmailMultiAlternatives(subject, text_content, from_email, to_email)
msg.attach_alternative(html_content, "text/html") msg.attach_alternative(html_content, "text/html")
if 'attach_file' in kwargs: if 'attach_file' in kwargs:
@@ -106,8 +112,8 @@ def send_template_email(from_email,to_email,subject,
else: else:
emailbounced = False emailbounced = False
if not emailbounced: if not emailbounced:
res = msg.send() res = msg.send()
else: else:

View File

@@ -124,7 +124,7 @@ class EmailForm(forms.Form):
subject = forms.CharField(max_length=255) subject = forms.CharField(max_length=255)
message = forms.CharField(widget=forms.Textarea()) message = forms.CharField(widget=forms.Textarea())
disqualificationreasons = ( disqualificationreasons = (
('noimage','No monitor screenshot or data evidence was included'), ('noimage','No monitor screenshot or data evidence was included'),
@@ -386,7 +386,7 @@ class UploadOptionsForm(forms.Form):
makeprivate = forms.BooleanField(initial=False,required=False, makeprivate = forms.BooleanField(initial=False,required=False,
label='Make Workout Private') label='Make Workout Private')
submitrace = forms.ModelChoiceField(queryset=VirtualRace.objects.all(), submitrace = forms.ChoiceField(
label='Submit as challenge Result', label='Submit as challenge Result',
required=False) required=False)
@@ -404,6 +404,7 @@ class UploadOptionsForm(forms.Form):
r = Rower.objects.get(user=self.request.user) r = Rower.objects.get(user=self.request.user)
races = VirtualRace.objects.filter( races = VirtualRace.objects.filter(
registration_closure__gt=timezone.now()) registration_closure__gt=timezone.now())
registrations = IndoorVirtualRaceResult.objects.filter( registrations = IndoorVirtualRaceResult.objects.filter(
race__in = races, race__in = races,
userid = r.id) userid = r.id)
@@ -413,25 +414,41 @@ class UploadOptionsForm(forms.Form):
userid = r.id, userid = r.id,
) )
raceids = [r.race.id for r in registrations] choices1 = [(r.id,str(r)) for r in registrations]
raceids2 = [r.race.id for r in registrations2] choices2 = [(r.id,str(r)) for r in registrations2]
choices3 = [(0,'---')]
raceids = raceids+raceids2 noregistrations = []
for ra in VirtualRace.objects.filter(registration_closure__gt=timezone.now(),sessiontype='race'):
rs = VirtualRaceResult.objects.filter(race = ra,userid=r.id)
if rs.count()==0:
noregistrations.append((-ra.id,ra.name))
for ra in VirtualRace.objects.filter(registration_closure__gt=timezone.now(),sessiontype='indoorrace'):
rs = IndoorVirtualRaceResult.objects.filter(race = ra,userid=r.id)
if rs.count()==0:
noregistrations.append((-ra.id,ra.name))
races = VirtualRace.objects.filter( choices = choices3+choices1+choices2+noregistrations
id__in=raceids
)
if int(raceid) in [r.id for r in races]:
therace = VirtualRace.objects.get(id=raceid)
if therace.sessiontype == 'race':
registrations = VirtualRaceResult.objects.filter(race=therace,userid=r.id)
else:
registrations = IndoorVirtualRaceResult.objects.filter(race=therace,userid=r.id)
if registrations.count()==0:
race = VirtualRace.objects.get(id=raceid)
choices = [(-int(raceid),race.name)]
else:
choices = [(r.id,str(r)) for r in registrations]
choices = choices+[(0,'---')]
if races: if races:
self.fields['submitrace'].queryset = races self.fields['submitrace'].choices = choices
else: else:
del self.fields['submitrace'] del self.fields['submitrace']
if int(raceid) in raceids:
self.fields['submitrace'].initial = VirtualRace.objects.get(id=raceid)
# The form to indicate additional actions to be performed immediately # The form to indicate additional actions to be performed immediately
# after a successful upload. This version allows the Team manager to select # after a successful upload. This version allows the Team manager to select

View File

@@ -5481,8 +5481,11 @@ def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line',
else: else:
windowsize = 1 windowsize = 1
if windowsize >= 3 and windowsize < len(group['y']): if windowsize > 3 and windowsize < len(group['y']):
group['y'] = savgol_filter(group['y'],windowsize,3) try:
group['y'] = savgol_filter(group['y'],windowsize,3)
except ValueError:
pass
ylabel = Label(x=100,y=60+nrworkouts*20-20*cntr, ylabel = Label(x=100,y=60+nrworkouts*20-20*cntr,
x_units='screen',y_units='screen', x_units='screen',y_units='screen',

View File

@@ -2643,7 +2643,11 @@ class VirtualRaceForm(ModelForm):
enddatetime enddatetime
) )
registration_closure = cd['registration_closure'] try:
registration_closure = cd['registration_closure']
except KeyError:
registration_closure = enddatetime+datetime.timedelta(days=1)
cd['registration_closure'] = registration_closure
registration_form = cd['registration_form'] registration_form = cd['registration_form']
@@ -3120,6 +3124,8 @@ class CourseTestResult(models.Model):
distance = models.IntegerField(default=0) distance = models.IntegerField(default=0)
coursecompleted = models.BooleanField(default=False) coursecompleted = models.BooleanField(default=False)
class IndoorVirtualRaceResultForm(ModelForm): class IndoorVirtualRaceResultForm(ModelForm):
class Meta: class Meta:
model = IndoorVirtualRaceResult model = IndoorVirtualRaceResult

View File

@@ -45,7 +45,7 @@ from rowers.models import (
GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle, GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle,
TrainingPlan,PlannedSession,VirtualRaceResult,CourseTestResult, TrainingPlan,PlannedSession,VirtualRaceResult,CourseTestResult,
get_course_timezone, IndoorVirtualRaceResult,VirtualRace,createmacrofillers, get_course_timezone, IndoorVirtualRaceResult,VirtualRace,createmacrofillers,
createmesofillers,createmicrofillers, createmesofillers,createmicrofillers,CourseStandard,
) )
from rowers.courses import get_time_course from rowers.courses import get_time_course
@@ -1026,7 +1026,13 @@ def race_can_edit(r,race):
startdatetime = pytz.timezone(race.timezone).localize( startdatetime = pytz.timezone(race.timezone).localize(
startdatetime startdatetime
) )
if timezone.now()<startdatetime: end_time = race.end_time
end_date = race.enddate
enddatetime = datetime.combine(end_date,end_time)
enddatetime = pytz.timezone(race.timezone).localize(
enddatetime
)
if timezone.now()<enddatetime:
return True return True
else: else:
return False return False
@@ -1035,8 +1041,8 @@ def race_can_edit(r,race):
return False return False
def race_can_submit(r,race): def race_can_submit(r,race):
if r not in race.rower.all(): #if r not in race.rower.all():
return False # return False
start_time = race.start_time start_time = race.start_time
start_date = race.startdate start_date = race.startdate
@@ -1335,8 +1341,83 @@ def remove_rower_race(r,race,recordid=None):
return 1 return 1
def default_class(r,w,race):
if r.birthdate:
age = calculate_age(r.birthdate)
else:
age = 25
sex = r.sex
if sex=='not specified':
sex='male'
if w is not None:
boatclass = w.workouttype
boattype = w.boattype
adaptiveclass = w.adaptiveclass
weightclass = w.weightcategory
else:
if race.sessiontype == 'race':
boatclass = 'water'
else:
boatclass = 'rower'
boattype = '1x'
adaptiveclass = 'None'
weightclass = 'hwt'
if race.coursestandards:
standards = CourseStandard.objects.filter(
agemin__lt=age,agemax__gt=age,
boatclass=boatclass,
adaptiveclass=adaptiveclass,
boattype=boattype,
weightclass=weightclass,
sex=sex,
).order_by("agemax","-agemin","boattype","sex")
if standards.count()==0:
# omit weight
standards = CourseStandard.objects.filter(
agemin__lt=age,agemax__gt=age,
boatclass=boatclass,
adaptiveclass=adaptiveclass,
boattype=boattype,
).order_by(
"agemax","-agemin","boattype","sex","weightcategory",
"referencespeed"
)
if standards.count()==0:
standards = CourseStandard.objects.filter(
agemin__lt=age,agemax__gt=age,
boattype=boattype
).order_by(
"agemax","-agemin","boattype","sex",
"weightcategory","referencespeed")
if standards.count()==0:
standards = CourseStandard.objects.filter(
agemin__lt=age,agemax__gt=age
).order_by(
"agemax","-agemin","boattype","sex",
"weightcategory","referencespeed")
if standards.count()==0:
# boolean, boattype, boatclass, adaptiveclass, weightclass, sex, coursestandard,
return False,'1x','water',None,'hwt','male',None
if standards.count()>0:
# find optimum standard
s = standards[0]
return True,s.boattype,s.boatclass,s.adaptiveclass,s.weightclass,s.sex,s
# No Course Standard
return True,boattype,boatclass,adaptiveclass,weightclass,sex,None
# Low Level functions - to be called by higher level methods # Low Level functions - to be called by higher level methods
def add_workout_indoorrace(ws,race,r,recordid=0): def add_workout_indoorrace(ws,race,r,recordid=0,doregister=False):
result = 0 result = 0
comments = [] comments = []
errors = [] errors = []
@@ -1383,12 +1464,34 @@ def add_workout_indoorrace(ws,race,r,recordid=0):
else: else:
age = None age = None
record = IndoorVirtualRaceResult.objects.get( try:
userid=r.id, record = IndoorVirtualRaceResult.objects.get(
race=race, userid=r.id,
id=recordid race=race,
) id=recordid
)
except IndoorVirtualRaceResult.DoesNotExist:
if doregister:
hasinitial,boattype,boatclass,adaptiveclass,weightclass,sex,initialcategory = default_class(r,ws[0],race)
if hasinitial:
record = IndoorVirtualRaceResult(
userid = r.id,
username = r.user.first_name+' '+r.user.last_name,
weightcategory=weightclass,
adaptiveclass=adaptiveclass,
race=race,
boatclass=boatclass,
sex=sex,
age = age,
entrycategory=initialcategory,
)
record.save()
else:
errors.append("Unable to find a suitable start category")
return result,comments,errors,0
else:
errors.append("Couldn't find this entry")
return result,comments,errors,0
records = IndoorVirtualRaceResult.objects.filter( records = IndoorVirtualRaceResult.objects.filter(
userid=r.id, userid=r.id,
@@ -1396,9 +1499,11 @@ def add_workout_indoorrace(ws,race,r,recordid=0):
workoutid = ws[0].id workoutid = ws[0].id
) )
if not record:
errors.append("Couldn't find this entry")
return result,comments,errors,0
if race.sessionmode == 'distance': if race.sessionmode == 'distance':
if ws[0].distance != race.sessionvalue: if ws[0].distance != race.sessionvalue:
@@ -1465,7 +1570,7 @@ def add_workout_indoorrace(ws,race,r,recordid=0):
return result,comments,errors,0 return result,comments,errors,0
def add_workout_race(ws,race,r,splitsecond=0,recordid=0): def add_workout_race(ws,race,r,splitsecond=0,recordid=0,doregister=False):
result = 0 result = 0
comments = [] comments = []
errors = [] errors = []
@@ -1511,11 +1616,35 @@ def add_workout_race(ws,race,r,splitsecond=0,recordid=0):
else: else:
age = None age = None
record = VirtualRaceResult.objects.get( try:
userid=r.id, record = VirtualRaceResult.objects.get(
race=race, userid=r.id,
id=recordid race=race,
) id=recordid
)
except VirtualRaceResult.DoesNotExist:
if doregister:
hasinitial,boattype,boatclass,adaptiveclass,weightclass,sex,initialcategory = default_class(r,ws[0],race)
if hasinitial:
record = VirtualRaceResult(
userid = r.id,
username = r.user.first_name+' '+r.user.last_name,
weightcategory=weightclass,
adaptiveclass=adaptiveclass,
race=race,
boatclass=boatclass,
boattype=boattype,
sex=sex,
age = age,
entrycategory=initialcategory,
)
record.save()
else:
errors.append("Unable to find a suitable start category")
return result,comments,errors,0
else:
errors.append("Couldn't find this entry")
return result,comments,errors,0
records = VirtualRaceResult.objects.filter( records = VirtualRaceResult.objects.filter(
userid=r.id, userid=r.id,
@@ -1523,7 +1652,7 @@ def add_workout_race(ws,race,r,splitsecond=0,recordid=0):
workoutid = ws[0].id workoutid = ws[0].id
) )
if not record: if not record and not doregister:
errors.append("Couldn't find this entry") errors.append("Couldn't find this entry")
return result,comments,errors,0 return result,comments,errors,0
@@ -1583,6 +1712,8 @@ def add_workout_race(ws,race,r,splitsecond=0,recordid=0):
referencespeed=record.referencespeed,coursedistance=race.course.distance referencespeed=record.referencespeed,coursedistance=race.course.distance
) )
comments.append('We are now checking adherence to the race course. This may take a few minutes to complete')
add_workouts_plannedsession(ws,race,r) add_workouts_plannedsession(ws,race,r)

View File

@@ -349,6 +349,8 @@ def handle_check_race_course(self,
recordid,useremail,userfirstname, recordid,useremail,userfirstname,
**kwargs): **kwargs):
logfile = 'courselog_{workoutid}_{courseid}.log'.format(workoutid=workoutid,courseid=courseid)
if 'debug' in kwargs: if 'debug' in kwargs:
debug = kwargs['debug'] debug = kwargs['debug']
else: else:
@@ -420,10 +422,11 @@ def handle_check_race_course(self,
engine = create_engine(database_url, echo=False) engine = create_engine(database_url, echo=False)
# get polygons # get polygons
query = "SELECT id FROM rowers_geopolygon WHERE course_id = {courseid} ORDER BY order_in_course ASC".format( query = "SELECT id,name FROM rowers_geopolygon WHERE course_id = {courseid} ORDER BY order_in_course ASC".format(
courseid=courseid courseid=courseid
) )
with engine.connect() as conn, conn.begin(): with engine.connect() as conn, conn.begin():
result = conn.execute(query) result = conn.execute(query)
polygons = result.fetchall() polygons = result.fetchall()
@@ -441,7 +444,16 @@ def handle_check_race_course(self,
# check how many times went through start polygon # check how many times went through start polygon
try: try:
entrytimes,entrydistances = time_in_path(rowdata,paths[0],maxmin='max',getall=True) entrytimes,entrydistances = time_in_path(rowdata,paths[0],maxmin='max',getall=True,
name=polygons[0].name,logfile=logfile)
with open(logfile,'a') as f:
t = time.localtime()
timestamp = time.strftime('%b-%d-%Y_%H%M', t)
f.write('\n')
f.write(timestamp)
f.write(' ')
f.write('Found {n} entrytimes'.format(n=len(entrytimes)))
except InvalidTrajectoryError: except InvalidTrajectoryError:
entrytimes = [] entrytimes = []
entrydistances = [] entrydistances = []
@@ -457,7 +469,13 @@ def handle_check_race_course(self,
endseconds = [] endseconds = []
for startt in entrytimes: for startt in entrytimes:
with open(logfile,'a') as f:
t = time.localtime()
timestamp = time.strftime('%b-%d-%Y_%H%M', t)
f.write('\n')
f.write(timestamp)
f.write(' ')
f.write('Path starting at {t}'.format(t=startt))
rowdata2 = rowdata[rowdata['time']>(startt-10.)] rowdata2 = rowdata[rowdata['time']>(startt-10.)]
( (
@@ -465,13 +483,13 @@ def handle_check_race_course(self,
coursemeters, coursemeters,
coursecompleted, coursecompleted,
) = coursetime_paths(rowdata2,paths) ) = coursetime_paths(rowdata2,paths,polygons=polygons,logfile=logfile)
( (
coursetimefirst, coursetimefirst,
coursemetersfirst, coursemetersfirst,
firstcompleted firstcompleted
) = coursetime_first( ) = coursetime_first(
rowdata2,paths) rowdata2,paths,polygons=polygons,logfile=logfile)
@@ -541,6 +559,8 @@ def handle_check_race_course(self,
conn.close() conn.close()
engine.dispose() engine.dispose()
os.remove(logfile)
return 1 return 1
else: else:
@@ -574,9 +594,11 @@ def handle_check_race_course(self,
# send email # send email
handle_sendemail_coursefail( handle_sendemail_coursefail(
useremail,userfirstname, useremail,userfirstname,logfile
) )
os.remove(logfile)
return 2 return 2
return 0 return 0
@@ -1168,7 +1190,7 @@ def handle_sendemail_raceregistration(
return 1 return 1
def handle_sendemail_coursefail( def handle_sendemail_coursefail(
useremail, username, **kwargs): useremail, username, logfile, **kwargs):
if 'debug' in kwargs: if 'debug' in kwargs:
debug = kwargs['debug'] debug = kwargs['debug']
@@ -1186,7 +1208,10 @@ def handle_sendemail_coursefail(
res = send_template_email(from_email,[useremail], res = send_template_email(from_email,[useremail],
subject, subject,
'trajectoryfailemail.html', 'trajectoryfailemail.html',
d,**kwargs) d,
cc=['info@rowsandall.com'],
attach_file=logfile,
**kwargs)
return 1 return 1
@@ -2421,8 +2446,8 @@ def handle_send_template_email(template,email,fromemail,rowername,
} }
res = send_template_email('Rowsandall <info@rowsandall.com>', res = send_template_email('Rowsandall <info@rowsandall.com>',
fullemail,subject, ['info@rowsandall.com'],subject,
template,d,cc=[fromemail],**kwargs) template,d,cc=[fromemail],bcc=fullemail,**kwargs)
@app.task @app.task
def handle_sendemail_message(email,fromemail,rowername,message,teamname,managername, def handle_sendemail_message(email,fromemail,rowername,message,teamname,managername,

View File

@@ -112,11 +112,11 @@
<i class="fas fa-map fa-fw"></i>&nbsp;Map View <i class="fas fa-map fa-fw"></i>&nbsp;Map View
</a> </a>
</li> </li>
{% if course.manager == rower %}
<li id="course-emailkml"> <li id="course-emailkml">
<a href="/rowers/courses/{{ course.id }}/downloadkml/"> <a href="/rowers/courses/{{ course.id }}/downloadkml/">
<i class="fas fa-globe-americas fa-fw"></i>&nbsp;Download as KML</a> <i class="fas fa-globe-americas fa-fw"></i>&nbsp;Download as KML</a>
</li> </li>
{% if course.manager == rower %}
<li id="course-editview"> <li id="course-editview">
<a href="/rowers/courses/{{ course.id }}/edit/"> <a href="/rowers/courses/{{ course.id }}/edit/">
<i class="fas fa-pencil-alt fa-fw"></i>&nbsp;Edit</a> <i class="fas fa-pencil-alt fa-fw"></i>&nbsp;Edit</a>

View File

@@ -16,6 +16,10 @@
contact me by reply to this email. contact me by reply to this email.
</p> </p>
<p>
The attachment contains debugging information for the site owners.
</p>
<p> <p>
Best Regards, the Rowsandall Team Best Regards, the Rowsandall Team
</p> </p>

View File

@@ -163,6 +163,7 @@
Registered users of rowsandall.com can participate in this challenge. Registered users of rowsandall.com can participate in this challenge.
Participation is free, unless specified differently in the race comment above. Participation is free, unless specified differently in the race comment above.
{% if race.sessiontype == 'race' %} {% if race.sessiontype == 'race' %}
Register to let others know you plan to do this challenge:
<a href="/rowers/virtualevent/{{ race.id }}/register"><h3>Register</h3></a> <a href="/rowers/virtualevent/{{ race.id }}/register"><h3>Register</h3></a>
{% else %} {% else %}
<a href="/rowers/virtualevent/{{ race.id }}/registerindoor"><h3>Register</h3></a> <a href="/rowers/virtualevent/{{ race.id }}/registerindoor"><h3>Register</h3></a>
@@ -183,6 +184,8 @@
{% for button in buttons %} {% for button in buttons %}
{% if button == 'registerbutton' %} {% if button == 'registerbutton' %}
<p> <p>
Register to let others know you plan to do this challenge. This also give you the option to
select your entry category:
{% if race.sessiontype == 'race' %} {% if race.sessiontype == 'race' %}
<a href="/rowers/virtualevent/{{ race.id }}/register"><h3>Register</h3></a> <a href="/rowers/virtualevent/{{ race.id }}/register"><h3>Register</h3></a>
{% else %} {% else %}

View File

@@ -526,6 +526,11 @@ def statsdata(workouts, options):
datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly) datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
try:
datadf['pace'] = datadf['pace']/1000.
except KeyError:
pass
# Create stats # Create stats
stats = {} stats = {}
# fielddict.pop('workoutstate') # fielddict.pop('workoutstate')
@@ -4169,6 +4174,10 @@ def cumstats(request,userid=0,
datadf,extracols = dataprep.read_cols_df_sql(ids,fieldlist) datadf,extracols = dataprep.read_cols_df_sql(ids,fieldlist)
datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly) datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
try:
datadf['pace'] = datadf['pace']/1000.
except KeyError:
pass
request.session['rowerid'] = r.id request.session['rowerid'] = r.id

View File

@@ -81,7 +81,10 @@ def plannedsession_comment_view(request,id=0,userid=0):
rowers = {r.user for r in ps.rower.all()} rowers = {r.user for r in ps.rower.all()}
commenters = set(list(commenters)+list(rowers)) commenters = set(list(commenters)+list(rowers))
for u in commenters: for u in commenters:
a_messages.info(u,message) try:
a_messages.info(u,message)
except ValueError:
pass
if u != request.user and u != r.user: if u != request.user and u != r.user:
ocr = Rower.objects.get(user=u) ocr = Rower.objects.get(user=u)
res = myqueue(queuelow, res = myqueue(queuelow,

View File

@@ -746,6 +746,7 @@ def virtualevent_disqualify_view(request,id=0,recordid=0):
r = getrower(request.user) r = getrower(request.user)
race = get_object_or_404(VirtualRace,pk=id) race = get_object_or_404(VirtualRace,pk=id)
raceid = race.id
if race.sessiontype == 'race': if race.sessiontype == 'race':
@@ -1223,7 +1224,6 @@ def virtualevent_view(request,id=0):
comments = PlannedSessionComment.objects.filter(plannedsession=race).order_by("created") comments = PlannedSessionComment.objects.filter(plannedsession=race).order_by("created")
return render(request,'virtualevent.html', return render(request,'virtualevent.html',
{ {
'coursescript':script, 'coursescript':script,
@@ -1500,6 +1500,7 @@ def virtualevent_addboat_view(request,id=0):
raise Http404("Virtual Challenge does not exist") raise Http404("Virtual Challenge does not exist")
categories = None categories = None
hasinitial,boattype,boatclass,adaptiveclass,weightclass,sex,initialcategory = default_class(r,None,race)
if race.coursestandards is not None: if race.coursestandards is not None:
categories = CourseStandard.objects.filter( categories = CourseStandard.objects.filter(
standardcollection=race.coursestandards).order_by("name") standardcollection=race.coursestandards).order_by("name")
@@ -1701,11 +1702,22 @@ def virtualevent_addboat_view(request,id=0):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
else: else:
initial = { if hasinitial:
'age': calculate_age(r.birthdate), initial = {
'weightcategory': r.weightcategory, 'age': calculate_age(r.birthdate),
'adaptiveclass': r.adaptiveclass, 'boattype':boattype,
'boatclass':boatclass,
'adaptiveclass':adaptiveclass,
'weightclass':weightclass,
'sex':sex,
'entrycategory':initialcategory,
} }
else:
initial = {
'age': calculate_age(r.birthdate),
'weightcategory': r.weightcategory,
'adaptiveclass': r.adaptiveclass,
}
categories = None categories = None
if race.coursestandards is not None: if race.coursestandards is not None:
@@ -1775,6 +1787,7 @@ def virtualevent_register_view(request,id=0):
raise Http404("Virtual Challenge does not exist") raise Http404("Virtual Challenge does not exist")
categories = None categories = None
hasinitial,boattype,boatclass,adaptiveclass,weightclass,sex,initialcategory = default_class(r,None,race)
if race.coursestandards is not None: if race.coursestandards is not None:
categories = CourseStandard.objects.filter( categories = CourseStandard.objects.filter(
standardcollection=race.coursestandards).order_by("name") standardcollection=race.coursestandards).order_by("name")
@@ -1938,11 +1951,22 @@ def virtualevent_register_view(request,id=0):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
else: else:
initial = { if hasinitial:
'age': calculate_age(r.birthdate), initial = {
'weightcategory': r.weightcategory, 'age': calculate_age(r.birthdate),
'adaptiveclass': r.adaptiveclass, 'boattype':boattype,
'boatclass':boatclass,
'adaptiveclass':adaptiveclass,
'weightclass':weightclass,
'sex':sex,
'entrycategory':initialcategory,
} }
else:
initial = {
'age': calculate_age(r.birthdate),
'weightcategory': r.weightcategory,
'adaptiveclass': r.adaptiveclass,
}
categories = None categories = None
if race.coursestandards is not None: if race.coursestandards is not None:
@@ -2565,8 +2589,15 @@ def virtualevent_edit_view(request,id=0):
startdatetime startdatetime
) )
if timezone.now() > startdatetime: end_time = race.end_time
messages.error(request,"You cannot edit a race after the start of the race window") end_date = race.enddate
enddatetime = datetime.datetime.combine(end_date,end_time)
enddatetime = pytz.timezone(race.timezone).localize(
enddatetime
)
if timezone.now() > enddatetime:
messages.error(request,"You cannot edit a race after the end of the race window")
url = reverse('virtualevent_view', url = reverse('virtualevent_view',
kwargs={ kwargs={
'id':race.id, 'id':race.id,
@@ -2658,8 +2689,15 @@ def indoorvirtualevent_edit_view(request,id=0):
startdatetime startdatetime
) )
if timezone.now() > startdatetime: end_time = race.end_time
messages.error(request,"You cannot edit a race after the start of the race window") end_date = race.enddate
enddatetime = datetime.datetime.combine(end_date,end_time)
enddatetime = pytz.timezone(race.timezone).localize(
enddatetime
)
if timezone.now() > enddatetime:
messages.error(request,"You cannot edit a race after the end of the race window")
url = reverse('virtualevent_view', url = reverse('virtualevent_view',
kwargs={ kwargs={
'id':race.id, 'id':race.id,
@@ -2773,6 +2811,29 @@ def virtualevent_submit_result_view(request,id=0,workoutid=0):
race=race race=race
) )
if records.count() == 0:
hasinitial,boattype,boatclass,adaptiveclass,weightclass,sex,initialcategory = default_class(r,None,race)
if not hasinitial:
messages.error(request,"Sorry, you have to register first")
url = reverse('virtualevent_view',
kwargs = {
'id':id,
})
return HttpResponseRedirect(url)
record = resultobj(
userid = r.id,
username = r.user.first_name+' '+r.user.last_name,
weightcategory=weightclass,
adaptiveclass=adaptiveclass,
race=race,
boatclass=boatclass,
sex=sex,
age=calculate_age(r.birthdate),
entrycategory=initialcategory,
)
record.save()
records = [record]
entrychoices = [] entrychoices = []

View File

@@ -4909,7 +4909,7 @@ def workout_upload_view(request,
notes = form.cleaned_data['notes'] notes = form.cleaned_data['notes']
offline = form.cleaned_data['offline'] offline = form.cleaned_data['offline']
race = None registrationid = 0
if optionsform.is_valid(): if optionsform.is_valid():
make_plot = optionsform.cleaned_data['make_plot'] make_plot = optionsform.cleaned_data['make_plot']
plottype = optionsform.cleaned_data['plottype'] plottype = optionsform.cleaned_data['plottype']
@@ -4923,9 +4923,9 @@ def workout_upload_view(request,
landingpage = optionsform.cleaned_data['landingpage'] landingpage = optionsform.cleaned_data['landingpage']
try: try:
race = optionsform.cleaned_data['submitrace'] registrationid = optionsform.cleaned_data['submitrace']
except KeyError: except KeyError:
race = None registrationid = 0
uploadoptions = { uploadoptions = {
'makeprivate':makeprivate, 'makeprivate':makeprivate,
@@ -5113,17 +5113,58 @@ def workout_upload_view(request,
else: else:
messages.error(request,message) messages.error(request,message)
if race and race_can_submit(r,race): if int(registrationid) < 0:
if race.sessiontype == 'indoorrace': race = VirtualRace.objects.get(id=-int(registrationid))
records = IndoorVirtualRaceResult.objects.filter( if race.sessiontype == 'race':
race=race, result,comments,errors,jobid = add_workout_race(
userid=r.id [w], race,r,doregister=True,
)
if result:
messages.info(
request,
"We have submitted your workout to the race")
for c in comments:
messages.info(request,c)
for er in errors:
messages.error(request,er)
elif race.sessiontype == 'indoorrace':
result,comments,errors,jobid = add_workout_indoorrace(
[w],race,r,doregister=True,
) )
if records: if result:
messages.info(
request,
"We have submitted your workout to the race")
for c in comments:
messages.info(request,c)
for er in errors:
messages.error(request,er)
if int(registrationid)>0:
races = VirtualRace.objects.filter(
registration_closure__gt=timezone.now()
)
registrations = IndoorVirtualRaceResult.objects.filter(
race__in = races,
id=registrationid,
userid = r.id,
)
registrations2 = VirtualRaceResult.objects.filter(
race__in = races,
id=registrationid,
userid=r.id,
)
if int(registrationid) in [r.id for r in registrations]:
# indoor race
registrations = registrations.filter(id=registrationid)
if registrations:
race = registrations[0].race
result,comments,errors,jobid = add_workout_indoorrace( result,comments,errors,jobid = add_workout_indoorrace(
[w],race,r,recordid=records[0].id [w],race,r,recordid=registrations[0].id
) )
if result: if result:
@@ -5135,15 +5176,15 @@ def workout_upload_view(request,
messages.info(request,c) messages.info(request,c)
for er in errors: for er in errors:
messages.error(request,er) messages.error(request,er)
if race.sessiontype == 'race':
records = VirtualRaceResult.objects.filter(
race=race,userid=r.id
)
if records: if int(registrationid) in [r.id for r in registrations2]:
# race
registrations = registrations2.filter(id=registrationid)
if registrations:
race = registrations[0].race
result,comments,errors,jobid = add_workout_race( result,comments,errors,jobid = add_workout_race(
[w], race,r,recordid=records[0].id [w], race,r,recordid=registrations[0].id
) )
if result: if result:
messages.info( messages.info(
@@ -5156,11 +5197,18 @@ def workout_upload_view(request,
messages.error(request,er) messages.error(request,er)
if landingpage != 'workout_upload_view':
if registrationid != 0:
url = reverse('virtualevent_view',
kwargs = {
'id':race.id,
})
elif landingpage != 'workout_upload_view':
url = reverse(landingpage, url = reverse(landingpage,
kwargs = { kwargs = {
'id':encoder.encode_hex(w.id), 'id':encoder.encode_hex(w.id),
}) })
else: else:
url = reverse(landingpage) url = reverse(landingpage)

View File

@@ -379,7 +379,7 @@ CACHE_MIDDLEWARE_SECONDS = 900
EMAIL_BACKEND = 'django_ses.SESBackend' EMAIL_BACKEND = 'django_ses.SESBackend'
AWS_SES_REGION_NAME = CFG['aws_smtp'] AWS_SES_REGION_NAME = CFG['aws_region']
AWS_SES_REGION_ENDPOINT = CFG['aws_smtp'] AWS_SES_REGION_ENDPOINT = CFG['aws_smtp']
AWS_SES_ACCESS_KEY_ID = CFG['aws_access_key_id'] AWS_SES_ACCESS_KEY_ID = CFG['aws_access_key_id']