2097 lines
69 KiB
Python
2097 lines
69 KiB
Python
from rowers.emails import htmlstrip, htmlstripnobr
|
|
from rowers.models import (
|
|
Rower, Workout, Team,
|
|
GeoCourse, TrainingMicroCycle, TrainingMesoCycle, TrainingMacroCycle,
|
|
TrainingPlan, PlannedSession, VirtualRaceResult, CourseTestResult,
|
|
get_course_timezone, IndoorVirtualRaceResult, VirtualRace, createmacrofillers,
|
|
createmesofillers, createmicrofillers, CourseStandard,
|
|
)
|
|
from rowers.tasks import (
|
|
handle_sendemail_raceregistration, handle_sendemail_racesubmission
|
|
)
|
|
from rowers.tasks import handle_check_race_course, create_sessions_from_json_async
|
|
from iso8601 import ParseError
|
|
import iso8601
|
|
import rowers.courses as courses
|
|
import rowers.datautils as datautils
|
|
import rowers.dataprep as dataprep
|
|
import numpy as np
|
|
import rowers.metrics as metrics
|
|
import rowers.mytypes as mytypes
|
|
from rowers.utils import to_pace
|
|
from rowers.opaque import encoder
|
|
from rowingdata import rower as rrower
|
|
from rowingdata import rowingdata as rrdata
|
|
import arrow
|
|
import polars as pl
|
|
import json
|
|
|
|
# Python
|
|
from django.utils import timezone
|
|
from datetime import datetime
|
|
import datetime as dt
|
|
from datetime import timedelta
|
|
from datetime import date
|
|
import time
|
|
from django.db import IntegrityError
|
|
import uuid
|
|
from django.conf import settings
|
|
import pytz
|
|
from dateutil import parser
|
|
from rowers.utils import myqueue, calculate_age, totaltime_sec_to_string, ps_dict_order
|
|
from rowers.rows import handle_uploaded_file
|
|
import collections
|
|
import re
|
|
import django_rq
|
|
queue = django_rq.get_queue('default')
|
|
queuelow = django_rq.get_queue('low')
|
|
queuehigh = django_rq.get_queue('low')
|
|
|
|
|
|
def to_time(milliseconds):
|
|
seconds = milliseconds/1000.
|
|
hours = int(seconds / 3600)
|
|
mins = int((seconds % 3600)/60)
|
|
sec = int((seconds % 3600) % 60)
|
|
microsec = int(1e6*(seconds % 1))
|
|
# print(seconds,hours,mins,sec,millisec)
|
|
|
|
return dt.time(hours, mins, sec, microsec)
|
|
|
|
# Wrapper around the rowingdata call to catch some exceptions
|
|
# Checks for CSV file, then for gzipped CSV file, and if all fails, returns 0
|
|
|
|
|
|
def rdata(file, rower=rrower()):
|
|
try:
|
|
res = rrdata(csvfile=file, rower=rower)
|
|
except (IOError, IndexError, EOFError, FileNotFoundError): # pragma: no cover
|
|
try:
|
|
res = rrdata(csvfile=file+'.gz', rower=rower)
|
|
except (IOError, IndexError, EOFError, FileNotFoundError):
|
|
res = 0
|
|
|
|
return res
|
|
|
|
|
|
def ps_dict_get_description(d, short=False): # pragma: no cover
|
|
sdict, totalmeters, totalseconds, totalrscore = ps_dict_order(
|
|
d, short=short, html=False)
|
|
s = ''
|
|
for item in sdict:
|
|
s += item['string']+'\n'
|
|
|
|
return s
|
|
|
|
|
|
def ps_dict_get_description_html(d, short=False):
|
|
sdict, totalmeters, totalseconds, totalrscore = ps_dict_order(
|
|
d, short=short)
|
|
|
|
s = '<ul>'
|
|
|
|
for item in sdict:
|
|
s += '<li>'+item['string']+'</li>'
|
|
|
|
s += '</ul>'
|
|
|
|
return s
|
|
|
|
|
|
def checkscores(r, macrocycles):
|
|
for m in macrocycles:
|
|
createmesofillers(m)
|
|
m.plantime = 0
|
|
m.actualtime = 0
|
|
m.plandistance = 0
|
|
m.actualdistance = 0
|
|
m.planrscore = 0
|
|
m.actualrscore = 0
|
|
m.plantrimp = 0
|
|
m.actualtrimp = 0
|
|
|
|
mesocycles = TrainingMesoCycle.objects.filter(
|
|
plan=m,
|
|
type='userdefined').order_by("startdate")
|
|
|
|
for me in mesocycles:
|
|
me.plantime = 0
|
|
me.actualtime = 0
|
|
me.plandistance = 0
|
|
me.actualdistance = 0
|
|
me.planrscore = 0
|
|
me.actualrscore = 0
|
|
me.plantrimp = 0
|
|
me.actualtrimp = 0
|
|
|
|
microcycles = TrainingMicroCycle.objects.filter(
|
|
plan=me,
|
|
type='userdefined').order_by("startdate")
|
|
|
|
for mm in microcycles:
|
|
sps = get_sessions(
|
|
r, startdate=mm.startdate, enddate=mm.enddate)
|
|
|
|
# sps = PlannedSession.objects.filter(
|
|
# rower = r,
|
|
# startdate__lte=mm.enddate,
|
|
# enddate__gte=mm.startdate)
|
|
|
|
mm.plantime = 0
|
|
mm.actualtime = 0
|
|
mm.plandistance = 0
|
|
mm.actualdistance = 0
|
|
mm.planrscore = 0
|
|
mm.actualrscore = 0
|
|
mm.plantrimp = 0
|
|
mm.actualtrimp = 0
|
|
|
|
if mm.type == 'userdefined':
|
|
for ps in sps: # pragma: no cover
|
|
ratio, status, cdate = is_session_complete(r, ps)
|
|
if ps.sessionmode == 'time':
|
|
mm.plantime += ps.sessionvalue
|
|
mm.actualtime += int(ps.sessionvalue*ratio)
|
|
elif ps.sessionmode == 'distance' and ps.sessiontype != 'race':
|
|
mm.plandistance += ps.sessionvalue
|
|
mm.actualdistance += int(ps.sessionvalue*ratio)
|
|
elif ps.sessionmode == 'rScore':
|
|
mm.planrscore += ps.sessionvalue
|
|
mm.actualrscore += int(ps.sessionvalue*ratio)
|
|
elif ps.sessionmode == 'TRIMP':
|
|
mm.plantrimp += ps.sessionvalue
|
|
mm.actualtrimp += int(ps.sessionvalue*ratio)
|
|
|
|
mm.save()
|
|
|
|
me.plantime += mm.plantime
|
|
me.actualtime += mm.actualtime
|
|
me.plandistance += mm.plandistance
|
|
me.actualdistance += mm.actualdistance
|
|
me.planrscore += mm.planrscore
|
|
me.actualrscore += mm.actualrscore
|
|
me.plantrimp += mm.plantrimp
|
|
me.actualtrimp += mm.actualtrimp
|
|
|
|
if me.type == 'userdefined':
|
|
me.save()
|
|
|
|
m.plantime += me.plantime
|
|
m.actualtime += me.actualtime
|
|
m.plandistance += me.plandistance
|
|
m.actualdistance += me.actualdistance
|
|
m.planrscore += me.planrscore
|
|
m.actualrscore += me.actualrscore
|
|
m.plantrimp += me.plantrimp
|
|
m.actualtrimp += me.actualtrimp
|
|
|
|
if m.type == 'userdefined':
|
|
m.save()
|
|
|
|
|
|
def get_execution_report(rower, startdate, enddate, plan=None):
|
|
if plan:
|
|
macros = TrainingMacroCycle.objects.filter(
|
|
plan=plan).order_by("startdate")
|
|
checkscores(rower, macros)
|
|
mesos = TrainingMesoCycle.objects.filter(
|
|
plan__in=macros).order_by("startdate")
|
|
micros = TrainingMicroCycle.objects.filter(
|
|
plan__in=mesos).order_by("startdate")
|
|
micros = micros.exclude(enddate__lte=startdate).exclude(
|
|
startdate__gte=enddate)
|
|
else: # pragma: no cover
|
|
plans = TrainingPlan.objects.filter(rowers__in=[rower])
|
|
|
|
if not plans:
|
|
micros = []
|
|
else:
|
|
sorted_plans = sorted(plans, key= lambda t: t.overlap(startdate,enddate))
|
|
plan = plans.reverse()[0]
|
|
macros = TrainingMacroCycle.objects.filter(
|
|
plan=plan).order_by("startdate")
|
|
checkscores(rower, macros)
|
|
mesos = TrainingMesoCycle.objects.filter(
|
|
plan__in=macros).order_by("startdate")
|
|
micros = TrainingMicroCycle.objects.filter(
|
|
plan__in=mesos,
|
|
enddate__gte=startdate,
|
|
startdate__lte=enddate,
|
|
).order_by("startdate")
|
|
|
|
if len(micros)==0:
|
|
startdate += timedelta(days=1-startdate.isoweekday())
|
|
startdate = startdate-timedelta(days=7)
|
|
micros = []
|
|
while startdate <= enddate:
|
|
micro = type('micros', (object,),
|
|
{
|
|
'startdate': startdate,
|
|
'enddate': startdate+timedelta(days=7)
|
|
})
|
|
micros.append(micro)
|
|
startdate += timedelta(days=7)
|
|
|
|
|
|
# we've got micros, now get sessions
|
|
startdates = []
|
|
planned = []
|
|
executed = []
|
|
|
|
|
|
for mm in micros:
|
|
plannedscore = 0
|
|
actualscore = 0
|
|
sps = get_sessions(rower, startdate=mm.startdate, enddate=mm.enddate)
|
|
unmatchedworkouts = Workout.objects.filter(
|
|
user=rower,
|
|
plannedsession=None,
|
|
date__gte=mm.startdate, date__lte=mm.enddate).exclude(duplicate=True)
|
|
for w in unmatchedworkouts:
|
|
row = {
|
|
'rscore': w.rscore,
|
|
'hrtss': w.hrtss,
|
|
'rpe': w.rpe,
|
|
'duration': w.duration,
|
|
'id': w.id
|
|
}
|
|
rscore = dataprep.rscore_approx(row)
|
|
actualscore += rscore
|
|
|
|
for ps in sps: # pragma: no cover
|
|
ratio, status, cdate = is_session_complete(rower, ps)
|
|
if ps.sessionmode == 'rScore':
|
|
plannedscore += ps.sessionvalue
|
|
actualscore += ratio*ps.sessionvalue
|
|
else:
|
|
ws = Workout.objects.filter(user=rower, plannedsession=ps)
|
|
if not ws:
|
|
if ps.sessionmode == 'time':
|
|
plannedscore += ps.sessionvalue
|
|
elif ps.sessionmode == 'distance':
|
|
plannedscore += 60.
|
|
elif ps.sessionmode == 'TRIMP':
|
|
plannedscore += ps.sessionvalue/2.
|
|
|
|
for w in ws:
|
|
if w.rscore != 0:
|
|
if ratio > 0:
|
|
plannedscore += w.rscore/ratio
|
|
actualscore += w.rscore
|
|
else:
|
|
plannedscore += 60
|
|
actualscore += 0
|
|
elif w.hrtss >= 0:
|
|
if ratio > 0:
|
|
plannedscore += w.hrtss/ratio
|
|
actualscore += w.hrtss
|
|
else:
|
|
plannedscore += 60
|
|
actualscore += 0
|
|
else:
|
|
minutes = w.duration.hour*60+w.duration.minute
|
|
if ratio > 0:
|
|
plannedscore += minutes/ratio
|
|
else:
|
|
plannedscore += 60
|
|
|
|
actualscore += minutes
|
|
|
|
actualscore = int(actualscore)
|
|
plannedscore = int(plannedscore)
|
|
|
|
startdates += [mm.startdate]
|
|
planned += [plannedscore]
|
|
executed += [actualscore]
|
|
|
|
data = pl.DataFrame({
|
|
'startdate': startdates,
|
|
'planned': planned,
|
|
'executed': executed,
|
|
})
|
|
|
|
return(data, 'ok')
|
|
|
|
|
|
def get_indoorraces(workout):
|
|
if workout is None:
|
|
return []
|
|
|
|
races1 = VirtualRace.objects.filter(
|
|
sessiontype='indoorrace',
|
|
startdate__lte=workout.date,
|
|
enddate__gte=workout.date,
|
|
sessionmode='distance',
|
|
sessionvalue=workout.distance)
|
|
|
|
|
|
if workout.duration is None:
|
|
workout.duration = timedelta(0)
|
|
|
|
if workout.duration.second == 0 and workout.duration.microsecond == 0:
|
|
duration = 60*workout.duration.hour+workout.duration.minute
|
|
|
|
races2 = VirtualRace.objects.filter(
|
|
sessiontype='indoorrace',
|
|
startdate__lte=workout.date,
|
|
enddate__gte=workout.date,
|
|
sessionmode='time',
|
|
sessionvalue=duration)
|
|
|
|
races = races1 | races2
|
|
else:
|
|
races = races1
|
|
|
|
registrations = IndoorVirtualRaceResult.objects.filter(
|
|
race__in=races,
|
|
boatclass=workout.workouttype,
|
|
userid=workout.user.id)
|
|
|
|
races = [r.race for r in registrations]
|
|
|
|
return races
|
|
|
|
|
|
def get_todays_micro(plan, thedate=timezone.now()):
|
|
thismicro = None
|
|
|
|
thismacro = TrainingMacroCycle.objects.filter(
|
|
plan=plan,
|
|
startdate__lte=thedate,
|
|
enddate__gte=thedate
|
|
)
|
|
|
|
if thismacro:
|
|
thismeso = TrainingMesoCycle.objects.filter(
|
|
plan=thismacro[0],
|
|
startdate__lte=thedate,
|
|
enddate__gte=thedate
|
|
)
|
|
|
|
if thismeso:
|
|
thismicro = TrainingMicroCycle.objects.filter(
|
|
plan=thismeso[0],
|
|
startdate__lte=thedate,
|
|
enddate__gte=thedate
|
|
)
|
|
|
|
if thismicro:
|
|
thismicro = thismicro[0]
|
|
else: # pragma: no cover
|
|
return None
|
|
|
|
return thismicro
|
|
|
|
# Low Level functions - to be called by higher level methods
|
|
|
|
|
|
def add_workouts_plannedsession(ws, ps, r):
|
|
result = 0
|
|
comments = []
|
|
errors = []
|
|
|
|
# check if all sessions have same date
|
|
dates = [w.date for w in ws]
|
|
if (not all(d == dates[0] for d in dates)) and ps.sessiontype not in ['challenge', 'cycletarget']: # pragma: no cover
|
|
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 ps.sessiontype == 'test': # pragma: no cover
|
|
errors.append('For tests, you can only attach one workout')
|
|
return result, comments, errors
|
|
|
|
wold = Workout.objects.filter(plannedsession=ps, user=r)
|
|
ids = [w.id for w in wold] + [w.id for w in ws]
|
|
ids = list(set(ids))
|
|
|
|
if len(ids) > 1 and ps.sessiontype in ['test', 'coursetest', 'race', 'fastest_time', 'fastest_distance']: # pragma: no cover
|
|
errors.append('For tests, you can only attach one workout')
|
|
return result, comments, errors
|
|
|
|
# start adding sessions
|
|
for w in ws:
|
|
if w.date >= ps.startdate and w.date <= ps.enddate:
|
|
w.plannedsession = ps
|
|
w.save()
|
|
result += 1
|
|
comments.append('Attached workout %s to session' %
|
|
encoder.encode_hex(w.id))
|
|
if ps.sessiontype == 'coursetest': # pragma: no cover
|
|
record = CourseTestResult(
|
|
userid=w.user.id,
|
|
plannedsession=ps,
|
|
duration=dt.time(0, 0),
|
|
coursecompleted=False,
|
|
)
|
|
record.save()
|
|
_ = myqueue(queue, handle_check_race_course, w.csvfilename,
|
|
w.id, ps.course.id, record.id,
|
|
w.user.user.email, w.user.user.first_name,
|
|
mode='coursetest')
|
|
if ps.sessiontype == 'fastest_distance': # pragma: no cover
|
|
records = CourseTestResult.objects.filter(
|
|
userid=w.user.id, plannedsession=ps)
|
|
for record in records:
|
|
record.delete()
|
|
|
|
df = dataprep.read_data(
|
|
['time', 'cumdist'], ids=[w.id])
|
|
df = dataprep.remove_nulls_pl(df)
|
|
fastest_milliseconds, starttime, endtime = datautils.getfastest(
|
|
df, ps.sessionvalue, mode='distance')
|
|
|
|
if fastest_milliseconds > 0:
|
|
w.plannedsession = ps
|
|
w.save()
|
|
|
|
duration = to_time(1000.*fastest_milliseconds)
|
|
|
|
record = CourseTestResult(
|
|
userid=w.user.user.id,
|
|
plannedsession=ps,
|
|
duration=duration,
|
|
coursecompleted=True,
|
|
workoutid=w.id,
|
|
distance=ps.sessionvalue,
|
|
startsecond=starttime,
|
|
endsecond=endtime,
|
|
)
|
|
|
|
record.save()
|
|
else:
|
|
errors.append('Could not find a matching interval')
|
|
if ps.sessiontype == 'fastest_time': # pragma: no cover
|
|
records = CourseTestResult.objects.filter(
|
|
userid=w.user.id, plannedsession=ps)
|
|
for record in records:
|
|
record.delete()
|
|
|
|
df = dataprep.read_data(
|
|
['time', 'cumdist'], ids=[w.id])
|
|
df = dataprep.remove_nulls_pl(df)
|
|
fastest_meters, starttime, endtime = datautils.getfastest(
|
|
df, ps.sessionvalue, mode='time')
|
|
if fastest_meters > 0:
|
|
w.plannedsession = ps
|
|
w.save()
|
|
duration = dt.time(0, ps.sessionvalue)
|
|
|
|
record = CourseTestResult(
|
|
userid=w.user.user.id,
|
|
workoutid=w.id,
|
|
plannedsession=ps,
|
|
duration=duration,
|
|
coursecompleted=True,
|
|
distance=fastest_meters,
|
|
startsecond=starttime,
|
|
endsecond=endtime,
|
|
)
|
|
record.save()
|
|
else:
|
|
errors.append('Could not find a matching interval')
|
|
else: # pragma: no cover
|
|
errors.append('Workout %i did not match session dates' % w.id)
|
|
|
|
return result, comments, errors
|
|
|
|
|
|
def remove_workout_plannedsession(w, ps):
|
|
if w.plannedsession == ps:
|
|
w.plannedsession = None
|
|
w.save()
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
def clone_planned_session(ps): # pragma: no cover
|
|
ps.save()
|
|
ps.pk = None # creates new instance
|
|
ps.save()
|
|
|
|
|
|
def timefield_to_seconds_duration(t):
|
|
duration = t.hour*3600.
|
|
duration += t.minute * 60.
|
|
duration += t.second
|
|
duration += t.microsecond/1.e6
|
|
|
|
return duration
|
|
|
|
|
|
def get_virtualrace_times(virtualrace): # pragma: no cover
|
|
geocourse = GeoCourse.objects.get(id=virtualrace.course.id)
|
|
timezone_str = 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):
|
|
rowers = ps.rower.all()
|
|
rscore = []
|
|
trimp = []
|
|
duration = []
|
|
distance = []
|
|
firstname = []
|
|
lastname = []
|
|
completedate = []
|
|
status = []
|
|
|
|
for r in rowers:
|
|
rscorev = 0
|
|
trimpv = 0
|
|
durationv = 0
|
|
distancev = 0
|
|
completedatev = ''
|
|
statusv = 0
|
|
|
|
ws = Workout.objects.filter(user=r, plannedsession=ps).order_by("date")
|
|
|
|
if ws.count() != 0:
|
|
for w in ws:
|
|
distancev += w.distance
|
|
durationv += timefield_to_seconds_duration(w.duration)
|
|
thetrimp, hrtss = dataprep.workout_trimp(w)
|
|
trimpv += thetrimp
|
|
row = {
|
|
'rscore': w.rscore,
|
|
'hrtss': w.hrtss,
|
|
'rpe': w.rpe,
|
|
'duration': w.duration,
|
|
'id': w.id
|
|
}
|
|
tss = dataprep.rscore_approx(row)
|
|
rscorev += tss
|
|
|
|
ratio, statusv, completiondate = is_session_complete_ws(ws, ps)
|
|
try:
|
|
completedatev = completiondate.strftime('%Y-%m-%d')
|
|
except AttributeError: # pragma: no cover
|
|
completedatev = ''
|
|
durationv /= 60.
|
|
|
|
trimp.append(int(trimpv))
|
|
duration.append(int(durationv))
|
|
distance.append(int(distancev))
|
|
rscore.append(int(rscorev))
|
|
firstname.append(r.user.first_name)
|
|
lastname.append(r.user.last_name)
|
|
status.append(statusv)
|
|
completedate.append(completedatev)
|
|
|
|
thedict = {
|
|
'first_name': firstname,
|
|
'last_name': lastname,
|
|
'duration': duration,
|
|
'distance': distance,
|
|
'rscore': rscore,
|
|
'trimp': trimp,
|
|
'completedate': completedate,
|
|
'status': status,
|
|
}
|
|
|
|
return thedict
|
|
|
|
|
|
cratiocolors = {
|
|
'better than nothing': "lightgreen",
|
|
'partial': "mediumgreen",
|
|
'on target': "green",
|
|
'over target': "purple",
|
|
'way over target': "violet",
|
|
'missed': "black",
|
|
'not assigned': "",
|
|
'not done': "white",
|
|
}
|
|
|
|
|
|
def is_session_complete_ws(ws, ps):
|
|
ws = ws.order_by("date")
|
|
if ws.count() == 0:
|
|
today = timezone.now()
|
|
if today.date() > ps.enddate:
|
|
verdict = 'missed'
|
|
ratio = 0
|
|
return ratio, verdict, None
|
|
else:
|
|
return 0, 'not done', None
|
|
|
|
value = ps.sessionvalue
|
|
if ps.sessionunit == 'min':
|
|
value *= 60.
|
|
elif ps.sessionunit == 'km': # pragma: no cover
|
|
value *= 1000.
|
|
|
|
cratiomin = 1
|
|
# cratiomax = 1
|
|
|
|
cratios = {
|
|
'better than nothing': 0,
|
|
'partial': 0.6,
|
|
'on target': 0.8,
|
|
'over target': 1.2,
|
|
'way over target': 1.5
|
|
}
|
|
|
|
if ps.criterium == 'none':
|
|
if ps.sessiontype == 'session':
|
|
cratiomin = 0.8
|
|
# cratiomax = 1.2
|
|
else:
|
|
cratios['on target'] = 0.9167
|
|
cratios['over target'] = 1.0833
|
|
cratiomin = 0.9167
|
|
# cratiomax = 1.0833
|
|
|
|
score = 0
|
|
completiondate = None
|
|
for w in ws:
|
|
if ps.sessionmode == 'distance':
|
|
score += w.distance
|
|
elif ps.sessionmode == 'time':
|
|
durationseconds = timefield_to_seconds_duration(w.duration)
|
|
score += durationseconds
|
|
elif ps.sessionmode == 'TRIMP':
|
|
trimp, hrtss = dataprep.workout_trimp(w)
|
|
score += trimp
|
|
elif ps.sessionmode == 'rScore':
|
|
row = {
|
|
'rscore': w.rscore,
|
|
'hrtss': w.hrtss,
|
|
'rpe': w.rpe,
|
|
'duration': w.duration,
|
|
'id': w.id
|
|
}
|
|
rscore = dataprep.rscore_approx(row)
|
|
score += rscore
|
|
if not completiondate and score >= cratiomin*value:
|
|
completiondate = w.date
|
|
|
|
try:
|
|
ratio = score/float(int(value))
|
|
except ZeroDivisionError: # pragma: no cover
|
|
ratio = 0
|
|
|
|
verdict = 'better than nothing'
|
|
|
|
if ps.sessiontype in ['session', 'cycletarget']:
|
|
if ps.criterium == 'exact':
|
|
if ratio == 1.0:
|
|
return ratio, 'on target', completiondate
|
|
else:
|
|
if not completiondate: # pragma: no cover
|
|
completiondate = ws.reverse()[0].date
|
|
return ratio, 'partial', completiondate
|
|
elif ps.criterium == 'minimum': # pragma: no cover
|
|
if ratio >= 1.0:
|
|
return ratio, 'on target', completiondate
|
|
else:
|
|
if not completiondate:
|
|
completiondate = ws.reverse()[0].date
|
|
|
|
return ratio, 'partial', completiondate
|
|
else:
|
|
thevalue = 0
|
|
for key, value in cratios.items():
|
|
if ratio > value and value > thevalue:
|
|
verdict = key
|
|
thevalue = value
|
|
|
|
completiondate = ws.reverse()[0].date
|
|
return ratio, verdict, completiondate
|
|
elif ps.sessiontype == 'test':
|
|
if ratio == 1.0:
|
|
return ratio, 'on target', completiondate
|
|
else:
|
|
return ratio, 'partial', completiondate
|
|
elif ps.sessiontype == 'challenge':
|
|
if ps.criterium == 'exact':
|
|
if ratio == 1.0:
|
|
return ratio, 'on target', completiondate
|
|
else:
|
|
return ratio, 'partial', completiondate
|
|
elif ps.criterium == 'minimum': # pragma: no cover
|
|
if ratio > 1.0:
|
|
return ratio, 'on target', completiondate
|
|
else:
|
|
if not completiondate:
|
|
completiondate = ws.reverse()[0].date
|
|
return ratio, 'partial', completiondate
|
|
else:
|
|
if not completiondate: # pragma: no cover
|
|
completiondate = ws.reverse()[0].date
|
|
return ratio, 'partial', completiondate
|
|
elif ps.sessiontype == 'race': # pragma: no cover
|
|
vs = VirtualRaceResult.objects.filter(race=ps)
|
|
wids = [w.id for w in ws]
|
|
for record in vs:
|
|
if record.workoutid in wids:
|
|
if record.coursecompleted:
|
|
ratio = record.distance/ps.sessionvalue
|
|
return ratio, 'on target', completiondate
|
|
else:
|
|
ratio = record.distance/ps.sessionvalue
|
|
return ratio, 'partial', completiondate
|
|
return (0, 'partial', None)
|
|
elif ps.sessiontype in ['fastest_time', 'fastest_distance']: # pragma: no cover
|
|
vs = CourseTestResult.objects.filter(
|
|
plannedsession=ps, userid=ws[0].user.user.id)
|
|
completiondate = ws.reverse()[0].date
|
|
wids = [w.id for w in ws]
|
|
for record in vs:
|
|
if record.workoutid in wids:
|
|
if record.coursecompleted:
|
|
ratio = 1
|
|
return ratio, 'on target', completiondate
|
|
else:
|
|
return 0, 'partial', completiondate
|
|
if ws:
|
|
record = CourseTestResult(
|
|
userid=ws[0].user.id,
|
|
plannedsession=ps,
|
|
workoutid=ws[0].id,
|
|
duration=dt.time(0, 0),
|
|
coursecompleted=False
|
|
)
|
|
record.save()
|
|
return (0, 'not done', None)
|
|
elif ps.sessiontype == 'coursetest': # pragma: no cover
|
|
vs = CourseTestResult.objects.filter(plannedsession=ps)
|
|
wids = [w.id for w in ws]
|
|
for record in vs:
|
|
if record.workoutid in wids:
|
|
if record.coursecompleted:
|
|
ratio = record.distance/float(ps.sessionvalue)
|
|
return ratio, 'on target', completiondate
|
|
else:
|
|
ratio = record.distance/float(ps.sessionvalue)
|
|
return ratio, 'partial', completiondate
|
|
|
|
# we're still here - no record, need to create one
|
|
if ws:
|
|
record = CourseTestResult(
|
|
userid=ws[0].user.id,
|
|
plannedsession=ps,
|
|
workoutid=ws[0].id,
|
|
duration=dt.time(0, 0),
|
|
coursecompleted=False,
|
|
)
|
|
record.save()
|
|
_ = myqueue(queue, handle_check_race_course, ws[0].csvfilename,
|
|
ws[0].id, ps.course.id, record.id,
|
|
ws[0].user.user.email, ws[0].user.user.first_name,
|
|
mode='coursetest')
|
|
|
|
return (0, 'not done', None)
|
|
|
|
else: # pragma: no cover
|
|
if not completiondate:
|
|
completiondate = ws.reverse()[0].date
|
|
return ratio, verdict, completiondate
|
|
|
|
|
|
def is_session_complete(r, ps):
|
|
if r not in ps.rower.all(): # pragma: no cover
|
|
return 0, 'not assigned', None
|
|
|
|
ws = Workout.objects.filter(user=r, plannedsession=ps)
|
|
|
|
return is_session_complete_ws(ws, ps)
|
|
|
|
|
|
def rank_results(ps): # pragma: no cover
|
|
return 1
|
|
|
|
|
|
def add_team_session(t, ps):
|
|
ps.team.add(t)
|
|
ps.save()
|
|
|
|
return 1
|
|
|
|
|
|
def add_rower_session(r, ps):
|
|
teams = Team.objects.filter(manager=ps.manager)
|
|
members = Rower.objects.filter(team__in=teams).distinct()
|
|
if r in members and r.rowerplan != 'freecoach':
|
|
ps.rower.add(r)
|
|
ps.save()
|
|
|
|
return 1
|
|
elif ps.manager.rower == r and r.rowerplan != 'freecoach':
|
|
ps.rower.add(r)
|
|
ps.save()
|
|
|
|
return 0
|
|
|
|
|
|
def remove_team_session(t, ps): # pragma: no cover
|
|
ps.team.remove(t)
|
|
|
|
return 1
|
|
|
|
|
|
def remove_rower_session(r, ps):
|
|
ps.rower.remove(r)
|
|
|
|
return 1
|
|
|
|
|
|
def get_team(request):
|
|
teamid = request.GET.get('team')
|
|
return teamid
|
|
|
|
|
|
def get_dates_timeperiod(request, startdatestring='', enddatestring='',
|
|
defaulttimeperiod='thisweek', rower=None):
|
|
# set start end date according timeperiod
|
|
# should always return datetime.date
|
|
|
|
timeperiod = request.GET.get('when')
|
|
|
|
if not timeperiod:
|
|
timeperiod = defaulttimeperiod
|
|
|
|
if startdatestring == '':
|
|
startdatestring = request.GET.get('startdate')
|
|
if enddatestring == '':
|
|
enddatestring = request.GET.get('enddate')
|
|
|
|
if startdatestring and enddatestring:
|
|
try:
|
|
startdate = dt.datetime.strptime(
|
|
startdatestring, '%Y-%m-%d').date()
|
|
enddate = dt.datetime.strptime(enddatestring, '%Y-%m-%d').date()
|
|
except ValueError:
|
|
try:
|
|
startdate = parser.parse(startdatestring, fuzzy=True).date()
|
|
enddate = parser.parse(enddatestring, fuzzy=True).date()
|
|
except ValueError: # pragma: no cover
|
|
startdate = timezone.now()-timezone.timedelta(days=5)
|
|
startdate = startdate.date()
|
|
enddate = timezone.now().date()
|
|
except parser.ParserError:
|
|
startdate = timezone.now()-timezone.timedelta(days=5)
|
|
startdate = startdate.date()
|
|
enddate = timezone.now().date()
|
|
|
|
if startdate > enddate:
|
|
e = startdate
|
|
startdate = enddate
|
|
enddate = e
|
|
|
|
if rower is not None:
|
|
tz = pytz.timezone(rower.defaulttimezone)
|
|
startdate = arrow.get(startdate)
|
|
enddate = arrow.get(enddate)
|
|
startdate = startdate.replace(tzinfo=tz)
|
|
enddate = enddate.replace(tzinfo=tz)
|
|
else:
|
|
startdate = dt.datetime.combine(startdate, dt.datetime.min.time())
|
|
enddate = dt.datetime.combine(enddate, dt.datetime.min.time())
|
|
startdate = startdate.astimezone(pytz.utc)
|
|
enddate = enddate.astimezone(pytz.utc)
|
|
|
|
# set time to 00:00 in local time
|
|
startdate = startdate.replace(
|
|
hour=0, minute=0, second=0, microsecond=0)
|
|
enddate = enddate.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
return startdate, enddate
|
|
|
|
daterangetester = re.compile('^(\d+-\d+-\d+)\/(\d+-\d+-\d+)')
|
|
|
|
if timeperiod == 'today': # pragma: no cover
|
|
startdate = timezone.now()
|
|
enddate = timezone.now()
|
|
elif timeperiod == 'last30':
|
|
startdate = timezone.now()-timezone.timedelta(days=30)
|
|
enddate = timezone.now()+timezone.timedelta(days=1)
|
|
elif timeperiod == 'tomorrow': # pragma: no cover
|
|
startdate = timezone.now()+timezone.timedelta(days=1)
|
|
enddate = timezone.now()+timezone.timedelta(days=1)
|
|
elif timeperiod == 'thisweek': # pragma: no cover
|
|
today = timezone.now()
|
|
startdate = timezone.now()-timezone.timedelta(days=today.weekday())
|
|
enddate = startdate+timezone.timedelta(days=6)
|
|
elif timeperiod == 'thismonth': # pragma: no cover
|
|
today = timezone.now()
|
|
startdate = today.replace(day=1)
|
|
enddate = startdate+timezone.timedelta(days=32)
|
|
enddate = enddate.replace(day=1)
|
|
enddate = enddate-timezone.timedelta(days=1)
|
|
elif timeperiod == 'lastweek': # pragma: no cover
|
|
today = timezone.now()
|
|
enddate = today - \
|
|
timezone.timedelta(days=today.weekday())-timezone.timedelta(days=1)
|
|
startdate = enddate-timezone.timedelta(days=6)
|
|
elif timeperiod == 'nextweek': # pragma: no cover
|
|
today = timezone.now()
|
|
startdate = today - \
|
|
timezone.timedelta(days=today.weekday())+timezone.timedelta(days=7)
|
|
enddate = startdate+timezone.timedelta(days=6)
|
|
elif timeperiod == 'lastmonth': # pragma: no cover
|
|
today = timezone.now()
|
|
startdate = today.replace(day=1)
|
|
startdate = startdate-timezone.timedelta(days=3)
|
|
startdate = startdate.replace(day=1)
|
|
enddate = startdate+timezone.timedelta(days=32)
|
|
enddate = enddate.replace(day=1)
|
|
enddate = enddate-timezone.timedelta(days=1)
|
|
elif timeperiod == 'nextmonth': # pragma: no cover
|
|
today = timezone.now()
|
|
startdate = today.replace(day=1)
|
|
startdate = startdate+timezone.timedelta(days=32)
|
|
startdate = startdate.replace(day=1)
|
|
enddate = startdate+timezone.timedelta(days=32)
|
|
enddate = enddate.replace(day=1)
|
|
enddate = enddate-timezone.timedelta(days=1)
|
|
elif timeperiod == 'lastyear': # pragma: no cover
|
|
today = timezone.now()
|
|
startdate = today-timezone.timedelta(days=365)
|
|
enddate = today+timezone.timedelta(days=1)
|
|
elif daterangetester.match(timeperiod):
|
|
tstartdatestring = daterangetester.match(timeperiod).group(1)
|
|
tenddatestring = daterangetester.match(timeperiod).group(2)
|
|
try:
|
|
startdate = dt.datetime.strptime(
|
|
tstartdatestring, '%Y-%m-%d').date()
|
|
enddate = dt.datetime.strptime(tenddatestring, '%Y-%m-%d').date()
|
|
startdate = dt.datetime.combine(startdate, dt.datetime.min.time())
|
|
enddate = dt.datetime.combine(enddate, dt.datetime.min.time())
|
|
if startdate > enddate: # pragma: no cover
|
|
startdate2 = enddate
|
|
enddate = startdate
|
|
startdate = startdate2
|
|
except ValueError: # pragma: no cover
|
|
startdate = timezone.now()
|
|
enddate = timezone.now()
|
|
else:
|
|
startdate = timezone.now()
|
|
enddate = timezone.now()
|
|
|
|
if startdatestring != '':
|
|
try:
|
|
startdate = iso8601.parse_date(startdatestring)
|
|
except ParseError:
|
|
pass
|
|
|
|
if enddatestring != '':
|
|
try:
|
|
enddate = iso8601.parse_date(enddatestring)
|
|
except ParseError:
|
|
pass
|
|
|
|
if rower is not None:
|
|
startdate = arrow.get(startdate)
|
|
enddate = arrow.get(enddate)
|
|
tz = pytz.timezone(rower.defaulttimezone)
|
|
startdate = startdate.astimezone(tz)
|
|
|
|
enddate = enddate.astimezone(tz)
|
|
|
|
# set time to 00:00 in local time
|
|
startdate = startdate.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
enddate = enddate.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
return startdate, enddate
|
|
|
|
|
|
def get_sessions_manager(m, teamid=0, startdate=timezone.now(),
|
|
enddate=timezone.now()+timezone.timedelta(+1000)):
|
|
if teamid: # pragma: no cover
|
|
t = Team.objects.get(id=teamid)
|
|
rs = Rower.objects.filter(team__in=[t]).distinct()
|
|
sps = PlannedSession.objects.filter(
|
|
rower__in=rs,
|
|
manager=m,
|
|
startdate__lte=enddate,
|
|
enddate__gte=startdate,
|
|
is_template=False,
|
|
).order_by("preferreddate", "startdate", "enddate").exclude(
|
|
sessiontype='race').exclude(sessiontype='indoorrace')
|
|
else:
|
|
sps = PlannedSession.objects.filter(
|
|
manager=m,
|
|
startdate__lte=enddate,
|
|
enddate__gte=startdate,
|
|
is_template=False,
|
|
).order_by("preferreddate", "startdate", "enddate").exclude(
|
|
sessiontype='race').exclude(sessiontype='indoorrace')
|
|
|
|
return sps
|
|
|
|
|
|
def get_sessions(r, startdate=timezone.now(),
|
|
enddate=timezone.now()+timezone.timedelta(+1000)):
|
|
|
|
sps = PlannedSession.objects.filter(
|
|
rower__in=[r],
|
|
startdate__lte=enddate,
|
|
enddate__gte=startdate,
|
|
is_template=False,
|
|
).order_by("preferreddate", "startdate", "enddate").exclude(
|
|
sessiontype='race').exclude(sessiontype='indoorrace')
|
|
|
|
return sps
|
|
|
|
|
|
def get_my_session_ids(r):
|
|
sps = PlannedSession.objects.filter(
|
|
rower__in=[r]
|
|
).order_by("preferreddate", "startdate", "enddate").exclude(
|
|
sessiontype='race')
|
|
|
|
return [ps.id for ps in sps]
|
|
|
|
|
|
def get_workouts_session(r, ps):
|
|
ws = Workout.objects.filter(user=r, plannedsession=ps)
|
|
|
|
return ws
|
|
|
|
def create_sessions_from_json(plansteps, rower, startdate, manager, planbyrscore=False, plan=None,
|
|
asynchronous=False, queue=queue):
|
|
trainingdays = plansteps['trainingDays']
|
|
planstartdate = startdate
|
|
if not asynchronous:
|
|
for day in trainingdays:
|
|
for workout in day['workouts']:
|
|
sessionsport = 'water'
|
|
try:
|
|
sessionsport = mytypes.fitmappinginv[workout['sport'].lower()]
|
|
except KeyError:
|
|
pass
|
|
|
|
preferreddate = planstartdate+timedelta(days=day['order'])
|
|
|
|
sessionmode = 'time'
|
|
if planbyrscore:
|
|
sessionmode = 'rScore'
|
|
|
|
ps = PlannedSession(
|
|
startdate=preferreddate -
|
|
timedelta(days=preferreddate.weekday()),
|
|
enddate=preferreddate +
|
|
timedelta(days=-preferreddate.weekday()-1, weeks=1),
|
|
preferreddate=preferreddate,
|
|
sessionsport=sessionsport, # change this
|
|
name=workout['workoutName'],
|
|
steps=workout,
|
|
manager=manager,
|
|
sessionmode=sessionmode,
|
|
comment=workout['description'],
|
|
from_plan=plan,
|
|
)
|
|
|
|
ps.save()
|
|
|
|
add_rower_session(rower, ps)
|
|
return
|
|
|
|
# async version
|
|
_ = myqueue(queue, create_sessions_from_json_async, plansteps, rower, startdate, manager, planbyrscore, plan)
|
|
|
|
|
|
|
|
def update_plannedsession(ps, cd):
|
|
for attr, value in cd.items():
|
|
if attr == 'comment':
|
|
value.replace("\r\n", "
")
|
|
value.replace("\n", "
")
|
|
if attr != 'fitfile':
|
|
setattr(ps, attr, value)
|
|
|
|
if cd['fitfile']: # pragma: no cover
|
|
f = cd['fitfile']
|
|
try:
|
|
filename, path_and_filename = handle_uploaded_file(f)
|
|
ps.fitfile.name = filename
|
|
ps.steps = {}
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
ps.save()
|
|
|
|
return 1, 'Planned Session Updated'
|
|
|
|
|
|
def update_indoorvirtualrace(ps, cd):
|
|
for attr, value in cd.items():
|
|
if attr == 'comment':
|
|
value = htmlstripnobr(value)
|
|
value.replace("\r\n", "
")
|
|
value.replace("\n", "
")
|
|
setattr(ps, attr, value)
|
|
|
|
timezone_str = cd['timezone']
|
|
|
|
# correct times
|
|
|
|
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)
|
|
)
|
|
|
|
registration_form = cd['registration_form']
|
|
registration_closure = cd['registration_closure']
|
|
if registration_form == 'manual': # pragma: no cover
|
|
try:
|
|
registration_closure = pytz.timezone(
|
|
timezone_str
|
|
).localize(
|
|
registration_closure.replace(tzinfo=None)
|
|
)
|
|
except AttributeError:
|
|
registration_closure = startdatetime
|
|
elif registration_form == 'windowstart': # pragma: no cover
|
|
registration_closure = startdatetime
|
|
elif registration_form == 'windowend': # pragma: no cover
|
|
registration_closure = enddatetime
|
|
else:
|
|
registration_closure = ps.evaluation_closure
|
|
|
|
ps.registration_closure = registration_closure
|
|
|
|
ps.timezone = timezone_str
|
|
|
|
if ps.sessiontype == 'fastest_distance': # pragma: no cover
|
|
ps.approximate_distance = ps.sessionvalue
|
|
|
|
if ps.course is not None: # pragma: no cover
|
|
ps.approximate_distance = ps.course.distance
|
|
|
|
ps.save()
|
|
|
|
return 1, 'Virtual Race Updated'
|
|
|
|
|
|
def update_virtualrace(ps, cd):
|
|
for attr, value in cd.items():
|
|
if attr == 'comment':
|
|
value = htmlstripnobr(value)
|
|
value.replace("\r\n", "
")
|
|
value.replace("\n", "
")
|
|
setattr(ps, attr, value)
|
|
|
|
# correct times
|
|
|
|
course = cd['course']
|
|
geocourse = GeoCourse.objects.get(id=course.id)
|
|
timezone_str = 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)
|
|
)
|
|
|
|
registration_form = cd['registration_form']
|
|
registration_closure = cd['registration_closure']
|
|
if registration_form == 'manual': # pragma: no cover
|
|
try:
|
|
registration_closure = pytz.timezone(
|
|
timezone_str
|
|
).localize(
|
|
registration_closure.replace(tzinfo=None)
|
|
)
|
|
except AttributeError:
|
|
registration_closure = startdatetime
|
|
elif registration_form == 'windowstart': # pragma: no cover
|
|
registration_closure = startdatetime
|
|
elif registration_form == 'windowend': # pragma: no cover
|
|
registration_closure = enddatetime
|
|
else:
|
|
registration_closure = ps.evaluation_closure
|
|
|
|
ps.registration_closure = registration_closure
|
|
|
|
ps.timezone = timezone_str
|
|
|
|
if ps.sessiontype == 'fastest_distance': # pragma: no cover
|
|
ps.approximate_distance = ps.sessionvalue
|
|
|
|
if ps.course is not None:
|
|
ps.approximate_distance = ps.course.distance
|
|
|
|
ps.save()
|
|
|
|
return 1, 'Virtual Race Updated'
|
|
|
|
|
|
def race_rower_status(r, race):
|
|
|
|
has_registered = False
|
|
is_complete = False
|
|
|
|
if race.sessiontype in ['race']:
|
|
resultobj = VirtualRaceResult
|
|
else:
|
|
resultobj = IndoorVirtualRaceResult
|
|
|
|
vs = resultobj.objects.filter(userid=r.id, race=race)
|
|
if vs:
|
|
has_registered = True
|
|
is_complete = vs[0].coursecompleted
|
|
|
|
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
|
|
)
|
|
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
|
|
else: # pragma: no cover
|
|
return False
|
|
|
|
return False # pragma: no cover
|
|
|
|
|
|
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 is False:
|
|
return True
|
|
else:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
return False # pragma: no cover
|
|
|
|
|
|
def race_can_editentry(r, race):
|
|
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() < evaluation_closure:
|
|
is_complete, has_registered = race_rower_status(r, race)
|
|
if is_complete is False:
|
|
return True
|
|
else: # pragma: no cover
|
|
return False
|
|
else: # pragma: no cover
|
|
return False
|
|
|
|
return False # pragma: no cover
|
|
|
|
|
|
def race_can_resubmit(r, race):
|
|
records = VirtualRaceResult.objects.filter(
|
|
userid=r.id,
|
|
race=race)
|
|
|
|
if not records:
|
|
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)
|
|
return is_complete
|
|
else: # pragma: no cover
|
|
return False
|
|
|
|
return False # pragma: no cover
|
|
|
|
|
|
def race_can_adddiscipline(r, race):
|
|
|
|
if race.sessiontype not in ['race', 'fastest_time', 'fastest_distance']:
|
|
return False
|
|
|
|
if race.sessiontype in ['race']:
|
|
resultobj = VirtualRaceResult
|
|
else: # pragma: no cover
|
|
resultobj = IndoorVirtualRaceResult
|
|
|
|
records = resultobj.objects.filter(
|
|
userid=r.id,
|
|
race=race)
|
|
|
|
if not records:
|
|
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() < evaluation_closure:
|
|
is_complete, has_registered = race_rower_status(r, race)
|
|
if has_registered:
|
|
return True
|
|
else: # pragma: no cover
|
|
return False
|
|
else: # pragma: no cover
|
|
return False
|
|
|
|
return False # pragma: no cover
|
|
|
|
|
|
def race_can_withdraw(r, race):
|
|
if race.sessiontype in ['race']:
|
|
recordobj = VirtualRaceResult
|
|
else:
|
|
recordobj = IndoorVirtualRaceResult
|
|
|
|
records = recordobj.objects.filter(
|
|
userid=r.id,
|
|
race=race
|
|
)
|
|
|
|
if not records:
|
|
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 None or registration_closure == '': # pragma: no cover
|
|
registration_closure = startdatetime
|
|
|
|
if timezone.now() > registration_closure: # pragma: no cover
|
|
return False
|
|
elif timezone.now() > startdatetime:
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def race_can_register(r, race):
|
|
if race.sessiontype in ['race']:
|
|
recordobj = VirtualRaceResult
|
|
else:
|
|
recordobj = IndoorVirtualRaceResult
|
|
|
|
records = recordobj.objects.filter(
|
|
userid=r.id,
|
|
race=race)
|
|
|
|
if records:
|
|
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 None or registration_closure == '': # pragma: no cover
|
|
registration_closure = startdatetime
|
|
|
|
if timezone.now() > registration_closure: # pragma: no cover
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def add_rower_race(r, race):
|
|
race.rower.add(r)
|
|
race.save()
|
|
|
|
return 1
|
|
|
|
|
|
def remove_rower_race(r, race, recordid=None):
|
|
race.rower.remove(r)
|
|
|
|
if race.sessiontype in ['race']: # pragma: no cover
|
|
recordobj = VirtualRaceResult
|
|
else:
|
|
recordobj = IndoorVirtualRaceResult
|
|
|
|
if recordid: # pragma: no cover
|
|
records = recordobj.objects.filter(userid=r.id,
|
|
workoutid__isnull=True,
|
|
race=race,
|
|
id=recordid)
|
|
else:
|
|
records = recordobj.objects.filter(userid=r.id,
|
|
workoutid__isnull=True,
|
|
race=race,)
|
|
for r in records:
|
|
r.delete()
|
|
|
|
return 1
|
|
|
|
|
|
def default_class(r, w, race):
|
|
if r.birthdate:
|
|
age = calculate_age(r.birthdate)
|
|
else: # pragma: no cover
|
|
age = 25
|
|
|
|
sex = r.sex
|
|
if sex == 'not specified':
|
|
sex = 'male'
|
|
|
|
if w is not None: # pragma: no cover
|
|
boatclass = w.workouttype
|
|
boattype = w.boattype
|
|
|
|
adaptiveclass = w.adaptiveclass
|
|
weightclass = w.weightcategory
|
|
else:
|
|
if race.sessiontype in ['race', 'fastest_time', 'fastest_distance']:
|
|
boatclass = 'water'
|
|
else: # pragma: no cover
|
|
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,
|
|
standardcollection=race.coursestandards,
|
|
).order_by("agemax", "-agemin", "boattype", "sex")
|
|
|
|
if standards.count() == 0: # pragma: no cover
|
|
# omit weight
|
|
standards = CourseStandard.objects.filter(
|
|
agemin__lt=age, agemax__gt=age,
|
|
boatclass=boatclass,
|
|
adaptiveclass=adaptiveclass,
|
|
boattype=boattype,
|
|
sex=sex,
|
|
standardcollection=race.coursestandards,
|
|
).order_by(
|
|
"agemax", "-agemin", "boattype", "sex", "weightclass",
|
|
"referencespeed"
|
|
)
|
|
|
|
if standards.count() == 0:
|
|
# omit adaptive class
|
|
standards = CourseStandard.objects.filter(
|
|
agemin__lt=age, agemax__gt=age,
|
|
boattype=boattype, sex=sex,
|
|
standardcollection=race.coursestandards,
|
|
).order_by(
|
|
"agemax", "-agemin", "boattype", "sex",
|
|
"weightclass", "referencespeed")
|
|
|
|
if standards.count() == 0:
|
|
# omit boattype
|
|
standards = CourseStandard.objects.filter(
|
|
agemin__lt=age, agemax__gt=age, sex=sex,
|
|
standardcollection=race.coursestandards,
|
|
).order_by(
|
|
"agemax", "-agemin", "boattype", "sex",
|
|
"weightclass", "referencespeed")
|
|
|
|
if standards.count() == 0:
|
|
# omit boattype
|
|
standards = CourseStandard.objects.filter(
|
|
agemin__lt=age, agemax__gt=age, sex='male',
|
|
standardcollection=race.coursestandards
|
|
).order_by(
|
|
"agemax", "-agemin", "boattype", "sex",
|
|
"weightclass", "referencespeed")
|
|
|
|
if standards.count() == 0:
|
|
# boolean, boattype, boatclass, adaptiveclass, weightclass, sex, coursestandard,
|
|
return False, '1x', 'water', None, 'hwt', 'male', 5.0, None
|
|
|
|
if standards.count() > 0:
|
|
# find optimum standard
|
|
s = standards[0]
|
|
return True, s.boattype, s.boatclass, s.adaptiveclass, s.weightclass, s.sex, s.referencespeed, s
|
|
|
|
# No Course Standard
|
|
return True, boattype, boatclass, adaptiveclass, weightclass, sex, 5.0, None
|
|
|
|
|
|
def add_workout_fastestrace(ws, race, r, recordid=0, doregister=False):
|
|
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
|
|
)
|
|
|
|
# from ws, remove any w where w.workoutsource = 'strava'. For each removal add an error "strava workout not permitted" to the errors list and if there are no workouts left, return 0, comments, errors, 0
|
|
ws2 = []
|
|
for w in ws:
|
|
if w.workoutsource != 'strava':
|
|
ws2.append(w)
|
|
else:
|
|
errors.append('Strava workouts are not permitted')
|
|
|
|
ws = ws2
|
|
|
|
if len(ws) == 0:
|
|
return result, comments, errors, 0
|
|
|
|
ids = [w.id for w in ws]
|
|
ids = list(set(ids))
|
|
|
|
|
|
if len(ids) > 1 and race.sessiontype in ['test', 'coursetest', 'race', 'indoorrace', 'fastest_time', 'fastest_distance']: # pragma: no cover
|
|
errors.append('For tests, you can only attach one workout')
|
|
return result, comments, errors, 0
|
|
|
|
|
|
if r.birthdate:
|
|
age = calculate_age(r.birthdate)
|
|
else: # pragma: no cover
|
|
age = None
|
|
|
|
try:
|
|
record = IndoorVirtualRaceResult.objects.get(
|
|
userid=r.id,
|
|
race=race,
|
|
id=recordid
|
|
)
|
|
except IndoorVirtualRaceResult.DoesNotExist: # pragma: no cover
|
|
if doregister:
|
|
hasinitial, boattype, boatclass, adaptiveclass, weightclass, sex, referencespeed, 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,
|
|
referencespeed=referencespeed,
|
|
entrycategory=initialcategory,
|
|
)
|
|
record.save()
|
|
else:
|
|
errors.append("Unable to find a suitable start category")
|
|
return result, comments, errors, 0
|
|
else: # pragma: no cover
|
|
errors.append("Couldn't find this entry")
|
|
return result, comments, errors, 0
|
|
|
|
records = IndoorVirtualRaceResult.objects.filter(
|
|
userid=r.id,
|
|
race=race,
|
|
)
|
|
|
|
if ws[0].workouttype != record.boatclass: # pragma: no cover
|
|
errors.append(
|
|
'Your workout boat class is different than on your race registration')
|
|
return 0, comments, errors, 0
|
|
|
|
if ws[0].workouttype not in mytypes.otwtypes: # pragma: no cover
|
|
errors.append('You must submit a on-the-water rowing workout')
|
|
return 0, comments, errors, 0
|
|
|
|
if record.weightcategory == 'lwt' and ws[0].weightcategory != record.weightcategory: # pragma: no cover
|
|
errors.append(
|
|
'Your workout weight category did not match the weight category you registered')
|
|
return 0, comments, errors, 0
|
|
|
|
if ws[0].adaptiveclass != record.adaptiveclass: # pragma: no cover
|
|
errors.append(
|
|
'Your adaptive classification did not match the registration')
|
|
return 0, comments, errors, 0
|
|
|
|
# start adding sessions
|
|
if ws[0].startdatetime >= startdatetime and ws[0].startdatetime <= enddatetime:
|
|
ws[0].plannedsession = race
|
|
ws[0].save()
|
|
result += 1
|
|
|
|
else: # pragma: no cover
|
|
errors.append('Workout %i did not match the race window' % ws[0].id)
|
|
return result, comments, errors, 0
|
|
|
|
if result > 0:
|
|
for otherrecord in records: # pragma: no cover
|
|
oldworkouts = Workout.objects.filter(plannedsession=race)
|
|
for oldworkout in oldworkouts:
|
|
oldworkout.plannedsession = None
|
|
oldworkout.save()
|
|
|
|
otherrecord.workoutid = None
|
|
otherrecord.coursecompleted = False
|
|
otherrecord.save()
|
|
|
|
result, comment, errors = add_workouts_plannedsession(ws, race, r)
|
|
if result:
|
|
record.coursecompleted = True
|
|
record.workoutid = ws[0].id
|
|
if race.sessiontype == 'fastest_distance':
|
|
df = dataprep.read_data(
|
|
['time', 'cumdist'], ids=[ws[0].id])
|
|
df = dataprep.remove_nulls_pl(df)
|
|
fastest_milliseconds, startsecond, endsecond = datautils.getfastest(
|
|
df, race.sessionvalue, mode='distance')
|
|
velo = race.sessionvalue/fastest_milliseconds
|
|
points = 100.*velo/record.referencespeed
|
|
|
|
if fastest_milliseconds > 0:
|
|
duration = to_time(1000.*fastest_milliseconds)
|
|
record.coursecompleted = True
|
|
record.duration = duration
|
|
record.distance = race.sessionvalue
|
|
record.points = points
|
|
record.startsecond = startsecond
|
|
record.endsecond = endsecond
|
|
record.save()
|
|
if race.sessiontype == 'fastest_time': # pragma: no cover
|
|
df = dataprep.read_data(
|
|
['time', 'cumdist'], ids=[ws[0].id])
|
|
df = dataprep.remove_nulls_pl(df)
|
|
fastest_meters, startsecond, endsecond = datautils.getfastest(
|
|
df, race.sessionvalue, mode='time')
|
|
velo = fastest_meters/(60.*race.sessionvalue)
|
|
points = 100.*velo/record.referencespeed
|
|
|
|
if fastest_meters > 0:
|
|
duration = dt.time(0, race.sessionvalue)
|
|
record.duration = duration
|
|
record.distance = fastest_meters
|
|
record.coursecompleted = True
|
|
record.points = points
|
|
record.startsecond = startsecond
|
|
record.endsecond = endsecond
|
|
record.save()
|
|
|
|
if ws[0].privacy == 'private': # pragma: no cover
|
|
ws[0].privacy = 'visible'
|
|
ws[0].save()
|
|
comments.append(
|
|
'Workouts submitted to virtual events have to be public. We have changed the workout to a public workout.')
|
|
|
|
record.save()
|
|
else: # pragma: no cover
|
|
errors.append('Could not find a valid interval in this workout')
|
|
|
|
return result, comments, errors, 0
|
|
|
|
|
|
# Low Level functions - to be called by higher level methods
|
|
def add_workout_indoorrace(ws, race, r, recordid=0, doregister=False):
|
|
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
|
|
)
|
|
|
|
# from ws, remove any w where w.workoutsource = 'strava'. For each removal add an error "strava workout not permitted" to the errors list and if there are no workouts left, return 0, comments, errors, 0
|
|
ws2 = []
|
|
for w in ws:
|
|
if w.workoutsource != 'strava':
|
|
ws2.append(w)
|
|
else:
|
|
errors.append('Strava workouts are not permitted')
|
|
|
|
ws = ws2
|
|
|
|
if len(ws) == 0:
|
|
return result, comments, errors, 0
|
|
|
|
# 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']: # pragma: no cover
|
|
errors.append(
|
|
'For tests and training sessions, selected workouts must all be done on the same date')
|
|
return result, comments, errors, 0
|
|
|
|
if len(ws) > 1 and race.sessiontype == 'test': # pragma: no cover
|
|
errors.append('For tests, you can only attach one workout')
|
|
return result, comments, errors, 0
|
|
|
|
ids = [w.id for w in ws]
|
|
ids = list(set(ids))
|
|
|
|
if len(ids) > 1 and race.sessiontype in ['test', 'coursetest', 'race', 'indoorrace', 'fastest_time', 'fastest_distance']: # pragma: no cover
|
|
errors.append('For tests, you can only attach one workout')
|
|
return result, comments, errors, 0
|
|
|
|
if r.birthdate:
|
|
age = calculate_age(r.birthdate)
|
|
else: # pragma: no cover
|
|
age = None
|
|
|
|
try:
|
|
record = IndoorVirtualRaceResult.objects.get(
|
|
userid=r.id,
|
|
race=race,
|
|
id=recordid
|
|
)
|
|
except IndoorVirtualRaceResult.DoesNotExist: # pragma: no cover
|
|
if doregister:
|
|
hasinitial, boattype, boatclass, adaptiveclass, weightclass, sex, referencespeed, 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,
|
|
referencespeed=referencespeed,
|
|
entrycategory=initialcategory,
|
|
)
|
|
record.save()
|
|
else:
|
|
errors.append("Unable to find a suitable start category")
|
|
return result, comments, errors, 0
|
|
else: # pragma: no cover
|
|
errors.append("Couldn't find this entry")
|
|
return result, comments, errors, 0
|
|
|
|
records = IndoorVirtualRaceResult.objects.filter(
|
|
userid=r.id,
|
|
race=race,
|
|
workoutid=ws[0].id
|
|
)
|
|
|
|
if race.sessionmode == 'distance':
|
|
if ws[0].distance != race.sessionvalue:
|
|
errors.append('Your workout did not have the correct distance')
|
|
return 0, comments, errors, 0
|
|
else:
|
|
record.distance = ws[0].distance
|
|
record.duration = ws[0].duration
|
|
else: # pragma: no cover
|
|
t = ws[0].duration
|
|
seconds = t.second+t.minute*60.+t.hour*3600.+t.microsecond/1.e6
|
|
if seconds != race.sessionvalue*60.: # pragma: no cover
|
|
errors.append('Your workout did not have the correct duration')
|
|
return 0, comments, errors, 0
|
|
else:
|
|
record.distance = ws[0].distance
|
|
record.duration = ws[0].duration
|
|
|
|
if ws[0].workouttype != record.boatclass:
|
|
errors.append(
|
|
'Your workout boat class is different than on your race registration')
|
|
return 0, comments, errors, 0
|
|
|
|
if ws[0].workouttype not in mytypes.otetypes: # pragma: no cover
|
|
errors.append('You must submit a indoor rowing workout')
|
|
return 0, comments, errors, 0
|
|
|
|
if record.weightcategory == 'lwt' and ws[0].weightcategory != record.weightcategory: # pragma: no cover
|
|
errors.append(
|
|
'Your workout weight category did not match the weight category you registered')
|
|
return 0, comments, errors, 0
|
|
|
|
if ws[0].adaptiveclass != record.adaptiveclass: # pragma: no cover
|
|
errors.append(
|
|
'Your adaptive classification did not match the registration')
|
|
return 0, comments, errors, 0
|
|
|
|
# start adding sessions
|
|
if ws[0].startdatetime >= startdatetime and ws[0].startdatetime <= enddatetime:
|
|
ws[0].plannedsession = race
|
|
ws[0].save()
|
|
result += 1
|
|
|
|
else: # pragma: no cover
|
|
errors.append('Workout %i did not match the race window' % ws[0].id)
|
|
return result, comments, errors, 0
|
|
|
|
if result > 0:
|
|
for otherrecord in records: # pragma: no cover
|
|
otherrecord.workoutid = None
|
|
otherrecord.coursecompleted = False
|
|
otherrecord.save()
|
|
|
|
record.coursecompleted = True
|
|
record.workoutid = ws[0].id
|
|
|
|
if ws[0].privacy == 'private': # pragma: no cover
|
|
ws[0].privacy = 'visible'
|
|
ws[0].save()
|
|
comments.append(
|
|
'Workouts submitted to virtual events have to be public. We have changed the workout to a public workout.')
|
|
|
|
record.save()
|
|
|
|
add_workouts_plannedsession(ws, race, r)
|
|
|
|
return result, comments, errors, 0
|
|
|
|
|
|
def add_workout_race(ws, race, r, splitsecond=0, recordid=0, doregister=False):
|
|
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
|
|
)
|
|
|
|
# from ws, remove any w where w.workoutsource = 'strava'. For each removal add an error "strava workout not permitted" to the errors list and if there are no workouts left, return 0, comments, errors, 0
|
|
ws2 = []
|
|
for w in ws:
|
|
if w.workoutsource != 'strava':
|
|
ws2.append(w)
|
|
else:
|
|
errors.append('Strava workouts are not permitted')
|
|
|
|
ws = ws2
|
|
|
|
if len(ws) == 0:
|
|
return result, comments, errors, 0
|
|
|
|
# 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']: # pragma: no cover
|
|
errors.append(
|
|
'For tests and training sessions, selected workouts must all be done on the same date')
|
|
return result, comments, errors, 0
|
|
|
|
if len(ws) > 1 and race.sessiontype == 'test': # pragma: no cover
|
|
errors.append('For tests, you can only attach one workout')
|
|
return result, comments, errors, 0
|
|
|
|
ids = [w.id for w in ws]
|
|
ids = list(set(ids))
|
|
|
|
if len(ids) > 1 and race.sessiontype in ['test', 'coursetest', 'race', 'fastest_time', 'fastest_distance']: # pragma: no cover
|
|
errors.append('For tests, you can only attach one workout')
|
|
return result, comments, errors, 0
|
|
|
|
if r.birthdate:
|
|
age = calculate_age(r.birthdate)
|
|
else: # pragma: no cover
|
|
age = None
|
|
|
|
try:
|
|
record = VirtualRaceResult.objects.get(
|
|
userid=r.id,
|
|
race=race,
|
|
id=recordid
|
|
)
|
|
except VirtualRaceResult.DoesNotExist: # pragma: no cover
|
|
if doregister:
|
|
hasinitial, boattype, boatclass, adaptiveclass, weightclass, sex, referencespeed, 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,
|
|
referencespeed=referencespeed,
|
|
)
|
|
record.save()
|
|
add_rower_race(r, race)
|
|
else:
|
|
errors.append("Unable to find a suitable start category")
|
|
return result, comments, errors, 0
|
|
else: # pragma: no cover
|
|
errors.append("Couldn't find this entry")
|
|
return result, comments, errors, 0
|
|
|
|
records = VirtualRaceResult.objects.filter(
|
|
userid=r.id,
|
|
race=race,
|
|
workoutid=ws[0].id
|
|
)
|
|
|
|
if not record and not doregister: # pragma: no cover
|
|
errors.append("Couldn't find this entry")
|
|
return result, comments, errors, 0
|
|
|
|
# if ws[0].workouttype not in mytypes.otwtypes:
|
|
# errors.append('You have to submit a rowing on water workout')
|
|
# return 0,comments,errors,0
|
|
|
|
if ws[0].workouttype != record.boatclass: # pragma: no cover
|
|
ws[0].workouttype = record.boatclass
|
|
ws[0].save()
|
|
|
|
if ws[0].boattype != record.boattype: # pragma: no cover
|
|
errors.append(
|
|
'Your workout boat type did not match the boat type you registered')
|
|
return 0, comments, errors, 0
|
|
|
|
if record.weightcategory == 'lwt' and ws[0].weightcategory != record.weightcategory: # pragma: no cover
|
|
errors.append(
|
|
'Your workout weight category did not match the weight category you registered')
|
|
return 0, comments, errors, 0
|
|
|
|
if ws[0].adaptiveclass != record.adaptiveclass: # pragma: no cover
|
|
errors.append(
|
|
'Your workout adaptive classification did not match the registration')
|
|
return 0, comments, errors, 0
|
|
|
|
# start adding sessions
|
|
if ws[0].startdatetime >= startdatetime and ws[0].startdatetime <= enddatetime:
|
|
# convert to gps
|
|
row = rdata(ws[0].csvfilename)
|
|
success = row.use_gpsdata()
|
|
if success:
|
|
row.write_csv(ws[0].csvfilename)
|
|
dataprep.update_strokedata(ws[0].id, row.df)
|
|
ws[0].impeller = False
|
|
|
|
ws[0].plannedsession = race
|
|
ws[0].save()
|
|
result += 1
|
|
|
|
else: # pragma: no cover
|
|
errors.append('Workout %i did not match the race window' % ws[0].id)
|
|
return result, comments, errors, 0
|
|
|
|
if result > 0:
|
|
for otherrecord in records: # pragma: no cover
|
|
otherrecord.workoutid = None
|
|
otherrecord.coursecompleted = False
|
|
otherrecord.save()
|
|
|
|
if ws[0].privacy == 'private': # pragma: no cover
|
|
ws[0].privacy = 'visible'
|
|
ws[0].save()
|
|
comments.append(
|
|
'Workouts submitted to virtual events have to be public. We have changed the workout to a public workout.')
|
|
|
|
job = myqueue(queue, handle_check_race_course, ws[0].csvfilename,
|
|
ws[0].id, race.course.id, record.id,
|
|
ws[0].user.user.email, ws[0].user.user.first_name,
|
|
splitsecond=splitsecond,
|
|
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)
|
|
|
|
return result, comments, errors, job.id
|
|
|
|
|
|
def delete_race_result(workout, race): # pragma: no cover
|
|
results = VirtualRaceResult.objects.filter(workoutid=workout.id, race=race)
|
|
for r in results:
|
|
r.workoutid = None
|
|
r.save()
|