Private
Public Access
1
0

Merge branch 'feature/indoor' into develop

This commit is contained in:
Sander Roosendaal
2018-11-28 16:02:00 +01:00
16 changed files with 1427 additions and 71 deletions
+2
View File
@@ -45,6 +45,8 @@ env = Environment(loader = FileSystemLoader(["rowers/templates"]))
from django.contrib.staticfiles import finders from django.contrib.staticfiles import finders
def textify(html): def textify(html):
# Remove html tags and continuous whitespaces # Remove html tags and continuous whitespaces
text_only = re.sub('[ \t]+', ' ', strip_tags(html)) text_only = re.sub('[ \t]+', ' ', strip_tags(html))
+21
View File
@@ -30,6 +30,24 @@ class EmailForm(forms.Form):
botcheck = forms.CharField(max_length=5) botcheck = forms.CharField(max_length=5)
message = forms.CharField() message = forms.CharField()
disqualificationreasons = (
('noimage','You did not attach a monitor screenshot or other photographic evidence as required'),
('suspicious','We doubt that you rowed this in the right boat class or type'),
('duplicate','This result looks like a duplicate entry'),
('other','Other Reason'),
)
disqualifiers = {}
for key, value in disqualificationreasons:
disqualifiers[key] = value
class DisqualificationForm(forms.Form):
reason = forms.ChoiceField(required=True,
choices=disqualificationreasons,
widget = forms.RadioSelect,)
message = forms.CharField(required=True,widget=forms.Textarea)
class MetricsForm(forms.Form): class MetricsForm(forms.Form):
avghr = forms.IntegerField(required=False,label='Average Heart Rate') avghr = forms.IntegerField(required=False,label='Average Heart Rate')
@@ -892,8 +910,11 @@ class RaceResultFilterForm(forms.Form):
self.fields['boatclass'].choices = boatclasschoices self.fields['boatclass'].choices = boatclasschoices
# boattype # boattype
try:
theboattypees = [record.boattype for record in records] theboattypees = [record.boattype for record in records]
theboattypees = list(set(theboattypees)) theboattypees = list(set(theboattypees))
except AttributeError:
theboattypees = []
if len(theboattypees)<= 1: if len(theboattypees)<= 1:
del self.fields['boattype'] del self.fields['boattype']
+179 -1
View File
@@ -1664,6 +1664,7 @@ class PlannedSession(models.Model):
('cycletarget','Total for a time period'), ('cycletarget','Total for a time period'),
('coursetest','OTW test over a course'), ('coursetest','OTW test over a course'),
('race','Virtual Race'), ('race','Virtual Race'),
('indoorrace','Indoor Virtual Race'),
) )
sessionmodechoices = ( sessionmodechoices = (
@@ -1772,7 +1773,7 @@ class PlannedSession(models.Model):
else: else:
self.sessionunit = 'None' self.sessionunit = 'None'
if self.sessiontype == 'test': if self.sessiontype == 'test' or self.sessiontype == 'indoorrace':
if self.sessionmode not in ['distance','time']: if self.sessionmode not in ['distance','time']:
if self.sessionvalue < 100: if self.sessionvalue < 100:
self.sessionmode = 'time' self.sessionmode = 'time'
@@ -1937,6 +1938,125 @@ def get_course_timezone(course):
return timezone_str return timezone_str
class IndoorVirtualRaceForm(ModelForm):
registration_closure = forms.SplitDateTimeField(widget=AdminSplitDateTime(),required=False)
evaluation_closure = forms.SplitDateTimeField(widget=AdminSplitDateTime(),required=True)
timezone = forms.ChoiceField(initial='UTC',
choices=[(x,x) for x in pytz.common_timezones],
label='Time Zone')
class Meta:
model = VirtualRace
fields = [
'name',
'startdate',
'start_time',
'enddate',
'end_time',
'timezone',
'sessionvalue',
'sessionunit',
'registration_form',
'registration_closure',
'evaluation_closure',
'comment',
'contact_phone',
'contact_email',
]
dateTimeOptions = {
'format': 'yyyy-mm-dd',
'autoclose': True,
}
widgets = {
'comment': forms.Textarea,
'startdate': AdminDateWidget(),
'enddate': AdminDateWidget(),
'start_time': AdminTimeWidget(),
'end_time': AdminTimeWidget(),
'registration_closure':AdminSplitDateTime(),
'evaluation_closure':AdminSplitDateTime(),
}
def __init__(self,*args,**kwargs):
super(IndoorVirtualRaceForm, self).__init__(*args, **kwargs)
self.fields['sessionunit'].choices = [('min','minutes'),('m','meters')]
def clean(self):
cd = self.cleaned_data
timezone_str = cd['timezone']
start_time = cd['start_time']
if start_time is None:
raise forms.ValidationError(
'Must have start time',
code='missing_yparam1'
)
start_date = cd['startdate']
startdatetime = datetime.datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(timezone_str).localize(
startdatetime
)
end_time = cd['end_time']
if end_time is None:
raise forms.ValidationError(
'Must have end time',
code='missing endtime'
)
end_date = cd['enddate']
enddatetime = datetime.datetime.combine(end_date,end_time)
enddatetime = pytz.timezone(timezone_str).localize(
enddatetime
)
registration_closure = cd['registration_closure']
registration_form = cd['registration_form']
try:
evaluation_closure = cd['evaluation_closure']
except KeyError:
evaluation_closure = enddatetime+datetime.timedelta(days=1)
cd['evaluation_closure'] = evaluation_closure
if registration_form == 'manual':
try:
registration_closure = pytz.timezone(
timezone_str
).localize(
registration_closure.replace(tzinfo=None)
)
except AttributeError:
registration_closure = startdatetime
elif registration_form == 'windowstart':
registration_closure = startdatetime
elif registration_form == 'windowend':
registration_closure = enddatetime
else:
registration_closure = evaluation_closure
if registration_closure <= timezone.now():
raise forms.ValidationError("Registration Closure cannot be in the past")
if startdatetime > enddatetime:
raise forms.ValidationError("The Start of the Race Window should be before the End of the Race Window")
if cd['evaluation_closure'] <= enddatetime:
raise forms.ValidationError("Evaluation closure deadline should be after the Race Window closes")
if cd['evaluation_closure'] <= timezone.now():
raise forms.ValidationError("Evaluation closure cannot be in the past")
return cd
class VirtualRaceForm(ModelForm): class VirtualRaceForm(ModelForm):
course = forms.ModelChoiceField(queryset = GeoCourse.objects, empty_label=None) course = forms.ModelChoiceField(queryset = GeoCourse.objects, empty_label=None)
@@ -2309,6 +2429,54 @@ class VirtualRaceResult(models.Model):
s = self.sex, s = self.sex,
) )
# Virtual Race results (for keeping results when workouts are deleted)
class IndoorVirtualRaceResult(models.Model):
boatclasses = (type for type in mytypes.workouttypes if type[0] in mytypes.otetypes)
userid = models.IntegerField(default=0)
teamname = models.CharField(max_length=80,verbose_name = 'Team Name',
blank=True,null=True)
username = models.CharField(max_length=150)
workoutid = models.IntegerField(null=True)
weightcategory = models.CharField(default="hwt",max_length=10,
choices=weightcategories,
verbose_name='Weight Category')
race = models.ForeignKey(VirtualRace)
duration = models.TimeField(default=datetime.time(1,0))
distance = models.IntegerField(default=0)
boatclass = models.CharField(choices=boatclasses,
max_length=40,
default='rower',
verbose_name = 'Ergometer Class')
coursecompleted = models.BooleanField(default=False)
sex = models.CharField(default="not specified",
max_length=30,
choices=sexcategories,
verbose_name='Gender')
age = models.IntegerField(null=True)
def __unicode__(self):
rr = Rower.objects.get(id=self.userid)
name = '{u1} {u2}'.format(
u1 = rr.user.first_name,
u2 = rr.user.last_name,
)
if self.teamname:
return u'Entry for {n} for "{r}" on {c} with {t} ({s})'.format(
n = name,
r = self.race,
t = self.teamname,
c = self.boatclass,
s = self.sex,
)
else:
return u'Entry for {n} for "{r}" on {c} ({s})'.format(
n = name,
r = self.race,
c = self.boatclass,
s = self.sex,
)
class CourseTestResult(models.Model): class CourseTestResult(models.Model):
userid = models.IntegerField(default=0) userid = models.IntegerField(default=0)
@@ -2318,6 +2486,16 @@ class CourseTestResult(models.Model):
distance = models.IntegerField(default=0) distance = models.IntegerField(default=0)
coursecompleted = models.BooleanField(default=False) coursecompleted = models.BooleanField(default=False)
class IndoorVirtualRaceResultForm(ModelForm):
class Meta:
model = IndoorVirtualRaceResult
fields = ['teamname','weightcategory','boatclass','age']
def __init__(self, *args, **kwargs):
super(IndoorVirtualRaceResultForm, self).__init__(*args, **kwargs)
class VirtualRaceResultForm(ModelForm): class VirtualRaceResultForm(ModelForm):
class Meta: class Meta:
model = VirtualRaceResult model = VirtualRaceResult
+6
View File
@@ -225,6 +225,12 @@ otwtypes = (
'churchboat' 'churchboat'
) )
otetypes = (
'rower',
'dynamic',
'slides'
)
rowtypes = ( rowtypes = (
'water', 'water',
'rower', 'rower',
+184 -6
View File
@@ -20,7 +20,7 @@ from rowers.models import (
Rower, Workout,Team, Rower, Workout,Team,
GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle, GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle,
TrainingPlan,PlannedSession,VirtualRaceResult,CourseTestResult, TrainingPlan,PlannedSession,VirtualRaceResult,CourseTestResult,
get_course_timezone get_course_timezone, IndoorVirtualRaceResult
) )
from rowers.courses import get_time_course from rowers.courses import get_time_course
@@ -574,6 +574,56 @@ def update_plannedsession(ps,cd):
return 1,'Planned Session Updated' return 1,'Planned Session Updated'
def update_indoorvirtualrace(ps,cd):
for attr, value in cd.items():
if attr == 'comment':
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':
try:
registration_closure = pytz.timezone(
timezone_str
).localize(
registration_closure.replace(tzinfo=None)
)
except AttributeError:
registration_closure = startdatetime
elif registration_form == 'windowstart':
registration_closure = startdatetime
elif registration_form == 'windowend':
registration_closure = enddatetime
else:
registration_closure = ps.evaluation_closure
ps.registration_closure = registration_closure
ps.timezone = timezone_str
ps.save()
return 1,'Virtual Race Updated'
def update_virtualrace(ps,cd): def update_virtualrace(ps,cd):
for attr, value in cd.items(): for attr, value in cd.items():
if attr == 'comment': if attr == 'comment':
@@ -631,7 +681,12 @@ def race_rower_status(r,race):
has_registered = False has_registered = False
is_complete = False is_complete = False
vs = VirtualRaceResult.objects.filter(userid=r.id,race=race) if race.sessiontype == 'race':
resultobj = VirtualRaceResult
else:
resultobj = IndoorVirtualRaceResult
vs = IndoorVirtualRaceResult.objects.filter(userid=r.id,race=race)
if vs: if vs:
has_registered = True has_registered = True
is_complete = vs[0].coursecompleted is_complete = vs[0].coursecompleted
@@ -708,6 +763,10 @@ def race_can_resubmit(r,race):
return False return False
def race_can_adddiscipline(r,race): def race_can_adddiscipline(r,race):
if race.sessiontype != 'race':
return False
records = VirtualRaceResult.objects.filter( records = VirtualRaceResult.objects.filter(
userid=r.id, userid=r.id,
race=race) race=race)
@@ -766,7 +825,12 @@ def race_can_withdraw(r,race):
return True return True
def race_can_register(r,race): def race_can_register(r,race):
records = VirtualRaceResult.objects.filter( if race.sessiontype == 'race':
recordobj = VirtualRaceResult
else:
recordobj = IndoorVirtualRaceResult
records = recordobj.objects.filter(
userid=r.id, userid=r.id,
race=race) race=race)
@@ -798,13 +862,18 @@ def add_rower_race(r,race):
def remove_rower_race(r,race,recordid=None): def remove_rower_race(r,race,recordid=None):
race.rower.remove(r) race.rower.remove(r)
if race.sessiontype == 'race':
recordobj = VirtualRaceResult
else:
recordobj = IndoorVirtualRaceResult
if recordid: if recordid:
records = VirtualRaceResult.objects.filter(userid=r.id, records = recordobj.objects.filter(userid=r.id,
workoutid__isnull=True, workoutid__isnull=True,
race=race, race=race,
id=recordid) id=recordid)
else: else:
records = VirtualRaceResult.objects.filter(userid=r.id, records = recordobj.objects.filter(userid=r.id,
workoutid__isnull=True, workoutid__isnull=True,
race=race,) race=race,)
for r in records: for r in records:
@@ -813,6 +882,116 @@ def remove_rower_race(r,race,recordid=None):
return 1 return 1
# Low Level functions - to be called by higher level methods # Low Level functions - to be called by higher level methods
def add_workout_indoorrace(ws,race,r,recordid=0):
result = 0
comments = []
errors = []
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
end_time = race.end_time
end_date = race.enddate
enddatetime = datetime.combine(end_date,end_time)
enddatetime = pytz.timezone(race.timezone).localize(
enddatetime
)
# check if all sessions have same date
dates = [w.date for w in ws]
if (not all(d == dates[0] for d in dates)) and race.sessiontype not in ['challenge','cycletarget']:
errors.append('For tests and training sessions, selected workouts must all be done on the same date')
return result,comments,errors,0
if len(ws)>1 and race.sessiontype == 'test':
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']:
errors.append('For tests, you can only attach one workout')
return result,comments,errors,0
username = r.user.first_name+' '+r.user.last_name
if r.birthdate:
age = calculate_age(r.birthdate)
else:
age = None
record = IndoorVirtualRaceResult.objects.get(
userid=r.id,
race=race,
id=recordid
)
records = IndoorVirtualRaceResult.objects.filter(
userid=r.id,
race=race,
workoutid = ws[0].id
)
if not record:
errors.append("Couldn't find this entry")
return result,comments,errors,0
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:
t = ws[0].duration
seconds = t.second+t.minute*60.+t.hour*3600.+t.microsecond/1.e6
if seconds != race.sessionvalue*60.:
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].weightcategory != record.weightcategory:
errors.append('Your workout weight category did not match the weight category you registered')
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:
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:
otherrecord.workoutid = None
otherrecord.coursecompleted = False
otherrecord.save()
record.coursecompleted = True
record.workoutid = ws[0].id
record.save()
add_workouts_plannedsession(ws,race,r)
return result,comments,errors,0
def add_workout_race(ws,race,r,splitsecond=0,recordid=0): def add_workout_race(ws,race,r,splitsecond=0,recordid=0):
result = 0 result = 0
comments = [] comments = []
@@ -895,7 +1074,6 @@ def add_workout_race(ws,race,r,splitsecond=0,recordid=0):
if result>0: if result>0:
for otherrecord in records: for otherrecord in records:
print otherrecord
otherrecord.workoutid = None otherrecord.workoutid = None
otherrecord.coursecompleted = False otherrecord.coursecompleted = False
otherrecord.save() otherrecord.save()
+45
View File
@@ -41,6 +41,22 @@ from django.utils.html import strip_tags
from utils import deserialize_list,ewmovingaverage,wavg from utils import deserialize_list,ewmovingaverage,wavg
from HTMLParser import HTMLParser
class MLStripper(HTMLParser):
def __init__(self):
self.reset()
self.fed = []
def handle_data(self, d):
self.fed.append(d)
def get_data(self):
return ''.join(self.fed)
def strip_tags(html):
s = MLStripper()
s.feed(html)
return s.get_data()
from rowers.dataprepnodjango import ( from rowers.dataprepnodjango import (
update_strokedata, new_workout_from_file, update_strokedata, new_workout_from_file,
getsmallrowdata_db, updatecpdata_sql, getsmallrowdata_db, updatecpdata_sql,
@@ -721,6 +737,35 @@ def handle_updatedps(useremail, workoutids, debug=False,**kwargs):
# send email when a breakthrough workout is uploaded # send email when a breakthrough workout is uploaded
@app.task
def handle_send_disqualification_email(
useremail,username,reason,message, racename, **kwargs):
if 'debug' in kwargs:
debug = kwargs['debug']
else:
debug = True
subject = "Your result for {n} has been disqualified on rowsandall.com".format(
n = racename
)
from_email = 'Rowsandall <support@rowsandall.com>'
d = {
'username':username,
'reason':reason,
'message': strip_tags(message),
'racename':racename,
}
res = send_template_email(from_email,[useremail],
subject,
'disqualificationemail.html',
d,**kwargs)
return 1
@app.task @app.task
def handle_sendemail_expired(useremail,userfirstname,userlastname,expireddate, def handle_sendemail_expired(useremail,userfirstname,userlastname,expireddate,
**kwargs): **kwargs):
+138
View File
@@ -0,0 +1,138 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block scripts %}
{% include "monitorjobs.html" %}
{% endblock %}
{% block title %}{{ workout.name }} {% endblock %}
{% block og_title %}{{ workout.name }} {% endblock %}
{% block description %}{{ workout.name }}
{{ workout.date }} - {{ workout.distance }}m - {{ workout.duration |durationprint:"%H:%M:%S.%f" }}{% endblock %}
{% block og_description %}{{ workout.name }}
{{ workout.date }} - {{ workout.distance }}m - {{ workout.duration |durationprint:"%H:%M:%S.%f" }}{% endblock %}
{% if graphs1 %}
{% endif %}
{% for graph in graphs1 %}
{% block og_image %}
{% if graphs1 %}
{% for graph in graphs %}
<meta property="og:image" content="http://rowsandall.com/{{ graph.filename |spacetohtml }}" />
<meta property="og:image:secure_url" content="https://rowsandall.com/{{ graph.filename |spacetohtml }}" />
<meta property="og:image:width" content="{{ graph.width }}" />
<meta property="og:image:height" content="{{ graph.height }}" />
{% endfor %}
{% else %}
<meta property="og:image" content="http://rowsandall.com/static/img/logo_r.png" />
<meta property="og:image:secure_url" content="https://rowsandall.com/static/img/logo_r.png" />
{% endif %}
{% endblock %}
{% block image_src %}
{% for graph in graphs %}
<link rel="image_src" href="/{{ graph.filename |spacetohtml }}" />
{% endfor %}
{% endblock %}
{% endfor %}
{% block main %}
<h1>Do you want to disqualify this result?</h1>
<ul class="main-content">
<li class="grid_4">
<p>
Before you reject this race result, please carefully review it
using the information below. If you still want to reject the entry,
scroll down to the rejection form.
</p>
</li>
<li class="grid_2">
<table width=100%>
<tr>
<th>Rower:</th><td>{{ record.username }}</td>
</tr><tr>
<tr>
<th>Name:</th><td>{{ workout.name }}</td>
</tr><tr>
<tr>
<th>Date:</th><td>{{ workout.date }}</td>
</tr><tr>
<th>Time:</th><td>{{ workout.starttime }}</td>
</tr><tr>
<th>Distance:</th><td>{{ workout.distance }}m</td>
</tr><tr>
<th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td>
</tr><tr>
<th>Type:</th><td>{{ workout.workouttype }}</td>
</tr><tr>
<th>Weight Category:</th><td>{{ workout.weightcategory }}</td>
</tr>
</table>
</li>
<li class="grid_2">
<h1>Workout Summary</h1>
<p>
<pre>
{{ workout.summary }}
</pre>
</p>
</li>
{% for graph in graphs %}
<li>
<a href="/rowers/graph/{{ graph.id }}/">
<img src="/{{ graph.filename }}"
onerror="this.src='/static/img/rowingtimer.gif'"
alt="{{ graph.filename }}" width="120" height="100">
</a>
</li>
{% endfor %}
{% if mapdiv %}
<li class="grid_2">
<div class="mapdiv">
{{ mapdiv|safe }}
{{ mapscript|safe }}
</div>
</li>
{% endif %}
<li class="grid_2">
<script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
{{ the_div|safe }}
</li>
<li class="grid_4">
<h1>
Yes, I want to reject this entry
</h1>
<p>
Please select a reason and add a comment (mandatory).
Pressing 'Reject' disqualifies the submitted result.
An email will be sent to the rower. There is no "undo".
</p>
<p>
<form enctype="multipart/form-date" action="" method="post">
<table>
{{ form.as_table }}
</table>
</p>
<p>
{% csrf_token %}
<input type="submit" value="Reject">
</p>
</form>
</li>
</ul>
{% endblock %}
{% block sidebar %}
{% include 'menu_racing.html' %}
{% endblock %}
@@ -0,0 +1,37 @@
{% extends "emailbase.html" %}
{% block body %}
<p>Dear <strong>{{ username }}</strong>,</p>
<p>
Unfortunately, the result that you have submitted
for the virtual race {{ racename }}
has been rejected by the race organizer.
</p>
<p>
The reason for the rejection was: <i>{{ reason }}</i>.
</p>
<p>
The race organizer added the following explanation:
<p>
<p>
<i>{{ message }}</i>
</p>
<p>
The decision to reject your result is the sole responsibility of
the race organizer. If you disagree with the decision, please do contact
him or her.
</p>
<p>
You are still registered for the race and can submit a result.
</p>
<p>
Best Regards, the Rowsandall Team
</p>
{% endblock %}
@@ -0,0 +1,71 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}New Virtual Race{% endblock %}
{% block main %}
<h1>New Indoor Virtual Race</h1>
<ul class="main-content">
<li class="grid_4">
<p>With this form, you can create a new virtual race. After you submit
the form, the race is created and will be visible to all users. From
that moment, only the site admin can delete the race
(admin@rowsandall.com). You can still edit the race until
the start of the race window.
</p>
</li>
<li class="grid_3">
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<p>
<table>
{{ form.as_table }}
</table>
</p>
<p>
{% csrf_token %}
<input type="submit" value="Save">
</p>
</form>
</li>
<li class="grid_1">
<p>
<ul>
<p>All times are local times in the time zone you select</p>
<p>Adding a contact phone number and email is not mandatory, but we
strongly recommend it.</p>
<p>If your event has a registration closure deadline, participants
have to enter (and can withdraw) before the registration closure time.</p>
<p>Participants can submit results until the evaluation closure time.</p>
<p>Until one hour after evaluation closure time, the race organizer
can review and reject submitted results ("disqualification"). If
you as the race organizer intend to use this functionality, it
is strongly recommended that you fill out a contact email or phone
number.
</p>
<p>
If you require a screenshot of the PM monitor, do mention this
in the comment.
</p>
</ul>
</p>
</li>
</ul>
{% endblock %}
{% block scripts %}
{% endblock %}
{% block sidebar %}
{% include 'menu_racing.html' %}
{% endblock %}
+5
View File
@@ -10,6 +10,11 @@
<i class="far fa-flag fa-fw"></i>&nbsp;New Race <i class="far fa-flag fa-fw"></i>&nbsp;New Race
</a> </a>
</li> </li>
<li id="indoor-new">
<a href="/rowers/virtualevent/createindoor">
<i class="far fa-flag fa-fw"></i>&nbsp;New Indoor Race
</a>
</li>
<li id="courses"> <li id="courses">
<a href="/rowers/list-courses"> <a href="/rowers/list-courses">
<i class="fas fa-map-marked fa-fw"></i>&nbsp;Courses <i class="fas fa-map-marked fa-fw"></i>&nbsp;Courses
+4 -2
View File
@@ -8,7 +8,8 @@
<th>Event</th> <th>Event</th>
<th>Country</th> <th>Country</th>
<th>Course</th> <th>Course</th>
<th>Distance</th> <th></th>
<th></th>
<th>Click for Details</th> <th>Click for Details</th>
</tr> </tr>
</thead> </thead>
@@ -26,7 +27,8 @@
<td><a href="/rowers/virtualevent/{{ race.id }}">{{ race.name }}</a></td> <td><a href="/rowers/virtualevent/{{ race.id }}">{{ race.name }}</a></td>
<td>{{ race.course.country }}</td> <td>{{ race.course.country }}</td>
<td><a href="/rowers/courses/{{ race.course.id }}">{{ race.course.name }}</a></td> <td><a href="/rowers/courses/{{ race.course.id }}">{{ race.course.name }}</a></td>
<td>{{ race.sessionvalue }} m</td> <td>{{ race.sessionvalue }}</td>
<td>{{ race.sessionunit }}</td>
<td> <td>
{% if rower %} {% if rower %}
{% if race|can_register:rower %} {% if race|can_register:rower %}
+73 -12
View File
@@ -14,6 +14,7 @@
<h1>{{ race.name }}</h1> <h1>{{ race.name }}</h1>
<ul class="main-content"> <ul class="main-content">
{% if race.sessiontype == 'race' %}
<li class="grid_2"> <li class="grid_2">
<p> <p>
<h2>Course</h2> <h2>Course</h2>
@@ -24,6 +25,7 @@
{{ coursescript|safe }} {{ coursescript|safe }}
</div> </div>
</li> </li>
{% endif %}
<li class="grid_2"> <li class="grid_2">
<div id="raceinfo"> <div id="raceinfo">
<p> <p>
@@ -32,11 +34,23 @@
<p> <p>
<table class="listtable shortpadded" width="100%"> <table class="listtable shortpadded" width="100%">
<tbody> <tbody>
{% if race.sessiontype == 'race' %}
<tr> <tr>
<th>Course</th><td>{{ race.course }}</td> <th>Course</th><td>{{ race.course }}</td>
</tr> </tr>
{% else %}
<tr> <tr>
<th>Distance</th><td>{{ race.sessionvalue }} m</td> <th>Indoor Race</th><td>To be rowed on a Concept2 ergometer</td>
</tr>
<tr>
<th>Time Zone</th><td>{{ race.timezone }}</td>
</tr>
{% endif %}
<tr>
<th>
{{ race.sessionmode }} challenge
</th><td>{{ race.sessionvalue }} {{ race.sessionunit }}
</td>
</tr> </tr>
<tr> <tr>
<th>Registration closure</th> <th>Registration closure</th>
@@ -81,38 +95,42 @@
{% for button in buttons %} {% for button in buttons %}
{% if button == 'registerbutton' %} {% if button == 'registerbutton' %}
<p> <p>
<a href="/rowers/virtualevent/{{ race.id }}/register" {% if race.sessiontype == 'race' %}
class="blue button">Register</a> <a href="/rowers/virtualevent/{{ race.id }}/register">Register</a>
{% else %}
<a href="/rowers/virtualevent/{{ race.id }}/registerindoor">Register</a>
{% endif %}
</p> </p>
{% endif %} {% endif %}
{% if button == 'submitbutton' %} {% if button == 'submitbutton' %}
<a href="/rowers/virtualevent/{{ race.id }}/submit" class="blue button">Submit Result</a> <a href="/rowers/virtualevent/{{ race.id }}/submit">Submit Result</a>
{% endif %} {% endif %}
{% if button == 'resubmitbutton' %} {% if button == 'resubmitbutton' %}
<p> <p>
<a href="/rowers/virtualevent/{{ race.id }}/submit" <a href="/rowers/virtualevent/{{ race.id }}/submit">Submit New Result</a>
class="blue button">Submit New Result</a>
</p> </p>
{% endif %} {% endif %}
{% if button == 'withdrawbutton' %} {% if button == 'withdrawbutton' %}
<p> <p>
<a href="/rowers/virtualevent/{{ race.id }}/withdraw" <a href="/rowers/virtualevent/{{ race.id }}/withdraw">Withdraw</a>
class="blue button">Withdraw</a>
</p> </p>
{% endif %} {% endif %}
{% if button == 'adddisciplinebutton' %} {% if button == 'adddisciplinebutton' %}
<p> <p>
<a href="/rowers/virtualevent/{{ race.id }}/adddiscipline" <a href="/rowers/virtualevent/{{ race.id }}/adddiscipline">
class="blue button">
Register New Boat Register New Boat
</a> </a>
</p> </p>
{% endif %} {% endif %}
{% if button == 'editbutton' %} {% if button == 'editbutton' %}
<p> <p>
<a href="/rowers/virtualevent/{{ race.id }}/edit" {% if race.sessiontype == 'race' %}
class="blue button">Edit Race <a href="/rowers/virtualevent/{{ race.id }}/edit">Edit Race
</a> </a>
{% else %}
<a href="/rowers/virtualevent/{{ race.id }}/editindoor">Edit Race
</a>
{% endif %}
</p> </p>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@@ -122,7 +140,11 @@
<li class="grid_2"> <li class="grid_2">
<div id="results"> <div id="results">
<p> <p>
{% if race|is_final %}
<h2>Final Results</h2>
{% else %}
<h2>Results</h2> <h2>Results</h2>
{% endif %}
</p> </p>
<p> <p>
{% if results or dns %} {% if results or dns %}
@@ -135,11 +157,14 @@
<th>&nbsp;</th> <th>&nbsp;</th>
<th>&nbsp;</th> <th>&nbsp;</th>
<th>&nbsp;</th> <th>&nbsp;</th>
{% if race.sessiontype == 'race' %}
<th>Class</th> <th>Class</th>
<th>Boat</th> <th>Boat</th>
{% endif %}
<th>Time</th> <th>Time</th>
<th>Distance</th> <th>Distance</th>
<th>Details</th> <th>Details</th>
<th>&nbsp;</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -153,13 +178,24 @@
<td>{{ result.age }}</td> <td>{{ result.age }}</td>
<td>{{ result.sex }}</td> <td>{{ result.sex }}</td>
<td>{{ result.weightcategory }}</td> <td>{{ result.weightcategory }}</td>
{% if race.sessiontype == 'race' %}
<td>{{ result.boatclass }}</td> <td>{{ result.boatclass }}</td>
<td>{{ result.boattype }}</td> <td>{{ result.boattype }}</td>
{% endif %}
<td>{{ result.duration |durationprint:"%H:%M:%S.%f" }}</td> <td>{{ result.duration |durationprint:"%H:%M:%S.%f" }}</td>
<td>{{ result.distance }} m</td> <td>{{ result.distance }} m</td>
<td> <td>
<a href="/rowers/workout/{{ result.workoutid }}"> <a href="/rowers/workout/{{ result.workoutid }}">
Details</a></td> Details</a></td>
<td>
{% if race.manager == request.user and not race|is_final %}
<a href="/rowers/virtualevent/{{ race.id }}/disqualify/{{ result.id }}/">
Disqualify
</a>
{% else %}
&nbsp;
{% endif %}
</td>
</tr> </tr>
{% endfor %} {% endfor %}
{% for result in dns %} {% for result in dns %}
@@ -170,8 +206,10 @@
<td>{{ result.age }}</td> <td>{{ result.age }}</td>
<td>{{ result.sex }}</td> <td>{{ result.sex }}</td>
<td>{{ result.weightcategory }}</td> <td>{{ result.weightcategory }}</td>
{% if race.sessiontype == 'race' %}
<td>{{ result.boatclass }}</td> <td>{{ result.boatclass }}</td>
<td>{{ result.boattype }}</td> <td>{{ result.boattype }}</td>
{% endif %}
<td>DNS</td> <td>DNS</td>
</tr> </tr>
{% endfor %} {% endfor %}
@@ -210,8 +248,10 @@
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Team Name</th> <th>Team Name</th>
{% if race.sessiontype == 'race' %}
<th>Class</th> <th>Class</th>
<th>Boat</th> <th>Boat</th>
{% endif %}
<th>Age</th> <th>Age</th>
<th>Gender</th> <th>Gender</th>
<th>Weight Category</th> <th>Weight Category</th>
@@ -221,8 +261,10 @@
<tr> <tr>
<td>{{ record.username }} <td>{{ record.username }}
<td>{{ record.teamname }}</td> <td>{{ record.teamname }}</td>
{% if race.sessiontype == 'race' %}
<td>{{ record.boatclass }}</td> <td>{{ record.boatclass }}</td>
<td>{{ record.boattype }}</td> <td>{{ record.boattype }}</td>
{% endif %}
<td>{{ record.age }}</td> <td>{{ record.age }}</td>
<td>{{ record.sex }}</td> <td>{{ record.sex }}</td>
<td>{{ record.weightcategory }}</td> <td>{{ record.weightcategory }}</td>
@@ -247,6 +289,16 @@
Virtual races are intended as an informal way to add a Virtual races are intended as an informal way to add a
competitive element to training and as a quick way to set competitive element to training and as a quick way to set
up and manage small regattas. up and manage small regattas.
</p>
<p>
On the water races are rowed on the course shown.
You cannot submit results rowed
on other bodies of water.
</p>
<p>
Indoor races are open for all, wherever you live.
However, be aware of the
time zone for the race window.
</p> </p>
<p> <p>
As a rowsandall.com user, you can As a rowsandall.com user, you can
@@ -271,6 +323,10 @@
you delete the respective workout or remove your account. you delete the respective workout or remove your account.
By registering, you agree with this and the race rules. By registering, you agree with this and the race rules.
</p> </p>
<p>
If you use a manually added workout for your indoor race result,
please attach a screenshot of the ergometer display for verification.
</p>
<p> <p>
Virtual Racing on rowsandall.com is honors based. Please be a good Virtual Racing on rowsandall.com is honors based. Please be a good
sport, submit real results rowed by you, and make sure you set the sport, submit real results rowed by you, and make sure you set the
@@ -285,6 +341,11 @@
Individual participants are entirely responsible for their Individual participants are entirely responsible for their
safety while participating in a virtual race. safety while participating in a virtual race.
</p> </p>
<p>
Until the evaluation closure time, the race organizer can
review and reject entries. If you are disqualified in this
way, you will receive an email with the reason.
</p>
</div> </div>
</li> </li>
</ul> </ul>
+12 -10
View File
@@ -32,20 +32,22 @@
</p> </p>
<p> <p>
{% csrf_token %} {% csrf_token %}
<input class="button green" type="submit" value="Save"> <input type="submit" value="Save">
</p> </p>
</form> </form>
</li> </li>
<li class="grid_1"> <li class="grid_1">
<p> <p>All times are local times in the race course time zone</p>
<ul> <p>Adding a contact phone number and email is not mandatory, but we
<li>All times are local times in the race course time zone</li> strongly recommend it.</p>
<li>Adding a contact phone number and email is not mandatory, but we <p>If your event has a registration closure deadline, participants
strongly recommend it.</li> have to enter (and can withdraw) before the registration closure time.</p>
<li>If your event has a registration closure deadline, participants <p>Participants can submit results until the evaluation closure time.</p>
have to enter (and can withdraw) before the registration closure time.</li> <p>Until one hour after evaluation closure time, the race organizer
<li>Participants can submit results until the evaluation closure time.</li> can review and reject submitted results ("disqualification"). If
</ul> you as the race organizer intend to use this functionality, it
is strongly recommended that you fill out a contact email or phone
number.
</p> </p>
</li> </li>
</ul> </ul>
+8
View File
@@ -370,6 +370,14 @@ def is_past_due(self):
def is_not_past_due(self): def is_not_past_due(self):
return datetime.date.today() <= self.date return datetime.date.today() <= self.date
@register.filter
def is_closed(race):
return race.evaluation_closure < timezone.now()
@register.filter
def is_final(race):
return race.evaluation_closure < timezone.now()-datetime.timedelta(hours=1)
@register.filter @register.filter
def userurl(path,member): def userurl(path,member):
pattern = re.compile('user\/\d+') pattern = re.compile('user\/\d+')
+5
View File
@@ -143,14 +143,19 @@ urlpatterns = [
url(r'^list-workouts/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)$',views.workouts_view), url(r'^list-workouts/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)$',views.workouts_view),
url(r'^virtualevents$',views.virtualevents_view), url(r'^virtualevents$',views.virtualevents_view),
url(r'^virtualevent/create$',views.virtualevent_create_view), url(r'^virtualevent/create$',views.virtualevent_create_view),
url(r'^virtualevent/createindoor$',views.indoorvirtualevent_create_view),
url(r'^virtualevent/(?P<id>\d+)$',views.virtualevent_view), url(r'^virtualevent/(?P<id>\d+)$',views.virtualevent_view),
url(r'^virtualevent/(?P<id>\d+)/edit$',views.virtualevent_edit_view), url(r'^virtualevent/(?P<id>\d+)/edit$',views.virtualevent_edit_view),
url(r'^virtualevent/(?P<id>\d+)/editindoor$',views.indoorvirtualevent_edit_view),
url(r'^virtualevent/(?P<id>\d+)/register$',views.virtualevent_register_view), url(r'^virtualevent/(?P<id>\d+)/register$',views.virtualevent_register_view),
url(r'^virtualevent/(?P<id>\d+)/registerindoor$',views.indoorvirtualevent_register_view),
url(r'^virtualevent/(?P<id>\d+)/adddiscipline$',views.virtualevent_addboat_view), url(r'^virtualevent/(?P<id>\d+)/adddiscipline$',views.virtualevent_addboat_view),
url(r'^virtualevent/(?P<id>\d+)/withdraw/(?P<recordid>\d+)$',views.virtualevent_withdraw_view), url(r'^virtualevent/(?P<id>\d+)/withdraw/(?P<recordid>\d+)$',views.virtualevent_withdraw_view),
url(r'^virtualevent/(?P<id>\d+)/withdraw$',views.virtualevent_withdraw_view), url(r'^virtualevent/(?P<id>\d+)/withdraw$',views.virtualevent_withdraw_view),
url(r'^virtualevent/(?P<id>\d+)/submit$', url(r'^virtualevent/(?P<id>\d+)/submit$',
views.virtualevent_submit_result_view), views.virtualevent_submit_result_view),
url(r'^virtualevent/(?P<raceid>\d+)/disqualify/(?P<recordid>\d+)/',
views.virtualevent_disqualify_view),
url(r'^list-workouts/$',views.workouts_view), url(r'^list-workouts/$',views.workouts_view),
url(r'^list-courses/$',views.courses_view), url(r'^list-courses/$',views.courses_view),
url(r'^courses/upload$',views.course_upload_view), url(r'^courses/upload$',views.course_upload_view),
+611 -14
View File
@@ -49,7 +49,8 @@ from rowers.forms import (
VirtualRaceSelectForm,WorkoutRaceSelectForm,CourseSelectForm, VirtualRaceSelectForm,WorkoutRaceSelectForm,CourseSelectForm,
RaceResultFilterForm,PowerIntervalUpdateForm,FlexAxesForm, RaceResultFilterForm,PowerIntervalUpdateForm,FlexAxesForm,
FlexOptionsForm,DataFrameColumnsForm,OteWorkoutTypeForm, FlexOptionsForm,DataFrameColumnsForm,OteWorkoutTypeForm,
MetricsForm, MetricsForm,DisqualificationForm,disqualificationreasons,
disqualifiers
) )
from django.core.urlresolvers import reverse, reverse_lazy from django.core.urlresolvers import reverse, reverse_lazy
@@ -90,7 +91,9 @@ from rowers.models import (
WorkoutComment,WorkoutCommentForm,RowerExportForm, WorkoutComment,WorkoutCommentForm,RowerExportForm,
CalcAgePerformance,PowerTimeFitnessMetric,PlannedSessionForm, CalcAgePerformance,PowerTimeFitnessMetric,PlannedSessionForm,
PlannedSessionFormSmall,GeoCourseEditForm,VirtualRace, PlannedSessionFormSmall,GeoCourseEditForm,VirtualRace,
VirtualRaceForm,VirtualRaceResultForm,RowerImportExportForm VirtualRaceForm,VirtualRaceResultForm,RowerImportExportForm,
IndoorVirtualRaceResultForm,IndoorVirtualRaceResult,
IndoorVirtualRaceForm,
) )
from rowers.models import ( from rowers.models import (
FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet, FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet,
@@ -154,6 +157,7 @@ from rowers.tasks import handle_makeplot,handle_otwsetpower,handle_sendemailtcx,
from rowers.tasks import ( from rowers.tasks import (
handle_sendemail_unrecognized,handle_sendemailnewcomment, handle_sendemail_unrecognized,handle_sendemailnewcomment,
handle_sendemailsummary, handle_sendemailsummary,
handle_send_disqualification_email,
handle_sendemailfile, handle_sendemailfile,
handle_sendemailkml, handle_sendemailkml,
handle_sendemailnewresponse, handle_updatedps, handle_sendemailnewresponse, handle_updatedps,
@@ -15765,7 +15769,8 @@ def virtualevents_view(request):
if country == 'All': if country == 'All':
countries = VirtualRace.objects.order_by('country').values_list('country').distinct() countries = VirtualRace.objects.order_by('country').values_list('country').distinct()
else: else:
countries = [country] countries = [country,
'Indoor']
if regattatype == 'upcoming': if regattatype == 'upcoming':
races1 = VirtualRace.objects.filter( races1 = VirtualRace.objects.filter(
@@ -15813,14 +15818,146 @@ def virtualevents_view(request):
'rower':r, 'rower':r,
}) })
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
]
return render(request,'virtualevents.html', return render(request,'virtualevents.html',
{ 'races':races, { 'races':races,
'form':form, 'form':form,
'breadcrumbs':breadcrumbs,
'active':'nav-racing', 'active':'nav-racing',
'rower':r, 'rower':r,
} }
) )
@login_required()
def virtualevent_disqualify_view(request,raceid=0,recordid=0):
r = getrower(request.user)
try:
race = VirtualRace.objects.get(id=raceid)
except VirtualRace.DoesNotExist:
raise Http404("Virtual Race does not exist")
if r.user != race.manager:
raise PermissionDenied("Access denied")
if race.sessiontype == 'race':
recordobj = VirtualRaceResult
else:
recordobj = IndoorVirtualRaceResult
# datum moet voor race evaluation date zijn (ook in template controleren)
try:
record = recordobj.objects.get(id=recordid)
except recordobj.DoesNotExist:
messages.error(request,"We couldn't find the record")
if timezone.now() > race.evaluation_closure+datetime.timedelta(hours=1):
messages.error(request,"The evaluation is already closed and the results are official")
url = reverse(virtualevent_view,kwargs={'id':raceid})
return HttpResponseRedirect(url)
if request.method == 'POST':
form = DisqualificationForm(request.POST)
if form.is_valid():
message = form.cleaned_data['message']
reason = form.cleaned_data['reason']
disqualifier = disqualifiers[reason]
u = User.objects.get(id=record.userid)
name = record.username
job = myqueue(queue,handle_send_disqualification_email,
u.email, name,
disqualifier,message,race.name)
messages.info(request,"We have invalidated the result for: "+str(record))
record.coursecompleted = False
record.save()
url = reverse(virtualevent_view,kwargs={'id':raceid})
return HttpResponseRedirect(url)
else:
form = DisqualificationForm(request.POST)
workout = Workout.objects.get(id=record.workoutid)
g = GraphImage.objects.filter(workout=workout).order_by("-creationdatetime")
for i in g:
try:
width,height = Image.open(i.filename).size
i.width = width
i.height = height
i.save()
except:
pass
script, div = interactive_chart(record.workoutid)
f1 = workout.csvfilename
rowdata = rdata(f1)
hascoordinates = 1
if rowdata != 0:
try:
latitude = rowdata.df[' latitude']
if not latitude.std():
hascoordinates = 0
except KeyError, AttributeError:
hascoordinates = 0
else:
hascoordinates = 0
if hascoordinates:
mapscript, mapdiv = leaflet_chart(rowdata.df[' latitude'],
rowdata.df[' longitude'],
workout.name)
else:
mapscript = ""
mapdiv = ""
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}),
'name': race.name
},
{
'url':reverse(virtualevent_disqualify_view,
kwargs={'raceid':raceid,
'recordid':recordid}),
'name': 'Disqualify Entry'
},
]
return render(request,"disqualification_view.html",
{'workout':workout,
'active':'nav-racing',
'graphs':g,
'interactiveplot':script,
'the_div':div,
'mapscript':mapscript,
'mapdiv':mapdiv,
'form':form,
'race':race,
'record':record,
})
def virtualevent_view(request,id=0): def virtualevent_view(request,id=0):
results = [] results = []
@@ -15836,12 +15973,16 @@ def virtualevent_view(request,id=0):
except VirtualRace.DoesNotExist: except VirtualRace.DoesNotExist:
raise Http404("Virtual Race does not exist") raise Http404("Virtual Race does not exist")
if race.sessiontype == 'race':
script,div = course_map(race.course) script,div = course_map(race.course)
resultobj = VirtualRaceResult
else:
script = ''
div = ''
resultobj = IndoorVirtualRaceResult
records = resultobj.objects.filter(race=race)
records = VirtualRaceResult.objects.filter(
race=race
)
buttons = [] buttons = []
@@ -15881,7 +16022,10 @@ def virtualevent_view(request,id=0):
try: try:
boatclass = cd['boatclass'] boatclass = cd['boatclass']
except KeyError: except KeyError:
if race.sessiontype == 'race':
boatclass = [t for t in mytypes.otwtypes] boatclass = [t for t in mytypes.otwtypes]
else:
boatclass = [t for t in mytypes.otetypes]
age_min = cd['age_min'] age_min = cd['age_min']
age_max = cd['age_max'] age_max = cd['age_max']
@@ -15891,7 +16035,8 @@ def virtualevent_view(request,id=0):
except KeyError: except KeyError:
weightcategory = ['hwt','lwt'] weightcategory = ['hwt','lwt']
results = VirtualRaceResult.objects.filter( if race.sessiontype == 'race':
results = resultobj.objects.filter(
race=race, race=race,
workoutid__isnull=False, workoutid__isnull=False,
boatclass__in=boatclass, boatclass__in=boatclass,
@@ -15901,25 +16046,36 @@ def virtualevent_view(request,id=0):
age__gte=age_min, age__gte=age_min,
age__lte=age_max age__lte=age_max
).order_by("duration") ).order_by("duration")
else:
results = resultobj.objects.filter(
race=race,
workoutid__isnull=False,
boatclass__in=boatclass,
sex__in=sex,
weightcategory__in=weightcategory,
age__gte=age_min,
age__lte=age_max
).order_by("duration","-distance")
# to-do - add DNS # to-do - add DNS
dns = [] dns = []
if timezone.now() > race.evaluation_closure: if timezone.now() > race.evaluation_closure:
dns = VirtualRaceResult.objects.filter( dns = resultobj.objects.filter(
race=race, race=race,
workoutid__isnull=True, workoutid__isnull=True,
boatclass__in=boatclass, boatclass__in=boatclass,
boattype__in=boattype,
sex__in=sex, sex__in=sex,
weightcategory__in=weightcategory, weightcategory__in=weightcategory,
age__gte=age_min, age__gte=age_min,
age__lte=age_max age__lte=age_max
) )
else: else:
results = VirtualRaceResult.objects.filter( results = resultobj.objects.filter(
race=race, race=race,
workoutid__isnull=False, workoutid__isnull=False,
).order_by("duration") coursecompleted=True,
).order_by("duration","-distance")
if results: if results:
form = RaceResultFilterForm(records=records) form = RaceResultFilterForm(records=records)
@@ -15929,18 +16085,32 @@ def virtualevent_view(request,id=0):
# to-do - add DNS # to-do - add DNS
dns = [] dns = []
if timezone.now() > race.evaluation_closure: if timezone.now() > race.evaluation_closure:
dns = VirtualRaceResult.objects.filter( dns = resultobj.objects.filter(
race=race, race=race,
workoutid__isnull=True, workoutid__isnull=True,
) )
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}
),
'name': race.name
}
]
return render(request,'virtualevent.html', return render(request,'virtualevent.html',
{ {
'coursescript':script, 'coursescript':script,
'coursediv':div, 'coursediv':div,
'breadcrumbs':breadcrumbs,
'race':race, 'race':race,
'rower':r, 'rower':r,
'results':results, 'results':results,
@@ -16089,9 +16259,31 @@ def virtualevent_addboat_view(request,id=0):
form = VirtualRaceResultForm(initial=initial) form = VirtualRaceResultForm(initial=initial)
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}
),
'name': race.name
},
{
'url': reverse(virtualevent_addboat_view,
kwargs = {'id':race.id}
),
'name': 'Add Discipline'
}
]
return render(request,'virtualeventregister.html', return render(request,'virtualeventregister.html',
{ {
'form':form, 'form':form,
'breadcrumbs':breadcrumbs,
'race':race, 'race':race,
'userid':r.user.id, 'userid':r.user.id,
'active': 'nav-racing', 'active': 'nav-racing',
@@ -16182,14 +16374,281 @@ def virtualevent_register_view(request,id=0):
form = VirtualRaceResultForm(initial=initial) form = VirtualRaceResultForm(initial=initial)
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}
),
'name': race.name
},
{
'url': reverse(virtualevent_register_view,
kwargs = {'id':race.id}
),
'name': 'Register'
}
]
return render(request,'virtualeventregister.html',
{
'form':form,
'breadcrumbs':breadcrumbs,
'race':race,
'userid':r.user.id,
})
@login_required()
def indoorvirtualevent_register_view(request,id=0):
r = getrower(request.user)
try:
race = VirtualRace.objects.get(id=id)
except VirtualRace.DoesNotExist:
raise Http404("Virtual Race does not exist")
if not race_can_register(r,race):
messages.error(request,"You cannot register for this race")
url = reverse(virtualevent_view,
kwargs = {
'id':race.id
})
return HttpResponseRedirect(url)
# we're still here
if request.method == 'POST':
# process form
form = IndoorVirtualRaceResultForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
teamname = cd['teamname']
weightcategory = cd['weightcategory']
age = cd['age']
boatclass = cd['boatclass']
sex = r.sex
if r.birthdate:
age = calculate_age(r.birthdate)
sex = r.sex
if sex == 'not specified':
sex = 'male'
record = IndoorVirtualRaceResult(
userid=r.id,
teamname=teamname,
race=race,
username = u'{f} {l}'.format(
f = r.user.first_name,
l = r.user.last_name
),
weightcategory=weightcategory,
duration=datetime.time(0,0),
boatclass=boatclass,
coursecompleted=False,
sex=sex,
age=age
)
record.save()
add_rower_race(r,race)
messages.info(
request,
"You have successfully registered for this race. Good luck!"
)
url = reverse(virtualevent_view,
kwargs = {
'id':race.id
})
return HttpResponseRedirect(url)
else:
initial = {
'age': calculate_age(r.birthdate),
'weightcategory': r.weightcategory,
}
form = IndoorVirtualRaceResultForm(initial=initial)
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}
),
'name': race.name
},
{
'url': reverse(indoorvirtualevent_register_view,
kwargs = {'id':race.id}
),
'name': 'Register'
}
]
return render(request,'virtualeventregister.html', return render(request,'virtualeventregister.html',
{ {
'form':form, 'form':form,
'race':race, 'race':race,
'breadcrumbs':breadcrumbs,
'userid':r.user.id, 'userid':r.user.id,
}) })
@login_required()
def indoorvirtualevent_create_view(request):
r = getrower(request.user)
if request.method == 'POST':
racecreateform = IndoorVirtualRaceForm(request.POST)
if racecreateform.is_valid():
cd = racecreateform.cleaned_data
startdate = cd['startdate']
start_time = cd['start_time']
enddate = cd['enddate']
end_time = cd['end_time']
comment = cd['comment']
sessionunit = cd['sessionunit']
sessionvalue = cd['sessionvalue']
name = cd['name']
registration_form = cd['registration_form']
registration_closure = cd['registration_closure']
evaluation_closure = cd['evaluation_closure']
contact_phone = cd['contact_phone']
contact_email = cd['contact_email']
# correct times
timezone_str = cd['timezone']
startdatetime = datetime.datetime.combine(startdate,start_time)
enddatetime = datetime.datetime.combine(enddate,end_time)
startdatetime = pytz.timezone(timezone_str).localize(
startdatetime
)
enddatetime = pytz.timezone(timezone_str).localize(
enddatetime
)
evaluation_closure = pytz.timezone(timezone_str).localize(
evaluation_closure.replace(tzinfo=None)
)
if registration_form == 'manual':
try:
registration_closure = pytz.timezone(
timezone_str
).localize(
registration_closure.replace(tzinfo=None)
)
except AttributeError:
registration_closure = startdatetime
elif registration_form == 'windowstart':
registration_closure = startdatetime
elif registration_form == 'windowend':
registration_closure = enddatetime
else:
registration_closure = evaluation_closure
if sessionunit == 'min':
sessionmode = 'time'
else:
sessionmode = 'distance'
vs = VirtualRace(
name=name,
startdate=startdate,
preferreddate = startdate,
start_time = start_time,
enddate=enddate,
end_time=end_time,
comment=comment,
sessiontype = 'indoorrace',
sessionunit = sessionunit,
sessionmode = sessionmode,
sessionvalue = sessionvalue,
course=None,
timezone=timezone_str,
evaluation_closure=evaluation_closure,
registration_closure=registration_closure,
contact_phone=contact_phone,
contact_email=contact_email,
country = 'Indoor',
manager=request.user,
)
vs.save()
# create Site Announcement & Tweet
if settings.DEBUG:
dotweet = False
elif 'dev' in settings.SITE_URL:
dotweet = False
else:
dotweet = True
try:
sa = SiteAnnouncement(
announcement = "New Virtual Indoor Race on rowsandall.com: {name}".format(
name = name.encode('utf8'),
),
dotweet = dotweet
)
sa.save()
except UnicodeEncodeError:
sa = SiteAnnouncement(
announcement = "New Virtual Indoor Race on rowsandall.com: {name}".format(
name = name,
),
dotweet = dotweet
)
sa.save()
url = reverse(virtualevents_view)
return HttpResponseRedirect(url)
else:
racecreateform = IndoorVirtualRaceForm()
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(indoorvirtualevent_create_view,
),
'name': 'New Indoor Virtual Regatta'
},
]
return render(request,'indoorvirtualeventcreate.html',
{
'form':racecreateform,
'breadcrumbs':breadcrumbs,
'rower':r,
'active':'nav-racing',
})
@login_required() @login_required()
def virtualevent_create_view(request): def virtualevent_create_view(request):
r = getrower(request.user) r = getrower(request.user)
@@ -16304,9 +16763,21 @@ def virtualevent_create_view(request):
racecreateform = VirtualRaceForm() racecreateform = VirtualRaceForm()
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_create_view,
),
'name': 'New Virtual Regatta'
},
]
return render(request,'virtualeventcreate.html', return render(request,'virtualeventcreate.html',
{ {
'form':racecreateform, 'form':racecreateform,
'breadcrumbs':breadcrumbs,
'rower':r, 'rower':r,
'active':'nav-racing', 'active':'nav-racing',
@@ -16360,15 +16831,111 @@ def virtualevent_edit_view(request,id=0):
racecreateform = VirtualRaceForm(instance=race) racecreateform = VirtualRaceForm(instance=race)
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}
),
'name': race.name
},
{
'url': reverse(virtualevent_edit_view,
kwargs = {'id':race.id}
),
'name': 'Edit'
}
]
return render(request,'virtualeventedit.html', return render(request,'virtualeventedit.html',
{ {
'form':racecreateform, 'form':racecreateform,
'breadcrumbs':breadcrumbs,
'rower':r, 'rower':r,
'race':race, 'race':race,
}) })
@login_required()
def indoorvirtualevent_edit_view(request,id=0):
r = getrower(request.user)
try:
race = VirtualRace.objects.get(id=id)
if race.manager != request.user:
raise PermissionDenied("Access denied")
except VirtualRace.DoesNotExist:
raise Http404("Virtual Race does not exist")
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
if timezone.now() > startdatetime:
messages.error(request,"You cannot edit a race after the start of the race window")
url = reverse(virtualevent_view,
kwargs={
'id':race.id,
})
if request.method == 'POST':
racecreateform = IndoorVirtualRaceForm(request.POST,instance=race)
if racecreateform.is_valid():
cd = racecreateform.cleaned_data
res, message = update_indoorvirtualrace(race,cd)
if res:
messages.info(request,message)
else:
messages.error(request,message)
url = reverse(virtualevent_view,
kwargs = {
'id':race.id
})
return HttpResponseRedirect(url)
else:
racecreateform = IndoorVirtualRaceForm(instance=race)
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}
),
'name': race.name
},
{
'url': reverse(indoorvirtualevent_edit_view,
kwargs = {'id':race.id}
),
'name': 'Edit'
}
]
return render(request,'virtualeventedit.html',
{
'form':racecreateform,
'breadcrumbs':breadcrumbs,
'rower':r,
'race':race,
})
@login_required() @login_required()
def virtualevent_submit_result_view(request,id=0): def virtualevent_submit_result_view(request,id=0):
@@ -16391,7 +16958,12 @@ def virtualevent_submit_result_view(request,id=0):
can_submit = race_can_submit(r,race) or race_can_resubmit(r,race) can_submit = race_can_submit(r,race) or race_can_resubmit(r,race)
records = VirtualRaceResult.objects.filter( if race.sessiontype == 'race':
resultobj = VirtualRaceResult
else:
resultobj = IndoorVirtualRaceResult
records = resultobj.objects.filter(
userid = r.id, userid = r.id,
race=race race=race
) )
@@ -16462,10 +17034,16 @@ def virtualevent_submit_result_view(request,id=0):
workouts = Workout.objects.filter(id=selectedworkout) workouts = Workout.objects.filter(id=selectedworkout)
if race.sessiontype == 'race':
result,comments,errors,jobid = add_workout_race( result,comments,errors,jobid = add_workout_race(
workouts,race,r, workouts,race,r,
splitsecond=splitsecond,recordid=recordid) splitsecond=splitsecond,recordid=recordid)
# if result: else:
result,comments,errors,jobid = add_workout_indoorrace(
workouts,race,r,recordid=recordid)
# if result:
# for w in ws: # for w in ws:
# remove_workout_plannedsession(w,race) # remove_workout_plannedsession(w,race)
# delete_race_result(w,race) # delete_race_result(w,race)
@@ -16494,10 +17072,29 @@ def virtualevent_submit_result_view(request,id=0):
else: else:
w_form = WorkoutRaceSelectForm(workoutdata,entries) w_form = WorkoutRaceSelectForm(workoutdata,entries)
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}
),
'name': race.name
},
{
'url': reverse(virtualevent_submit_result_view,
kwargs = {'id':race.id}
),
'name': 'Submit Result'
}
]
return render(request,'race_submit.html', return render(request,'race_submit.html',
{ {
'race':race, 'race':race,
'workouts':ws, 'workouts':ws,
'breadcrumbs':breadcrumbs,
'active':'nav-racing', 'active':'nav-racing',
'rower':r, 'rower':r,
'w_form':w_form, 'w_form':w_form,