Private
Public Access
1
0
Files
rowsandall/rowers/plannedsessions.py

2097 lines
70 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:
ps.rower.add(r)
ps.save()
return 1
elif ps.manager.rower == r:
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,
plan_past_days=False,
asynchronous=True, 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'
if plan_past_days or startdate >= timezone.now().date():
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, plan_past_days)
def update_plannedsession(ps, cd):
for attr, value in cd.items():
if attr == 'comment':
value.replace("\r\n", "&#10")
value.replace("\n", "&#10")
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", "&#10")
value.replace("\n", "&#10")
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", "&#10")
value.replace("\n", "&#10")
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()