From 8162b2fc45841022ea2ef8108c77eb3dc4dc8eba Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 27 Nov 2018 17:49:02 +0100
Subject: [PATCH 1/7] initial incomplete version
---
rowers/models.py | 180 ++++++++-
rowers/mytypes.py | 6 +
rowers/plannedsessions.py | 167 +++++++-
.../templates/indoorvirtualeventcreate.html | 61 +++
rowers/templates/menu_racing.html | 5 +
rowers/templates/racelist.html | 6 +-
rowers/templates/virtualevent.html | 64 ++-
rowers/templates/virtualeventcreate.html | 2 +-
rowers/urls.py | 3 +
rowers/views.py | 373 ++++++++++++++++--
10 files changed, 815 insertions(+), 52 deletions(-)
create mode 100644 rowers/templates/indoorvirtualeventcreate.html
diff --git a/rowers/models.py b/rowers/models.py
index 3a711c35..34d4aa1d 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -1664,6 +1664,7 @@ class PlannedSession(models.Model):
('cycletarget','Total for a time period'),
('coursetest','OTW test over a course'),
('race','Virtual Race'),
+ ('indoorrace','Indoor Virtual Race'),
)
sessionmodechoices = (
@@ -1772,7 +1773,7 @@ class PlannedSession(models.Model):
else:
self.sessionunit = 'None'
- if self.sessiontype == 'test':
+ if self.sessiontype == 'test' or self.sessiontype == 'indoorrace':
if self.sessionmode not in ['distance','time']:
if self.sessionvalue < 100:
self.sessionmode = 'time'
@@ -1937,7 +1938,126 @@ def get_course_timezone(course):
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):
course = forms.ModelChoiceField(queryset = GeoCourse.objects, empty_label=None)
registration_closure = forms.SplitDateTimeField(widget=AdminSplitDateTime(),required=False)
@@ -2309,6 +2429,54 @@ class VirtualRaceResult(models.Model):
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}" in {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}" in {c} ({s})'.format(
+ n = name,
+ r = self.race,
+ c = self.boatclass,
+ s = self.sex,
+ )
+
class CourseTestResult(models.Model):
userid = models.IntegerField(default=0)
@@ -2318,6 +2486,16 @@ class CourseTestResult(models.Model):
distance = models.IntegerField(default=0)
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 Meta:
model = VirtualRaceResult
diff --git a/rowers/mytypes.py b/rowers/mytypes.py
index e0b9fa7a..ad704756 100644
--- a/rowers/mytypes.py
+++ b/rowers/mytypes.py
@@ -225,6 +225,12 @@ otwtypes = (
'churchboat'
)
+otetypes = (
+ 'rower',
+ 'dynamic',
+ 'slides'
+ )
+
rowtypes = (
'water',
'rower',
diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py
index 3b274605..8edd5390 100644
--- a/rowers/plannedsessions.py
+++ b/rowers/plannedsessions.py
@@ -20,7 +20,7 @@ from rowers.models import (
Rower, Workout,Team,
GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle,
TrainingPlan,PlannedSession,VirtualRaceResult,CourseTestResult,
- get_course_timezone
+ get_course_timezone, IndoorVirtualRaceResult
)
from rowers.courses import get_time_course
@@ -574,6 +574,56 @@ def update_plannedsession(ps,cd):
return 1,'Planned Session Updated'
+def update_indoorvirtualrace(ps,cd):
+ for attr, value in cd.items():
+ if attr == 'comment':
+ value.replace("\r\n", "
");
+ value.replace("\n", "
");
+ setattr(ps, attr, value)
+
+ timezone_str = cd['timezone']
+
+ # correct times
+
+ startdatetime = datetime.combine(cd['startdate'],cd['start_time'])
+ enddatetime = datetime.combine(cd['enddate'],cd['end_time'])
+
+ startdatetime = pytz.timezone(timezone_str).localize(
+ startdatetime
+ )
+ enddatetime = pytz.timezone(timezone_str).localize(
+ enddatetime
+ )
+ ps.evaluation_closure = pytz.timezone(timezone_str).localize(
+ ps.evaluation_closure.replace(tzinfo=None)
+ )
+
+ registration_form = cd['registration_form']
+ registration_closure = cd['registration_closure']
+ if registration_form == 'manual':
+ 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):
for attr, value in cd.items():
if attr == 'comment':
@@ -708,6 +758,10 @@ def race_can_resubmit(r,race):
return False
def race_can_adddiscipline(r,race):
+
+ if race.sessiontype != 'race':
+ return False
+
records = VirtualRaceResult.objects.filter(
userid=r.id,
race=race)
@@ -813,6 +867,116 @@ def remove_rower_race(r,race,recordid=None):
return 1
# 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):
result = 0
comments = []
@@ -895,7 +1059,6 @@ def add_workout_race(ws,race,r,splitsecond=0,recordid=0):
if result>0:
for otherrecord in records:
- print otherrecord
otherrecord.workoutid = None
otherrecord.coursecompleted = False
otherrecord.save()
diff --git a/rowers/templates/indoorvirtualeventcreate.html b/rowers/templates/indoorvirtualeventcreate.html
new file mode 100644
index 00000000..912d21fa
--- /dev/null
+++ b/rowers/templates/indoorvirtualeventcreate.html
@@ -0,0 +1,61 @@
+{% extends "newbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}New Virtual Race{% endblock %}
+
+{% block main %}
+
+
New Indoor Virtual Race
+
+
+
+
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.
+
+
+
+
+
+
+ {% csrf_token %}
+
+
+
+
+
+
+
+
All times are local times in the time zone you select
+
Adding a contact phone number and email is not mandatory, but we
+ strongly recommend it.
+
If your event has a registration closure deadline, participants
+ have to enter (and can withdraw) before the registration closure time.
+
Participants can submit results until the evaluation closure time.
@@ -247,6 +275,14 @@
Virtual races are intended as an informal way to add a
competitive element to training and as a quick way to set
up and manage small regattas.
+
+
+ On the water races are rowed on the course shown. You cannot submit results rowed
+ on other bodies of water.
+
+
+ Indoor races are open for all, wherever you live. However, be aware of the
+ time zone for the race window.
As a rowsandall.com user, you can
@@ -271,6 +307,10 @@
you delete the respective workout or remove your account.
By registering, you agree with this and the race rules.
+
+ If you use a manually added workout for your indoor race result,
+ please attach a screenshot of the ergometer display for verification.
+
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
diff --git a/rowers/templates/virtualeventcreate.html b/rowers/templates/virtualeventcreate.html
index 6898709f..d2489440 100644
--- a/rowers/templates/virtualeventcreate.html
+++ b/rowers/templates/virtualeventcreate.html
@@ -32,7 +32,7 @@
{% endfor %}
{% for result in dns %}
diff --git a/rowers/urls.py b/rowers/urls.py
index eeea79e5..8fcd905a 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -154,6 +154,8 @@ urlpatterns = [
url(r'^virtualevent/(?P\d+)/withdraw$',views.virtualevent_withdraw_view),
url(r'^virtualevent/(?P\d+)/submit$',
views.virtualevent_submit_result_view),
+ url(r'^virtualevent/(?P\d+)/disqualify/(?P\d+)/',
+ views.virtualevent_disqualify_view),
url(r'^list-workouts/$',views.workouts_view),
url(r'^list-courses/$',views.courses_view),
url(r'^courses/upload$',views.course_upload_view),
diff --git a/rowers/views.py b/rowers/views.py
index 3ddb7653..2b048024 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -15832,6 +15832,46 @@ def virtualevents_view(request):
}
)
+@login_required()
+def virtualevent_disqualify_view(request,raceid=0,recordid=0):
+
+ r = getrower(request.user)
+
+ # datum moet voor race evaluation date zijn (ook in template controleren)
+
+
+ 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
+
+ if timezone.now() > race.evaluation_closure:
+ try:
+ record = recordobj.objects.get(id=recordid)
+
+
+ messages.info(request,"We have invalidated the result for: "+str(record))
+
+ record.coursecompleted = False
+ record.save()
+ print record.coursecompleted
+ except recordobj.DoesNotExist:
+ messages.error(request,"We couldn't find the record")
+ else:
+ messages.error(request,"The evaluation is already closed and the results are official")
+
+ url = reverse(virtualevent_view,kwargs={'id':raceid})
+
+ return HttpResponseRedirect(url)
+
def virtualevent_view(request,id=0):
results = []
@@ -15948,6 +15988,7 @@ def virtualevent_view(request,id=0):
results = resultobj.objects.filter(
race=race,
workoutid__isnull=False,
+ coursecompleted=True,
).order_by("duration","-distance")
if results:
From 774028497b6111d976dbdcc5a2db645cc1cea4da Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Wed, 28 Nov 2018 15:37:41 +0100
Subject: [PATCH 6/7] disqualification now with review and form
---
rowers/emails.py | 2 +
rowers/forms.py | 18 +++
rowers/tasks.py | 45 ++++++
rowers/templates/disqualification_view.html | 138 ++++++++++++++++++
rowers/templates/disqualificationemail.html | 37 +++++
.../templates/indoorvirtualeventcreate.html | 18 ++-
rowers/templates/virtualevent.html | 19 ++-
rowers/templates/virtualeventcreate.html | 20 +--
rowers/templatetags/rowerfilters.py | 8 +
rowers/views.py | 112 ++++++++++++--
10 files changed, 385 insertions(+), 32 deletions(-)
create mode 100644 rowers/templates/disqualification_view.html
create mode 100644 rowers/templates/disqualificationemail.html
diff --git a/rowers/emails.py b/rowers/emails.py
index 7dfa16d1..826d43bb 100644
--- a/rowers/emails.py
+++ b/rowers/emails.py
@@ -45,6 +45,8 @@ env = Environment(loader = FileSystemLoader(["rowers/templates"]))
from django.contrib.staticfiles import finders
+
+
def textify(html):
# Remove html tags and continuous whitespaces
text_only = re.sub('[ \t]+', ' ', strip_tags(html))
diff --git a/rowers/forms.py b/rowers/forms.py
index 65122682..d6dc0cdd 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -30,6 +30,24 @@ class EmailForm(forms.Form):
botcheck = forms.CharField(max_length=5)
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):
avghr = forms.IntegerField(required=False,label='Average Heart Rate')
diff --git a/rowers/tasks.py b/rowers/tasks.py
index f18c1ba8..f53d6dda 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -41,6 +41,22 @@ from django.utils.html import strip_tags
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 (
update_strokedata, new_workout_from_file,
getsmallrowdata_db, updatecpdata_sql,
@@ -721,6 +737,35 @@ def handle_updatedps(useremail, workoutids, debug=False,**kwargs):
# 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 '
+
+ 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
def handle_sendemail_expired(useremail,userfirstname,userlastname,expireddate,
**kwargs):
diff --git a/rowers/templates/disqualification_view.html b/rowers/templates/disqualification_view.html
new file mode 100644
index 00000000..fe7b61a5
--- /dev/null
+++ b/rowers/templates/disqualification_view.html
@@ -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 %}
+
+
+
+
+{% endfor %}
+{% else %}
+
+
+{% endif %}
+{% endblock %}
+{% block image_src %}
+{% for graph in graphs %}
+
+{% endfor %}
+{% endblock %}
+
+{% endfor %}
+{% block main %}
+
+
Do you want to disqualify this result?
+
+
+
+ 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.
+
+ 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".
+
+ Unfortunately, the result that you have submitted
+ for the virtual race {{ racename }}
+ has been rejected by the race organizer.
+
+
+
+ The reason for the rejection was: {{ reason }}.
+
+
+
+ The race organizer added the following explanation:
+
+
+
+ {{ message }}
+
+
+
+ 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.
+
+
+
+ You are still registered for the race and can submit a result.
+
All times are local times in the time zone you select
-
Adding a contact phone number and email is not mandatory, but we
- strongly recommend it.
-
If your event has a registration closure deadline, participants
- have to enter (and can withdraw) before the registration closure time.
-
Participants can submit results until the evaluation closure time.
+
All times are local times in the time zone you select
+
Adding a contact phone number and email is not mandatory, but we
+ strongly recommend it.
+
If your event has a registration closure deadline, participants
+ have to enter (and can withdraw) before the registration closure time.
+
Participants can submit results until the evaluation closure time.
+
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.
+
{% if results or dns %}
@@ -184,7 +188,7 @@
Details
- {% if race.manager == request.user %}
+ {% if race.manager == request.user and not race|is_final %}
Disqualify
@@ -287,11 +291,13 @@
up and manage small regattas.
- On the water races are rowed on the course shown. You cannot submit results rowed
+ On the water races are rowed on the course shown.
+ You cannot submit results rowed
on other bodies of water.
- Indoor races are open for all, wherever you live. However, be aware of the
+ Indoor races are open for all, wherever you live.
+ However, be aware of the
time zone for the race window.
@@ -334,7 +340,12 @@
refereed or staffed to provide for participants safety.
Individual participants are entirely responsible for their
safety while participating in a virtual race.
-
+
+
+ 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.
+
All times are local times in the race course time zone
-
Adding a contact phone number and email is not mandatory, but we
- strongly recommend it.
-
If your event has a registration closure deadline, participants
- have to enter (and can withdraw) before the registration closure time.
-
Participants can submit results until the evaluation closure time.
-
+
All times are local times in the race course time zone
+
Adding a contact phone number and email is not mandatory, but we
+ strongly recommend it.
+
If your event has a registration closure deadline, participants
+ have to enter (and can withdraw) before the registration closure time.
+
Participants can submit results until the evaluation closure time.
+
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.
diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py
index 0142f292..c9b81bd3 100644
--- a/rowers/templatetags/rowerfilters.py
+++ b/rowers/templatetags/rowerfilters.py
@@ -370,6 +370,14 @@ def is_past_due(self):
def is_not_past_due(self):
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
def userurl(path,member):
pattern = re.compile('user\/\d+')
diff --git a/rowers/views.py b/rowers/views.py
index 2b048024..307ba0de 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -49,7 +49,8 @@ from rowers.forms import (
VirtualRaceSelectForm,WorkoutRaceSelectForm,CourseSelectForm,
RaceResultFilterForm,PowerIntervalUpdateForm,FlexAxesForm,
FlexOptionsForm,DataFrameColumnsForm,OteWorkoutTypeForm,
- MetricsForm,
+ MetricsForm,DisqualificationForm,disqualificationreasons,
+ disqualifiers
)
from django.core.urlresolvers import reverse, reverse_lazy
@@ -156,6 +157,7 @@ from rowers.tasks import handle_makeplot,handle_otwsetpower,handle_sendemailtcx,
from rowers.tasks import (
handle_sendemail_unrecognized,handle_sendemailnewcomment,
handle_sendemailsummary,
+ handle_send_disqualification_email,
handle_sendemailfile,
handle_sendemailkml,
handle_sendemailnewresponse, handle_updatedps,
@@ -15837,9 +15839,6 @@ def virtualevent_disqualify_view(request,raceid=0,recordid=0):
r = getrower(request.user)
- # datum moet voor race evaluation date zijn (ook in template controleren)
-
-
try:
race = VirtualRace.objects.get(id=raceid)
except VirtualRace.DoesNotExist:
@@ -15853,24 +15852,111 @@ def virtualevent_disqualify_view(request,raceid=0,recordid=0):
else:
recordobj = IndoorVirtualRaceResult
- if timezone.now() > race.evaluation_closure:
- try:
- record = recordobj.objects.get(id=recordid)
+ # 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()
- print record.coursecompleted
- except recordobj.DoesNotExist:
- messages.error(request,"We couldn't find the record")
+
+ url = reverse(virtualevent_view,kwargs={'id':raceid})
+
+ return HttpResponseRedirect(url)
+
else:
- messages.error(request,"The evaluation is already closed and the results are official")
+ form = DisqualificationForm(request.POST)
- url = reverse(virtualevent_view,kwargs={'id':raceid})
+ workout = Workout.objects.get(id=record.workoutid)
- return HttpResponseRedirect(url)
+ 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):
From 1c79a03f276203b50c079d041bfb905acab0bf39 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Wed, 28 Nov 2018 15:39:25 +0100
Subject: [PATCH 7/7] added explanation
---
rowers/templates/indoorvirtualeventcreate.html | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/rowers/templates/indoorvirtualeventcreate.html b/rowers/templates/indoorvirtualeventcreate.html
index 69970198..1492d6aa 100644
--- a/rowers/templates/indoorvirtualeventcreate.html
+++ b/rowers/templates/indoorvirtualeventcreate.html
@@ -45,12 +45,16 @@
If your event has a registration closure deadline, participants
have to enter (and can withdraw) before the registration closure time.
Participants can submit results until the evaluation closure time.
-
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.
-
+
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.
+
+
+ If you require a screenshot of the PM monitor, do mention this
+ in the comment.
+