Merge branch 'feature/popupregatta' into develop
This commit is contained in:
+4
-1
@@ -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)
|
||||
|
||||
@@ -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
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
)
|
||||
|
||||
+121
-9
@@ -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 = (
|
||||
('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
@@ -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", "
");
|
||||
value.replace("\n", "
");
|
||||
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()
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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> </th>
|
||||
<th>Name</th>
|
||||
<th>Team Name</th>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
<th> </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> </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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
+1
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Vendored
+1
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -39,6 +39,10 @@
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block challenges %}
|
||||
<a class="button gray small" href="/rowers/virtualevents">Racing</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -62,6 +62,10 @@
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block challenges %}
|
||||
<a class="button gray small" href="/rowers/virtualevents">Racing</a>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block body_bottom %}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user