disqualification now with review and form
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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):
|
||||
|
||||
138
rowers/templates/disqualification_view.html
Normal file
138
rowers/templates/disqualification_view.html
Normal 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 %}
|
||||
37
rowers/templates/disqualificationemail.html
Normal file
37
rowers/templates/disqualificationemail.html
Normal 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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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+')
|
||||
|
||||
112
rowers/views.py
112
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):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user