Private
Public Access
1
0

Merge branch 'feature/popupregatta' into develop

This commit is contained in:
Sander Roosendaal
2018-04-20 13:38:45 +02:00
24 changed files with 7893 additions and 51 deletions
+4 -1
View File
@@ -6,7 +6,7 @@ from .models import (
Rower, Workout,GraphImage,FavoriteChart,SiteAnnouncement,
Team,TeamInvite,TeamRequest,
WorkoutComment,C2WorldClassAgePerformance,PlannedSession,
GeoCourse,GeoPolygon,GeoPoint,
GeoCourse,GeoPolygon,GeoPoint,VirtualRace,
)
# Register your models here so you can use them in the Admin module
@@ -60,6 +60,8 @@ class GeoCourseAdmin(admin.ModelAdmin):
inlines = (GeoPolygonInline,)
class VirtualRaceAdmin(admin.ModelAdmin):
list_display = ('manager','name','startdate','country')
admin.site.unregister(User)
admin.site.register(User,UserAdmin)
@@ -75,3 +77,4 @@ admin.site.register(C2WorldClassAgePerformance,
C2WorldClassAgePerformanceAdmin)
admin.site.register(PlannedSession,PlannedSessionAdmin)
admin.site.register(GeoCourse, GeoCourseAdmin)
admin.site.register(VirtualRace, VirtualRaceAdmin)
+19
View File
@@ -16,6 +16,7 @@ import xml.etree.ElementTree as et
import pandas as pd
import numpy as np
from timezonefinder import TimezoneFinder
import dataprep
from rowers.utils import geo_distance
@@ -38,6 +39,24 @@ class InvalidTrajectoryError(Exception):
def __str__(self):
return repr(self.value)
def get_course_timezone(course):
polygons = GeoPolygon.objects.filter(course = course)
points = GeoPoint.objects.filter(polygon = polygons[0])
lat = points[0].latitude
lon = points[0].longitude
tf = TimezoneFinder()
try:
timezone_str = tf.timezone_at(lng=lon,lat=lat)
except ValueError:
timezone_str = 'UTC'
if timezone_str is None:
timezone_str = tf.closest_timezone_at(lng=lon,lat=lat)
if timezone_str is None:
timezone_str = 'UTC'
return timezone_str
def polygon_to_path(polygon):
points = GeoPoint.objects.filter(polygon=polygon).order_by("order_in_poly")
+1 -28
View File
@@ -809,34 +809,7 @@ def create_row_df(r,distance,duration,startdatetime,
return (id, message)
def totaltime_sec_to_string(totaltime):
hours = int(totaltime / 3600.)
if hours > 23:
message = 'Warning: The workout duration was longer than 23 hours. '
hours = 23
minutes = int((totaltime - 3600. * hours) / 60.)
if minutes > 59:
minutes = 59
if not message:
message = 'Warning: there is something wrong with the workout duration'
seconds = int(totaltime - 3600. * hours - 60. * minutes)
if seconds > 59:
seconds = 59
if not message:
message = 'Warning: there is something wrong with the workout duration'
tenths = int(10 * (totaltime - 3600. * hours - 60. * minutes - seconds))
if tenths > 9:
tenths = 9
if not message:
message = 'Warning: there is something wrong with the workout duration'
duration = "%s:%s:%s.%s" % (hours, minutes, seconds, tenths)
return duration
from utils import totaltime_sec_to_string
# Processes painsled CSV file to database
def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
+47
View File
@@ -695,6 +695,19 @@ class WorkoutSessionSelectForm(forms.Form):
widget = forms.CheckboxSelectMultiple,
)
class WorkoutRaceSelectForm(forms.Form):
def __init__(self, workoutdata, *args, **kwargs):
super(WorkoutRaceSelectForm, self).__init__(*args, **kwargs)
self.fields['workouts'] = forms.ChoiceField(
label='Workouts',
choices = workoutdata['choices'],
initial=workoutdata['initial'],
widget=forms.RadioSelect,
)
class PlannedSessionTeamForm(forms.Form):
team = forms.ModelMultipleChoiceField(
queryset=Team.objects.all(),
@@ -717,4 +730,38 @@ class PlannedSessionTeamMemberForm(forms.Form):
self.fields['members'].queryset = thesession.rower.all()
from rowers.models import VirtualRace,GeoCourse
def get_countries():
countries = VirtualRace.objects.order_by('country').values_list('country').distinct()
countries = tuple([(c[0],c[0]) for c in countries])
countries = countries+(('All','All'),)
return countries
class VirtualRaceSelectForm(forms.Form):
regattatypechoices = (
('upcoming','Upcoming Races'),
('ongoing','Ongoing Races'),
('previous','Previous Races'),
('my','My Races'),
('all','All Races'),
)
regattatype = forms.ChoiceField(
label='Type',
choices = regattatypechoices,
initial = 'upcoming',
)
country = forms.ChoiceField(
label='Country',
choices = get_countries()
)
def __init__(self, *args, **kwargs):
super(VirtualRaceSelectForm, self).__init__(*args, **kwargs)
self.fields['country'] = forms.ChoiceField(
choices = get_countries(),initial='All'
)
+123 -11
View File
@@ -10,7 +10,7 @@ from django.dispatch import receiver
from django.forms.widgets import SplitDateTimeWidget
from django.forms.extras.widgets import SelectDateWidget
from django.forms.formsets import BaseFormSet
from django.contrib.admin.widgets import AdminDateWidget
from django.contrib.admin.widgets import AdminDateWidget,AdminTimeWidget,AdminSplitDateTime
from datetimewidget.widgets import DateTimeWidget
from django.core.validators import validate_email
import os
@@ -422,18 +422,19 @@ def course_length(course):
return int(totaldist)
# Extension of User with rowing specific data
class Rower(models.Model):
weightcategories = (
('hwt','heavy-weight'),
('lwt','light-weight'),
)
sexcategories = (
sexcategories = (
('male','male'),
('female','female'),
('not specified','not specified'),
)
)
weightcategories = (
('hwt','heavy-weight'),
('lwt','light-weight'),
)
# Extension of User with rowing specific data
class Rower(models.Model):
stravatypes = (
('Ride','Ride'),
@@ -1056,6 +1057,44 @@ class PlannedSession(models.Model):
super(PlannedSession,self).save(*args, **kwargs)
from django.core.validators import RegexValidator,validate_email
class VirtualRace(PlannedSession):
has_registration = models.BooleanField(default=False)
registration_closure = models.DateTimeField(blank=True,null=True)
evaluation_closure = models.DateTimeField(blank=True,null=True)
start_time = models.TimeField(blank=True,null=True)
end_time = models.TimeField(blank=True,null=True)
country = models.CharField(max_length=100,blank=True)
timezone = models.CharField(default='UTC',
choices=timezones,
max_length=100)
phone_regex = RegexValidator(
regex=r'^\+?1?\d{9,15}$',
message="Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed."
)
contact_phone = models.CharField(validators=[phone_regex], max_length=17, blank=True)
contact_email = models.EmailField(max_length=254,
validators=[validate_email],blank=True)
def __unicode__(self):
name = self.name
startdate = self.startdate
enddate = self.enddate
stri = u'Virtual Race {n}'.format(
n = name,
)
return stri
# Date input utility
class DateInput(forms.DateInput):
input_type = 'date'
@@ -1093,6 +1132,49 @@ class PlannedSessionForm(ModelForm):
super(PlannedSessionForm, self).__init__(*args, **kwargs)
self.fields['course'].queryset = GeoCourse.objects.all().order_by("country","name")
class VirtualRaceForm(ModelForm):
course = forms.ModelChoiceField(queryset = GeoCourse.objects, empty_label=None)
registration_closure = forms.SplitDateTimeField(widget=AdminSplitDateTime(),required=False)
evaluation_closure = forms.SplitDateTimeField(widget=AdminSplitDateTime(),required=False)
class Meta:
model = VirtualRace
fields = [
'name',
'startdate',
'start_time',
'enddate',
'end_time',
'has_registration',
'registration_closure',
'evaluation_closure',
'course',
'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(VirtualRaceForm, self).__init__(*args, **kwargs)
self.fields['course'].queryset = GeoCourse.objects.all().order_by("country","name")
class PlannedSessionFormSmall(ModelForm):
class Meta:
@@ -1124,12 +1206,12 @@ class PlannedSessionFormSmall(ModelForm):
'manager': forms.HiddenInput(),
}
boattypes = types.boattypes
# Workout
class Workout(models.Model):
workouttypes = types.workouttypes
workoutsources = types.workoutsources
boattypes = types.boattypes
privacychoices = types.privacychoices
user = models.ForeignKey(Rower)
@@ -1242,6 +1324,36 @@ def auto_delete_strokedata_on_delete(sender, instance, **kwargs):
conn.close()
engine.dispose()
# Virtual Race results (for keeping results when workouts are deleted)
class VirtualRaceResult(models.Model):
user = models.ForeignKey(Rower)
teamname = models.CharField(max_length=20,verbose_name = 'Team Name',
blank=True,null=True)
username = models.CharField(max_length=150)
workout = models.ForeignKey(Workout,blank=True,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))
boattype = models.CharField(choices=boattypes,max_length=40,
default='1x',
verbose_name = 'Boat Type'
)
coursecompleted = models.BooleanField(default=False)
sex = models.CharField(default="not specified",
max_length=30,
choices=sexcategories,
verbose_name='Gender')
age = models.IntegerField(null=True)
class VirtualRaceResultForm(ModelForm):
class Meta:
model = VirtualRaceResult
fields = ['teamname','weightcategory','boattype','age']
from rowers.metrics import rowingmetrics
strokedatafields = {
+300 -3
View File
@@ -7,8 +7,8 @@ import time
from django.db import IntegrityError
import uuid
from django.conf import settings
from utils import myqueue
import pytz
from utils import myqueue,calculate_age,totaltime_sec_to_string
import django_rq
queue = django_rq.get_queue('default')
@@ -18,7 +18,7 @@ queuehigh = django_rq.get_queue('low')
from rowers.models import (
Rower, Workout,Team,
GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle,
TrainingPlan,PlannedSession,
TrainingPlan,PlannedSession,VirtualRaceResult
)
import metrics
@@ -86,6 +86,30 @@ def timefield_to_seconds_duration(t):
return duration
def get_virtualrace_times(virtualrace):
geocourse = GeoCourse.objects.get(id = virtualrace.course.id)
timezone_str = courses.get_course_timezone(geocourse)
startdatetime = datetime.datetime.combine(
virtualrace.startdate,virtualrace.start_time)
enddatetime = datetime.datetime.combine(
virtualrace.enddate,virtualrace.end_time)
startdatetime = pytz.timezone(timezone_str).localize(
startdatetime
)
enddatetime = pytz.timezone(timezone_str).localize(
enddatetime
)
return {
'startdatetime':startdatetime,
'enddatetime':enddatetime,
'evaluation_closure':virtualrace.evaluation_closure,
'registration_closure':virtualrace.registration_closure,
}
def get_session_metrics(ps):
rowers = ps.rower.all()
rscore = []
@@ -401,6 +425,13 @@ def get_sessions(r,startdate=date.today(),
return sps
def get_my_session_ids(r):
sps = PlannedSession.objects.filter(
rower__in=[r]
).order_by("preferreddate","startdate","enddate")
return [ps.id for ps in sps]
def get_workouts_session(r,ps):
ws = Workout.objects.filter(user=r,plannedsession=ps)
@@ -416,3 +447,269 @@ def update_plannedsession(ps,cd):
ps.save()
return 1,'Planned Session Updated'
def update_virtualrace(ps,cd):
for attr, value in cd.items():
if attr == 'comment':
value.replace("\r\n", "&#10");
value.replace("\n", "&#10");
setattr(ps, attr, value)
# correct times
course = cd['course']
geocourse = GeoCourse.objects.get(id= course.id)
timezone_str = courses.get_course_timezone(geocourse)
startdatetime = datetime.combine(cd['startdate'],cd['start_time'])
enddatetime = datetime.combine(cd['enddate'],cd['end_time'])
startdatetime = pytz.timezone(timezone_str).localize(
startdatetime
)
enddatetime = pytz.timezone(timezone_str).localize(
enddatetime
)
ps.evaluation_closure = pytz.timezone(timezone_str).localize(
ps.evaluation_closure.replace(tzinfo=None)
)
ps.registration_closure = pytz.timezone(timezone_str).localize(
ps.registration_closure.replace(tzinfo=None)
)
ps.timezone = timezone_str
ps.save()
return 1,'Virtual Race Updated'
def race_rower_status(r,race):
ws = Workout.objects.filter(user=r,plannedsession=race)
is_complete = is_session_complete_ws(ws,race)[1]
has_registered = r in race.rower.all()
return is_complete,has_registered
def race_can_edit(r,race):
if r.user != race.manager:
return False
else:
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
if timezone.now()<startdatetime:
return True
else:
return False
return False
def race_can_submit(r,race):
if r not in race.rower.all():
return False
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
evaluation_closure = race.evaluation_closure
if timezone.now() > startdatetime and timezone.now() < evaluation_closure:
is_complete,has_registered = race_rower_status(r,race)
if is_complete == 'not done':
return True
else:
return False
else:
return False
return False
def race_can_resubmit(r,race):
if r not in race.rower.all():
return False
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
evaluation_closure = race.evaluation_closure
if timezone.now() > startdatetime and timezone.now() < evaluation_closure:
is_complete,has_registered = race_rower_status(r,race)
if is_complete in ['partial','completed']:
return True
else:
return False
else:
return False
return False
def race_can_withdraw(r,race):
if r not in race.rower.all():
return False
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
registration_closure = race.registration_closure
if registration_closure is not None and registration_closure != '':
if timezone.now() > registration_closure:
return False
elif timezone.now() > startdatetime:
return False
elif timezone.now() > startdatetime:
return False
return True
def race_can_register(r,race):
if r in race.rower.all():
return False
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
registration_closure = race.registration_closure
if registration_closure is not None and registration_closure != '':
if timezone.now() > registration_closure:
return False
elif timezone.now() > startdatetime:
return False
elif timezone.now() > startdatetime:
return False
return True
def add_rower_race(r,race):
race.rower.add(r)
race.save()
return 1
def remove_rower_race(r,race):
race.rower.remove(r)
records = VirtualRaceResult.objects.filter(user=r,
workout__isnull=True,
race=race)
for r in records:
r.delete()
return 1
# Low Level functions - to be called by higher level methods
def add_workout_race(ws,race,r):
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
if len(ws)>1 and race.sessiontype == 'test':
errors.append('For tests, you can only attach one workout')
return result,comments,errors
ids = [w.id for w in ws]
ids = list(set(ids))
if len(ids)>1 and race.sessiontype in ['test','coursetest']:
errors.append('For tests, you can only attach one workout')
return result,comments,errors
# start adding sessions
for w in ws:
if w.startdatetime>=startdatetime and w.startdatetime<=enddatetime:
w.plannedsession = race
w.save()
result += 1
comments.append('Your result has been submitted')
else:
errors.append('Workout %i did not match the race window' % w.id)
return result,comments,errors
if result>0:
username = r.user.first_name+' '+r.user.last_name
if r.birthdate:
age = calculate_age(r.birthdate)
else:
age = None
(
coursetime,
coursemeters,
coursecompleted
) = courses.get_time_course(ws,race.course)
if not coursecompleted:
errors.append('Your trajectory did not match the race course')
return result,comments,errors
duration = totaltime_sec_to_string(coursetime)
record = VirtualRaceResult(
user=r,
username=username,
workout = ws[0],
race = race,
coursecompleted=coursecompleted,
duration = duration,
boattype = ws[0].boattype,
sex = r.sex,
age = age,
)
record.save()
return result,comments,errors
def delete_race_result(workout,race):
results = VirtualRaceResult.objects.filter(workout=workout,race=race)
for r in results:
r.delete()
+2 -2
View File
@@ -63,11 +63,11 @@
</table>
{% csrf_token %}
</div>
<div class="grid_2">
<div class="grid_2 alpha">
<input name='daterange' class="button green" type="submit" value="Submit"> </form>
</div>
{% if user.is_authenticated and user|is_manager %}
<div class="grid_2 omega dropdown">
<div class="grid_2 dropdown">
<button class="grid_2 alpha button green small dropbtn">
{{ rower.user.first_name }} {{ rower.user.last_name }}
</button>
+11 -1
View File
@@ -52,7 +52,7 @@
<div id="right" class="grid_6 omega">
{% if plannedsession.sessiontype == 'test' or plannedsession.sessiontype == 'coursetest' %}
<h1>Ranking</h1>
<table class="listtable shortpadded" width="80%">
<table id="rankingtable" class="listtable shortpadded tablesorter" width="80%">
<thead>
<tr>
<th>Nr</th>
@@ -158,4 +158,14 @@
{% endblock %}
{% block scripts %}
<script type="text/javascript" src="/static/admin/js/jquery.min.js"></script>
<script type="text/javascript" src="/static/admin/js/jquery.tablesorter.min.js"></script>
<script type="text/javascript" src="/static/admin/js/jquery.tablesorter.widgets.js"></script>
<script>
$( document ).ready(function() {
$("#rankingtable").tablesorter();
});
</script>
{% endblock %}
+56
View File
@@ -0,0 +1,56 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Submit Race Result{% endblock %}
{% block meta %}
<script type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
</script>
<script type='text/javascript'
src='https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js'>
</script>
<script>
</script>
{% endblock %}
{% block content %}
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<h1>Submit Your Result for {{ race.name }}</h1>
</div>
</div>
<form id="race_submit_form"
method="post">
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<p>Select one of the following workouts that you rowed within the race window</p>
<table width="100%">
<tr>
{% for field in w_form.hidden_fields %}
{{ field }}
{% endfor %}
{% for field in w_form.visible_fields %}
<td>
{{ field }}
</td>
{% endfor %}
</tr>
</table>
</div>
</div>
<div class="grid_2 prefix_2 suffix_8">
{% csrf_token %}
<input class="button green" type="submit" value="Submit">
</div>
</form>
{% endblock %}
{% block scripts %}
{% endblock %}
+210
View File
@@ -0,0 +1,210 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}New Virtual Race{% endblock %}
{% block content %}
<div class="grid_12 alpha">
<h1>{{ race.name }}</h1>
</div>
<div class="grid_12 alpha">
<div class="grid_8 alpha">
<div id="raceinfo">
<h2>Race Information</h2>
<p>
<table class="listtable shortpadded" width="80%">
<tbody>
<tr>
<th>Course</th><td>{{ race.course }}</td>
</tr>
{% if race.has_registration %}
<tr>
<th>Registration closure</th>
<td>{{ race.registration_closure }}</td>
</tr>
{% endif %}
<tr>
<th>Date</th><td>{{ race.startdate }}</td>
</tr>
<tr>
<th>Race Window</th><td>{{ race.startdate }} {{ race.start_time }} to {{ race.enddate }} {{ race.end_time }}</td>
</tr>
<tr>
<th>Results Submission Deadline</th><td>{{ race.evaluation_closure }}</td>
</tr>
<tr>
<th>Organizer</th><td>{{ race.manager.first_name }} {{ race.manager.last_name }}</td>
</tr>
<tr>
<th>Contact Email</th><td>{{ race.contact_email }}</td>
</tr>
<tr>
<th>Contact Phone</th><td>{{ race.contact_phone }}</td>
</tr>
<tr>
<th>Comment</th><td>{{ race.comment }}</td>
</tr>
</tbody>
</table>
</p>
</div>
<div id="registerbuttons">
{% if request.user.is_anonymous %}
<p>
Registered users of rowsandall.com can participate in this event.
</p>
{% else %}
<p>
See race rules below.
</p>
<p>
{% for button in buttons %}
{% if button == 'registerbutton' %}
<a href="/rowers/virtualevent/{{ race.id }}/register" class="button gray small grid_2">Register</a>
{% endif %}
{% if button == 'submitbutton' %}
<a href="/rowers/virtualevent/{{ race.id }}/submit" class="button gray small grid_2">Submit Result</a>
{% endif %}
{% if button == 'resubmitbutton' %}
<a href="/rowers/virtualevent/{{ race.id }}/submit" class="button gray small grid_2">Submit New Result</a>
{% endif %}
{% if button == 'withdrawbutton' %}
<a href="/rowers/virtualevent/{{ race.id }}/withdraw" class="button gray small grid_2">Withdraw</a>
{% endif %}
{% if button == 'editbutton' %}
<a href="/rowers/virtualevent/{{ race.id }}/edit" class="button gray small grid_2">Edit Race</a>
{% endif %}
{% endfor %}
</p>
{% endif %}
</div>
<div id="results">
<h2>Results</h2>
<p>
{% if results or dns %}
<table class="listtable shortpadded" width="100%">
<thead>
<tr>
<th>&nbsp;</th>
<th>Name</th>
<th>Team Name</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>Boat</th>
<th>Raw Time</th>
<th>In Class</th>
<th>Corrected Time</th>
<th>Corrected Place</th>
</tr>
</thead>
<tbody>
{% for result in results %}
<tr>
<td>{{ forloop.counter }}</td>
<td>
<a href="/rowers/workout/{{ result.workout.id }}">
{{ result.username }}</a></td>
<td>{{ result.teamname }}</th>
<td>{{ result.age }}</td>
<td>{{ result.sex }}</td>
<td>{{ result.weightcategory }}</td>
<td>{{ result.boattype }}</td>
<td>{{ result.duration |durationprint:"%H:%M:%S.%f" }}</td>
</tr>
{% endfor %}
{% for result in dns %}
<tr>
<td>&nbsp;</td>
<td>{{ result.username }}</td>
<td>{{ result.teamname }}</td>
<td>{{ result.age }}</td>
<td>{{ result.sex }}</td>
<td>{{ result.weightcategory }}</td>
<td>{{ result.boattype }}</td>
<td>DNS</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
No results yet
{% endif %}
</p>
</div>
<div id="registered">
{% if records %}
<h2>Registered Competitors</h2>
<table class="listtable shortpadded" width="80%">
<thead>
<tr>
<th>Name</th>
<th>Team Name</th>
<th>Age</th>
<th>Weight Category</th>
</tr>
<tbody>
{% for record in records %}
<tr>
<td>{{ record.username }}
<td>{{ record.teamname }}</td>
<td>{{ record.weightcategory }}</td>
<td>{{ record.age }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
<div id="rules">
<h2>Rules</h2>
<p>
As a rowsandall.com user, you can
register to take part in this event.
If the race organizer has set a registration deadline, you must
register before the deadline. Otherwise, it is sufficient to
register before the start of the race window.
You can always withdraw from participating before the registration
deadline or the start of the race window, if no registration
deadline was set.
</p>
<p>
After the start of the race window and before the submission deadline,
you can submit results by linking the race to one of your uploaded
workouts. The workout start time must be within the race window
and your trajectory must pass through the blue polygons on the course
map (in the right order), for your result to be valid.
</p>
<p>
The results table has a link to a page where details of your workout
are shown.
</p>
<p>
Race results are stored permanently and are not deleted when
you delete the respective workout or remove your account.
By registering, you agree with this and the race rules.
</p>
<p>
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
boat type correctly. For (future functionality) age and gender
corrected times, please be sure your gender and birth date are set
correctly in your user settings.
</p>
</div>
</div>
<div class="grid_4 omega">
<h2>Course</h2>
{{ coursediv|safe }}
{{ coursescript|safe }}
</div>
</div>
{% endblock %}
+49
View File
@@ -0,0 +1,49 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}New Virtual Race{% endblock %}
{% block content %}
<div class="grid_12 alpha">
<h1>New Virtual Race</h1>
<div class="grid_8 alpha">
<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 %}
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<div id="formbutton" class="grid_1 prefix_4 suffix_1">
<input class="button green" type="submit" value="Save">
</div>
</form>
</div>
<div class="grid_4 omega">
<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>
</div>
</div>
{% endblock %}
{% block scripts %}
{% endblock %}
+49
View File
@@ -0,0 +1,49 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Edit Virtual Race{% endblock %}
{% block content %}
<div class="grid_12 alpha">
<h1>Edit Race {{ race.name }}</h1>
<div class="grid_8 alpha">
<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 %}
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<div id="formbutton" class="grid_1 prefix_4 suffix_1">
<input class="button green" type="submit" value="Save">
</div>
</form>
</div>
<div class="grid_4 omega">
<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>
</div>
</div>
{% endblock %}
{% block scripts %}
{% endblock %}
@@ -0,0 +1,50 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Register for a Virtual Race{% endblock %}
{% block meta %}
<script type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
</script>
<script type='text/javascript'
src='https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js'>
</script>
<script>
</script>
{% endblock %}
{% block content %}
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<h1>Register for {{ race.name }}</h1>
</div>
</div>
<form id="race_register_form"
method="post">
<div class="grid_12 alpha">
<p>If you are participating in a single, we will take the age and weight
value from your user settings. For other boat types, please fill out
crew weight class and average age.
</p>
<div class="grid_6 alpha">
<table width="100%">
{{ form.as_table }}
</table>
</div>
</div>
<div class="grid_2 prefix_2 suffix_8">
{% csrf_token %}
<input class="button green" type="submit" value="Submit">
</div>
</form>
{% endblock %}
{% block scripts %}
{% endblock %}
+59
View File
@@ -0,0 +1,59 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Rowsandall Virtual Racing{% endblock %}
{% block scripts %}
{% endblock %}
{% block content %}
<div class="grid_12 alpha">
<div class="grid_2 alpha suffix_10">
<p>
<a class="button green small" href="/rowers/virtualevent/create">Add Race</a>
</p>
</div>
</div>
<div class="grid_12 alpha">
<form enctype="multipart/form-data" method="post">
<div class="grid_8 alpha">
{{ form.as_table }}
{% csrf_token %}
</div>
<div class="grid_2">
<input name='form' class='button green' type='submit' value="Submit">
</div>
</form>
</div>
<div class="grid_12 alpha">
<p>
<table width="100%" class="listtable shortpadded">
<thead>
<tr>
<th>Date</th>
<th>Event</th>
<th>Country</th>
<th>Course</th>
</tr>
</thead>
<tbody>
{% for race in races %}
<tr>
<td>{{ race.startdate }}</td>
<td><a href="/rowers/virtualevent/{{ race.id }}">{{ race.name }}</a></td>
<td>{{ race.course.country }}</td>
<td>{{ race.course.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</p>
</div>
{% endblock %}
+7
View File
@@ -5,6 +5,7 @@ import dateutil.parser
import json
import datetime
register = template.Library()
from rowers.utils import calculate_age
def strfdelta(tdelta):
minutes,seconds = divmod(tdelta.seconds,60)
@@ -136,6 +137,11 @@ from rowers.views import hasplannedsessions
def is_planmember(user):
return hasplannedsessions(user)
@register.filter
def get_age(r):
return calculate_age(r.birthdate)
@register.filter
def user_teams(user):
try:
@@ -196,6 +202,7 @@ def team_rowers(user):
return []
@register.filter
def verbosetimeperiod(timeperiod):
table = {
+8
View File
@@ -143,6 +143,14 @@ urlpatterns = [
url(r'^u/(?P<userid>\d+)/list-workouts/$',views.workouts_view),
url(r'^u/(?P<userid>\d+)/list-workouts/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view),
url(r'^list-workouts/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view),
url(r'^virtualevents$',views.virtualevents_view),
url(r'^virtualevent/create$',views.virtualevent_create_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+)/register$',views.virtualevent_register_view),
url(r'^virtualevent/(?P<id>\d+)/withdraw$',views.virtualevent_withdraw_view),
url(r'^virtualevent/(?P<id>\d+)/submit$',
views.virtualevent_submit_result_view),
url(r'^list-workouts/$',views.workouts_view),
url(r'^list-courses/$',views.courses_view),
url(r'^courses/upload$',views.course_upload_view),
+28
View File
@@ -337,3 +337,31 @@ def wavg(group, avg_name, weight_name):
return (d * w).sum() / w.sum()
except ZeroDivisionError:
return d.mean()
def totaltime_sec_to_string(totaltime):
hours = int(totaltime / 3600.)
if hours > 23:
message = 'Warning: The workout duration was longer than 23 hours. '
hours = 23
minutes = int((totaltime - 3600. * hours) / 60.)
if minutes > 59:
minutes = 59
if not message:
message = 'Warning: there is something wrong with the workout duration'
seconds = int(totaltime - 3600. * hours - 60. * minutes)
if seconds > 59:
seconds = 59
if not message:
message = 'Warning: there is something wrong with the workout duration'
tenths = int(10 * (totaltime - 3600. * hours - 60. * minutes - seconds))
if tenths > 9:
tenths = 9
if not message:
message = 'Warning: there is something wrong with the workout duration'
duration = "%s:%s:%s.%s" % (hours, minutes, seconds, tenths)
return duration
+473 -2
View File
@@ -34,6 +34,7 @@ from rowers.forms import (
WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement,
LandingPageForm,PlannedSessionSelectForm,WorkoutSessionSelectForm,
PlannedSessionTeamForm,PlannedSessionTeamMemberForm,
VirtualRaceSelectForm,WorkoutRaceSelectForm,
)
from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied
@@ -65,7 +66,8 @@ from rowers.models import (
Team,TeamForm,TeamInviteForm,TeamInvite,TeamRequest,
WorkoutComment,WorkoutCommentForm,RowerExportForm,
CalcAgePerformance,PowerTimeFitnessMetric,PlannedSessionForm,
PlannedSessionFormSmall,GeoCourseEditForm,
PlannedSessionFormSmall,GeoCourseEditForm,VirtualRace,
VirtualRaceForm,VirtualRaceResultForm,
)
from rowers.models import (
FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet
@@ -146,6 +148,7 @@ import matplotlib.pyplot as plt
from rowers.emails import send_template_email
from pytz import timezone as tz,utc
from timezonefinder import TimezoneFinder
import dateutil
import mpld3
from mpld3 import plugins
@@ -11511,7 +11514,6 @@ def rower_edit_view(request,rowerid=0,message=""):
r.sex = sex
r.birthdate = birthdate
if resetbounce and r.emailbounced:
print "aap"
r.emailbounced = False
r.save()
form = RowerForm(instance=r)
@@ -13313,3 +13315,472 @@ def plannedsession_deleteconfirm_view(request,id=0):
}
)
def virtualevents_view(request):
# default races
races = VirtualRace.objects.filter(
startdate__gte=datetime.date.today()
).order_by("startdate","start_time")
r = getrower(request.user)
if request.method == 'POST':
# process form
form = VirtualRaceSelectForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
country = cd['country']
regattatype = cd['regattatype']
if country == 'All':
countries = VirtualRace.objects.order_by('country').values_list('country').distinct()
else:
countries = [country]
if regattatype == 'upcoming':
races = VirtualRace.objects.filter(
startdate__gte=datetime.date.today(),
country__in=countries
).order_by("startdate","start_time")
elif regattatype == 'previous':
races = VirtualRace.objects.filter(
enddate__lt=datetime.date.today(),
country__in=countries
).order_by("startdate","start_time")
elif regattatype == 'ongoing':
races = VirtualRace.objects.filter(
startdate__lte=datetime.date.today(),
evaluation_closure__gte=datetime.date.today(),
country__in=countries
).order_by("startdate","start_time")
elif regattatype == 'my':
mysessions = get_my_session_ids(r)
races = VirtualRace.objects.filter(
id__in=mysessions,
country__in=countries
).order_by("startdate","start_time")
elif regattatype == 'all':
races = VirtualRace.objects.filter(
country__in=countries
).order_by("startdate","start_time")
else:
form = VirtualRaceSelectForm()
return render(request,'virtualevents.html',
{ 'races':races,
'form':form,
}
)
def virtualevent_view(request,id=0):
if not request.user.is_anonymous():
r = getrower(request.user)
else:
r = None
try:
race = VirtualRace.objects.get(id=id)
except VirtualRace.DoesNotExist:
raise Http404("Virtual Race does not exist")
script,div = course_map(race.course)
buttons = []
if not request.user.is_anonymous():
if race_can_register(r,race):
buttons += ['registerbutton']
if race_can_submit(r,race):
buttons += ['submitbutton']
if race_can_resubmit(r,race):
buttons += ['resubmitbutton']
if race_can_withdraw(r,race):
buttons += ['withdrawbutton']
if race_can_edit(r,race):
buttons += ['editbutton']
results = VirtualRaceResult.objects.filter(
race=race,
workout__isnull=False,
).order_by("duration")
# to-do - add DNS
dns = []
if timezone.now() > race.evaluation_closure:
print "aap"
dns = VirtualRaceResult.objects.filter(
race=race,
workout__isnull=True,
)
print dns[0].username,"noot"
records = VirtualRaceResult.objects.filter(
race=race
)
return render(request,'virtualevent.html',
{
'coursescript':script,
'coursediv':div,
'race':race,
'rower':r,
'results':results,
'buttons':buttons,
'dns':dns,
'records':records,
})
@login_required()
def virtualevent_withdraw_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 race_can_withdraw(r,race):
remove_rower_race(r,race)
messages.info(request,
"You have successfully withdrawn from this race.")
else:
messages.error(request,"You cannot withdraw from this race")
url = reverse(virtualevent_view,
kwargs = {
'id':race.id
})
return HttpResponseRedirect(url)
@login_required()
def virtualevent_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 = VirtualRaceResultForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
teamname = cd['teamname']
boattype = cd['boattype']
if not boattype == '1x':
weightcategory = cd['weightcategory']
age = cd['age']
else:
weightcategory = r.weightcategory
age = calculate_age(r.birthdate)
record = VirtualRaceResult(
user=r,
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),
boattype=boattype,
coursecompleted=False,
sex=r.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 = VirtualRaceResultForm(initial=initial)
return render(request,'virtualeventregister.html',
{
'form':form,
'race':race,
'rower':r,
})
@login_required()
def virtualevent_create_view(request):
r = getrower(request.user)
if request.method == 'POST':
racecreateform = VirtualRaceForm(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']
course = cd['course']
name = cd['name']
has_registration = cd['has_registration']
registration_closure = cd['registration_closure']
evaluation_closure = cd['evaluation_closure']
contact_phone = cd['contact_phone']
contact_email = cd['contact_email']
# correct times
geocourse = GeoCourse.objects.get(id= course.id)
timezone_str = courses.get_course_timezone(geocourse)
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)
)
registration_closure = pytz.timezone(timezone_str).localize(
registration_closure.replace(tzinfo=None)
)
vs = VirtualRace(
name=name,
startdate=startdate,
preferreddate = startdate,
start_time = start_time,
enddate=enddate,
end_time=end_time,
course=geocourse,
comment=comment,
sessiontype = 'coursetest',
timezone=timezone_str,
has_registration=has_registration,
evaluation_closure=evaluation_closure,
registration_closure=registration_closure,
contact_phone=contact_phone,
contact_email=contact_email,
country = course.country,
manager=request.user,
)
vs.save()
url = reverse(virtualevents_view)
return HttpResponseRedirect(url)
else:
racecreateform = VirtualRaceForm()
return render(request,'virtualeventcreate.html',
{
'form':racecreateform,
'rower':r,
})
@login_required()
def virtualevent_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 = VirtualRaceForm(request.POST,instance=race)
if racecreateform.is_valid():
cd = racecreateform.cleaned_data
res, message = update_virtualrace(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 = VirtualRaceForm(instance=race)
return render(request,'virtualeventedit.html',
{
'form':racecreateform,
'rower':r,
'race':race,
})
@login_required()
def virtualevent_submit_result_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")
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.datetime.combine(start_date, start_time)
startdatetime = pytz.timezone(race.timezone).localize(startdatetime)
end_time = race.end_time
end_date = race.enddate
enddatetime = datetime.datetime.combine(end_date, end_time)
enddatetime = pytz.timezone(race.timezone).localize(enddatetime)
can_submit = race_can_submit(r,race) or race_can_resubmit(r,race)
if not can_submit:
messages.error(request,'You cannot submit a result to this race')
url = reverse(virtualevent_view,
kwargs = {
'id':id
}
)
return HttpResponseRedirect(url)
ws = Workout.objects.filter(
user=r,
startdatetime__gte=startdatetime,
startdatetime__lte=enddatetime,
).order_by("date","startdatetime","id")
initialworkouts = [w.id for w in Workout.objects.filter(
user=r,plannedsession=race
)]
workoutdata = {}
workoutdata['initial'] = []
choices = []
for w in ws:
wtpl = (w.id, w.__unicode__())
choices.append(wtpl)
if w.id in initialworkouts:
workoutdata['initial'].append(w.id)
workoutdata['choices'] = tuple(choices)
if request.method == 'POST':
w_form = WorkoutRaceSelectForm(workoutdata,request.POST)
if w_form.is_valid():
selectedworkout = w_form.cleaned_data['workouts']
else:
selectedworkout = None
for w in ws:
remove_workout_plannedsession(w,race)
if selectedworkout is not None:
for w in ws:
remove_workout_plannedsession(w,race)
delete_race_result(w,race)
workouts = Workout.objects.filter(id=selectedworkout)
result,comments,errors = add_workout_race(workouts,race,r)
for c in comments:
messages.info(request,c)
for er in errors:
messages.error(request,er)
# redirect to race page
url = reverse(virtualevent_view,
kwargs = {
'id':race.id
})
return HttpResponseRedirect(url)
else:
w_form = WorkoutRaceSelectForm(workoutdata=workoutdata)
return render(request,'race_submit.html',
{
'race':race,
'workouts':ws,
'rower':r,
'w_form':w_form,
})
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+4
View File
@@ -39,6 +39,10 @@
{% endif %}
{% endblock %}
{% block challenges %}
<a class="button gray small" href="/rowers/virtualevents">Racing</a>
{% endblock %}
{% block content %}
{% endblock %}
+4
View File
@@ -62,6 +62,10 @@
{% endif %}
{% endblock %}
{% block challenges %}
<a class="button gray small" href="/rowers/virtualevents">Racing</a>
{% endblock %}
{% block body_bottom %}
</div>