Private
Public Access
1
0

disqualification now with review and form

This commit is contained in:
Sander Roosendaal
2018-11-28 15:37:41 +01:00
parent a75b59519e
commit 774028497b
10 changed files with 385 additions and 32 deletions

View File

@@ -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))

View File

@@ -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')

View File

@@ -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 <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
def handle_sendemail_expired(useremail,userfirstname,userlastname,expireddate,
**kwargs):

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 %}

View File

@@ -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 %}

View File

@@ -39,12 +39,18 @@
<li class="grid_1">
<p>
<ul>
<li>All times are local times in the time zone you select</li>
<li>Adding a contact phone number and email is not mandatory, but we
strongly recommend it.</li>
<li>If your event has a registration closure deadline, participants
have to enter (and can withdraw) before the registration closure time.</li>
<li>Participants can submit results until the evaluation closure time.</li>
<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>
</ul>
</p>
</li>

View File

@@ -140,7 +140,11 @@
<li class="grid_2">
<div id="results">
<p>
{% if race|is_final %}
<h2>Final Results</h2>
{% else %}
<h2>Results</h2>
{% endif %}
</p>
<p>
{% if results or dns %}
@@ -184,7 +188,7 @@
<a href="/rowers/workout/{{ result.workoutid }}">
Details</a></td>
<td>
{% if race.manager == request.user %}
{% if race.manager == request.user and not race|is_final %}
<a href="/rowers/virtualevent/{{ race.id }}/disqualify/{{ result.id }}/">
Disqualify
</a>
@@ -287,11 +291,13 @@
up and manage small regattas.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
@@ -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.
</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>
</li>
</ul>

View File

@@ -37,15 +37,17 @@
</form>
</li>
<li class="grid_1">
<p>
<ul>
<li>All times are local times in the race course time zone</li>
<li>Adding a contact phone number and email is not mandatory, but we
strongly recommend it.</li>
<li>If your event has a registration closure deadline, participants
have to enter (and can withdraw) before the registration closure time.</li>
<li>Participants can submit results until the evaluation closure time.</li>
</ul>
<p>All times are local times in the race course time zone</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>
</li>
</ul>

View File

@@ -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+')

View File

@@ -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):