Private
Public Access
1
0

Merge branch 'release/v8.59'

This commit is contained in:
Sander Roosendaal
2018-11-28 16:59:48 +01:00
29 changed files with 1578 additions and 77 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ class RowerInline(admin.StackedInline):
fieldsets = (
('Rower Plan',
{'fields':('rowerplan','planexpires','teamplanexpires','clubsize','protrialexpires','plantrialexpires',)}),
{'fields':('rowerplan','paymenttype','planexpires','teamplanexpires','clubsize','protrialexpires','plantrialexpires',)}),
('Rower Settings',
{'fields':
('gdproptin','gdproptindate','weightcategory','sex','birthdate','getemailnotifications',
+8
View File
@@ -45,6 +45,8 @@ env = Environment(loader = FileSystemLoader(["rowers/templates"]))
from django.contrib.staticfiles import finders
def textify(html):
# Remove html tags and continuous whitespaces
text_only = re.sub('[ \t]+', ' ', strip_tags(html))
@@ -60,7 +62,11 @@ def send_template_email(from_email,to_email,subject,
html_content = htmly.render(context)
text_content = textify(html_content)
if 'cc' in kwargs:
msg = EmailMultiAlternatives(subject, text_content, from_email, to_email,cc=kwargs['cc'])
else:
msg = EmailMultiAlternatives(subject, text_content, from_email, to_email)
msg.attach_alternative(html_content, "text/html")
if 'attach_file' in kwargs:
@@ -82,6 +88,8 @@ def send_template_email(from_email,to_email,subject,
else:
emailbounced = False
if not emailbounced:
res = msg.send()
else:
+21
View File
@@ -30,6 +30,24 @@ class EmailForm(forms.Form):
botcheck = forms.CharField(max_length=5)
message = forms.CharField()
disqualificationreasons = (
('noimage','You did not attach a monitor screenshot or other photographic evidence as required'),
('suspicious','We doubt that you rowed this in the right boat class or type'),
('duplicate','This result looks like a duplicate entry'),
('other','Other Reason'),
)
disqualifiers = {}
for key, value in disqualificationreasons:
disqualifiers[key] = value
class DisqualificationForm(forms.Form):
reason = forms.ChoiceField(required=True,
choices=disqualificationreasons,
widget = forms.RadioSelect,)
message = forms.CharField(required=True,widget=forms.Textarea)
class MetricsForm(forms.Form):
avghr = forms.IntegerField(required=False,label='Average Heart Rate')
@@ -892,8 +910,11 @@ class RaceResultFilterForm(forms.Form):
self.fields['boatclass'].choices = boatclasschoices
# boattype
try:
theboattypees = [record.boattype for record in records]
theboattypees = list(set(theboattypees))
except AttributeError:
theboattypees = []
if len(theboattypees)<= 1:
del self.fields['boattype']
+21 -1
View File
@@ -5,8 +5,9 @@ import datetime
from utils import myqueue
import django_rq
queue = django_rq.get_queue('default')
from rowers.tasks import handle_updatefitnessmetric
from rowers.tasks import handle_updatefitnessmetric,handle_sendemail_expired
from rowers.mytypes import otwtypes
from django.contrib import messages
def getrower(user):
try:
@@ -101,3 +102,22 @@ class GDPRMiddleWare(object):
return redirect(
'/rowers/me/gdpr-optin/?next=%s' % nexturl
)
class RowerPlanMiddleWare(object):
def process_request(self, request):
if request.user.is_authenticated() and request.user.rower.rowerplan != 'basic':
if request.user.rower.paymenttype == 'single':
if request.user.rower.planexpires < timezone.now().date():
messg = 'Your paid plan has expired. We have reset you to a free basic plan.'
messages.error(request,messg)
r = getrower(request.user)
r.rowerplan = 'basic'
r.save()
# send email
job = myqueue(queue,
handle_sendemail_expired,
r.user.email,
r.user.first_name,
r.user.last_name,
str(r.planexpires))
+187 -1
View File
@@ -702,6 +702,14 @@ class Rower(models.Model):
rowerplan = models.CharField(default='basic',max_length=30,
choices=plans)
paymenttype = models.CharField(
default='single',max_length=30,
verbose_name='Payment Type',
choices=(
('single','single'),
('recurring','recurring')
)
)
planexpires = models.DateField(default=timezone.now)
teamplanexpires = models.DateField(default=timezone.now)
@@ -1656,6 +1664,7 @@ class PlannedSession(models.Model):
('cycletarget','Total for a time period'),
('coursetest','OTW test over a course'),
('race','Virtual Race'),
('indoorrace','Indoor Virtual Race'),
)
sessionmodechoices = (
@@ -1764,7 +1773,7 @@ class PlannedSession(models.Model):
else:
self.sessionunit = 'None'
if self.sessiontype == 'test':
if self.sessiontype == 'test' or self.sessiontype == 'indoorrace':
if self.sessionmode not in ['distance','time']:
if self.sessionvalue < 100:
self.sessionmode = 'time'
@@ -1929,6 +1938,125 @@ def get_course_timezone(course):
return timezone_str
class IndoorVirtualRaceForm(ModelForm):
registration_closure = forms.SplitDateTimeField(widget=AdminSplitDateTime(),required=False)
evaluation_closure = forms.SplitDateTimeField(widget=AdminSplitDateTime(),required=True)
timezone = forms.ChoiceField(initial='UTC',
choices=[(x,x) for x in pytz.common_timezones],
label='Time Zone')
class Meta:
model = VirtualRace
fields = [
'name',
'startdate',
'start_time',
'enddate',
'end_time',
'timezone',
'sessionvalue',
'sessionunit',
'registration_form',
'registration_closure',
'evaluation_closure',
'comment',
'contact_phone',
'contact_email',
]
dateTimeOptions = {
'format': 'yyyy-mm-dd',
'autoclose': True,
}
widgets = {
'comment': forms.Textarea,
'startdate': AdminDateWidget(),
'enddate': AdminDateWidget(),
'start_time': AdminTimeWidget(),
'end_time': AdminTimeWidget(),
'registration_closure':AdminSplitDateTime(),
'evaluation_closure':AdminSplitDateTime(),
}
def __init__(self,*args,**kwargs):
super(IndoorVirtualRaceForm, self).__init__(*args, **kwargs)
self.fields['sessionunit'].choices = [('min','minutes'),('m','meters')]
def clean(self):
cd = self.cleaned_data
timezone_str = cd['timezone']
start_time = cd['start_time']
if start_time is None:
raise forms.ValidationError(
'Must have start time',
code='missing_yparam1'
)
start_date = cd['startdate']
startdatetime = datetime.datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(timezone_str).localize(
startdatetime
)
end_time = cd['end_time']
if end_time is None:
raise forms.ValidationError(
'Must have end time',
code='missing endtime'
)
end_date = cd['enddate']
enddatetime = datetime.datetime.combine(end_date,end_time)
enddatetime = pytz.timezone(timezone_str).localize(
enddatetime
)
registration_closure = cd['registration_closure']
registration_form = cd['registration_form']
try:
evaluation_closure = cd['evaluation_closure']
except KeyError:
evaluation_closure = enddatetime+datetime.timedelta(days=1)
cd['evaluation_closure'] = evaluation_closure
if registration_form == 'manual':
try:
registration_closure = pytz.timezone(
timezone_str
).localize(
registration_closure.replace(tzinfo=None)
)
except AttributeError:
registration_closure = startdatetime
elif registration_form == 'windowstart':
registration_closure = startdatetime
elif registration_form == 'windowend':
registration_closure = enddatetime
else:
registration_closure = evaluation_closure
if registration_closure <= timezone.now():
raise forms.ValidationError("Registration Closure cannot be in the past")
if startdatetime > enddatetime:
raise forms.ValidationError("The Start of the Race Window should be before the End of the Race Window")
if cd['evaluation_closure'] <= enddatetime:
raise forms.ValidationError("Evaluation closure deadline should be after the Race Window closes")
if cd['evaluation_closure'] <= timezone.now():
raise forms.ValidationError("Evaluation closure cannot be in the past")
return cd
class VirtualRaceForm(ModelForm):
course = forms.ModelChoiceField(queryset = GeoCourse.objects, empty_label=None)
@@ -2301,6 +2429,54 @@ class VirtualRaceResult(models.Model):
s = self.sex,
)
# Virtual Race results (for keeping results when workouts are deleted)
class IndoorVirtualRaceResult(models.Model):
boatclasses = (type for type in mytypes.workouttypes if type[0] in mytypes.otetypes)
userid = models.IntegerField(default=0)
teamname = models.CharField(max_length=80,verbose_name = 'Team Name',
blank=True,null=True)
username = models.CharField(max_length=150)
workoutid = models.IntegerField(null=True)
weightcategory = models.CharField(default="hwt",max_length=10,
choices=weightcategories,
verbose_name='Weight Category')
race = models.ForeignKey(VirtualRace)
duration = models.TimeField(default=datetime.time(1,0))
distance = models.IntegerField(default=0)
boatclass = models.CharField(choices=boatclasses,
max_length=40,
default='rower',
verbose_name = 'Ergometer Class')
coursecompleted = models.BooleanField(default=False)
sex = models.CharField(default="not specified",
max_length=30,
choices=sexcategories,
verbose_name='Gender')
age = models.IntegerField(null=True)
def __unicode__(self):
rr = Rower.objects.get(id=self.userid)
name = '{u1} {u2}'.format(
u1 = rr.user.first_name,
u2 = rr.user.last_name,
)
if self.teamname:
return u'Entry for {n} for "{r}" on {c} with {t} ({s})'.format(
n = name,
r = self.race,
t = self.teamname,
c = self.boatclass,
s = self.sex,
)
else:
return u'Entry for {n} for "{r}" on {c} ({s})'.format(
n = name,
r = self.race,
c = self.boatclass,
s = self.sex,
)
class CourseTestResult(models.Model):
userid = models.IntegerField(default=0)
@@ -2310,6 +2486,16 @@ class CourseTestResult(models.Model):
distance = models.IntegerField(default=0)
coursecompleted = models.BooleanField(default=False)
class IndoorVirtualRaceResultForm(ModelForm):
class Meta:
model = IndoorVirtualRaceResult
fields = ['teamname','weightcategory','boatclass','age']
def __init__(self, *args, **kwargs):
super(IndoorVirtualRaceResultForm, self).__init__(*args, **kwargs)
class VirtualRaceResultForm(ModelForm):
class Meta:
model = VirtualRaceResult
+6
View File
@@ -225,6 +225,12 @@ otwtypes = (
'churchboat'
)
otetypes = (
'rower',
'dynamic',
'slides'
)
rowtypes = (
'water',
'rower',
+184 -6
View File
@@ -20,7 +20,7 @@ from rowers.models import (
Rower, Workout,Team,
GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle,
TrainingPlan,PlannedSession,VirtualRaceResult,CourseTestResult,
get_course_timezone
get_course_timezone, IndoorVirtualRaceResult
)
from rowers.courses import get_time_course
@@ -574,6 +574,56 @@ def update_plannedsession(ps,cd):
return 1,'Planned Session Updated'
def update_indoorvirtualrace(ps,cd):
for attr, value in cd.items():
if attr == 'comment':
value.replace("\r\n", "&#10");
value.replace("\n", "&#10");
setattr(ps, attr, value)
timezone_str = cd['timezone']
# correct times
startdatetime = datetime.combine(cd['startdate'],cd['start_time'])
enddatetime = datetime.combine(cd['enddate'],cd['end_time'])
startdatetime = pytz.timezone(timezone_str).localize(
startdatetime
)
enddatetime = pytz.timezone(timezone_str).localize(
enddatetime
)
ps.evaluation_closure = pytz.timezone(timezone_str).localize(
ps.evaluation_closure.replace(tzinfo=None)
)
registration_form = cd['registration_form']
registration_closure = cd['registration_closure']
if registration_form == 'manual':
try:
registration_closure = pytz.timezone(
timezone_str
).localize(
registration_closure.replace(tzinfo=None)
)
except AttributeError:
registration_closure = startdatetime
elif registration_form == 'windowstart':
registration_closure = startdatetime
elif registration_form == 'windowend':
registration_closure = enddatetime
else:
registration_closure = ps.evaluation_closure
ps.registration_closure = registration_closure
ps.timezone = timezone_str
ps.save()
return 1,'Virtual Race Updated'
def update_virtualrace(ps,cd):
for attr, value in cd.items():
if attr == 'comment':
@@ -631,7 +681,12 @@ def race_rower_status(r,race):
has_registered = False
is_complete = False
vs = VirtualRaceResult.objects.filter(userid=r.id,race=race)
if race.sessiontype == 'race':
resultobj = VirtualRaceResult
else:
resultobj = IndoorVirtualRaceResult
vs = IndoorVirtualRaceResult.objects.filter(userid=r.id,race=race)
if vs:
has_registered = True
is_complete = vs[0].coursecompleted
@@ -708,6 +763,10 @@ def race_can_resubmit(r,race):
return False
def race_can_adddiscipline(r,race):
if race.sessiontype != 'race':
return False
records = VirtualRaceResult.objects.filter(
userid=r.id,
race=race)
@@ -766,7 +825,12 @@ def race_can_withdraw(r,race):
return True
def race_can_register(r,race):
records = VirtualRaceResult.objects.filter(
if race.sessiontype == 'race':
recordobj = VirtualRaceResult
else:
recordobj = IndoorVirtualRaceResult
records = recordobj.objects.filter(
userid=r.id,
race=race)
@@ -798,13 +862,18 @@ def add_rower_race(r,race):
def remove_rower_race(r,race,recordid=None):
race.rower.remove(r)
if race.sessiontype == 'race':
recordobj = VirtualRaceResult
else:
recordobj = IndoorVirtualRaceResult
if recordid:
records = VirtualRaceResult.objects.filter(userid=r.id,
records = recordobj.objects.filter(userid=r.id,
workoutid__isnull=True,
race=race,
id=recordid)
else:
records = VirtualRaceResult.objects.filter(userid=r.id,
records = recordobj.objects.filter(userid=r.id,
workoutid__isnull=True,
race=race,)
for r in records:
@@ -813,6 +882,116 @@ def remove_rower_race(r,race,recordid=None):
return 1
# Low Level functions - to be called by higher level methods
def add_workout_indoorrace(ws,race,r,recordid=0):
result = 0
comments = []
errors = []
start_time = race.start_time
start_date = race.startdate
startdatetime = datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(race.timezone).localize(
startdatetime
)
end_time = race.end_time
end_date = race.enddate
enddatetime = datetime.combine(end_date,end_time)
enddatetime = pytz.timezone(race.timezone).localize(
enddatetime
)
# check if all sessions have same date
dates = [w.date for w in ws]
if (not all(d == dates[0] for d in dates)) and race.sessiontype not in ['challenge','cycletarget']:
errors.append('For tests and training sessions, selected workouts must all be done on the same date')
return result,comments,errors,0
if len(ws)>1 and race.sessiontype == 'test':
errors.append('For tests, you can only attach one workout')
return result,comments,errors,0
ids = [w.id for w in ws]
ids = list(set(ids))
if len(ids)>1 and race.sessiontype in ['test','coursetest','race','indoorrace']:
errors.append('For tests, you can only attach one workout')
return result,comments,errors,0
username = r.user.first_name+' '+r.user.last_name
if r.birthdate:
age = calculate_age(r.birthdate)
else:
age = None
record = IndoorVirtualRaceResult.objects.get(
userid=r.id,
race=race,
id=recordid
)
records = IndoorVirtualRaceResult.objects.filter(
userid=r.id,
race=race,
workoutid = ws[0].id
)
if not record:
errors.append("Couldn't find this entry")
return result,comments,errors,0
if race.sessionmode == 'distance':
if ws[0].distance != race.sessionvalue:
errors.append('Your workout did not have the correct distance')
return 0,comments, errors, 0
else:
record.distance = ws[0].distance
record.duration = ws[0].duration
else:
t = ws[0].duration
seconds = t.second+t.minute*60.+t.hour*3600.+t.microsecond/1.e6
if seconds != race.sessionvalue*60.:
errors.append('Your workout did not have the correct duration')
return 0, comments, errors, 0
else:
record.distance = ws[0].distance
record.duration = ws[0].duration
if ws[0].weightcategory != record.weightcategory:
errors.append('Your workout weight category did not match the weight category you registered')
return 0,comments, errors,0
# start adding sessions
if ws[0].startdatetime>=startdatetime and ws[0].startdatetime<=enddatetime:
ws[0].plannedsession = race
ws[0].save()
result += 1
else:
errors.append('Workout %i did not match the race window' % ws[0].id)
return result,comments,errors,0
if result>0:
for otherrecord in records:
otherrecord.workoutid = None
otherrecord.coursecompleted = False
otherrecord.save()
record.coursecompleted = True
record.workoutid = ws[0].id
record.save()
add_workouts_plannedsession(ws,race,r)
return result,comments,errors,0
def add_workout_race(ws,race,r,splitsecond=0,recordid=0):
result = 0
comments = []
@@ -895,7 +1074,6 @@ def add_workout_race(ws,race,r,splitsecond=0,recordid=0):
if result>0:
for otherrecord in records:
print otherrecord
otherrecord.workoutid = None
otherrecord.coursecompleted = False
otherrecord.save()
+1 -1
View File
@@ -299,7 +299,7 @@ def get_polar_workout(user,id,transactionid):
if response.status_code == 200:
exerciseurls = response.json()['exercises']
for exerciseurl in exerciseurls:
response = requests.gedt(exerciseurl,headers=headers)
response = requests.get(exerciseurl,headers=headers)
if response.status_code == 200:
exercise_dict = response.json()
thisid = exercise_dict['id']
+67
View File
@@ -41,6 +41,22 @@ from django.utils.html import strip_tags
from utils import deserialize_list,ewmovingaverage,wavg
from HTMLParser import HTMLParser
class MLStripper(HTMLParser):
def __init__(self):
self.reset()
self.fed = []
def handle_data(self, d):
self.fed.append(d)
def get_data(self):
return ''.join(self.fed)
def strip_tags(html):
s = MLStripper()
s.feed(html)
return s.get_data()
from rowers.dataprepnodjango import (
update_strokedata, new_workout_from_file,
getsmallrowdata_db, updatecpdata_sql,
@@ -721,6 +737,57 @@ def handle_updatedps(useremail, workoutids, debug=False,**kwargs):
# send email when a breakthrough workout is uploaded
@app.task
def handle_send_disqualification_email(
useremail,username,reason,message, racename, **kwargs):
if 'debug' in kwargs:
debug = kwargs['debug']
else:
debug = True
subject = "Your result for {n} has been disqualified on rowsandall.com".format(
n = racename
)
from_email = 'Rowsandall <support@rowsandall.com>'
d = {
'username':username,
'reason':reason,
'message': strip_tags(message),
'racename':racename,
}
res = send_template_email(from_email,[useremail],
subject,
'disqualificationemail.html',
d,**kwargs)
return 1
@app.task
def handle_sendemail_expired(useremail,userfirstname,userlastname,expireddate,
**kwargs):
if 'debug' in kwargs:
debug = kwargs['debug']
else:
debug = False
subject = "Your rowsandall.com paid account has expired"
from_email = 'Rowsandall <info@rowsandall.com>'
d = {
'first_name':userfirstname,
'last_name':userlastname,
'siteurl':siteurl,
'expireddate':expireddate,
}
res = send_template_email(from_email,[useremail],
subject,'accountexpiredemail.html',
d,cc=['support@rowsandall.com'],**kwargs)
return 1
@app.task
def handle_sendemail_breakthrough(workoutid, useremail,
+23
View File
@@ -0,0 +1,23 @@
{% extends "emailbase.html" %}
{% block body %}
<p>Dear <strong>{{ first_name }}</strong>,</p>
<p>
Your Pro account on rowsandall.com expired on {{ expireddate }}. It
has now been automatically reset to Basic.
Let me know if you have any questions. If you want to continue using Pro membership,
just sign up again through the site and I will change your membership back to Pro.
</p>
<p>
If you do not want to continue the Pro membership, I'd be interested to know why you
decided to not continue your Pro account. Did it not fulfill your expectations?
This information is valuable to improve the site for all users. Thank you!
</p>
<p>
Best Regards, the Rowsandall Team
</p>
{% endblock %}
+138
View File
@@ -0,0 +1,138 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block scripts %}
{% include "monitorjobs.html" %}
{% endblock %}
{% block title %}{{ workout.name }} {% endblock %}
{% block og_title %}{{ workout.name }} {% endblock %}
{% block description %}{{ workout.name }}
{{ workout.date }} - {{ workout.distance }}m - {{ workout.duration |durationprint:"%H:%M:%S.%f" }}{% endblock %}
{% block og_description %}{{ workout.name }}
{{ workout.date }} - {{ workout.distance }}m - {{ workout.duration |durationprint:"%H:%M:%S.%f" }}{% endblock %}
{% if graphs1 %}
{% endif %}
{% for graph in graphs1 %}
{% block og_image %}
{% if graphs1 %}
{% for graph in graphs %}
<meta property="og:image" content="http://rowsandall.com/{{ graph.filename |spacetohtml }}" />
<meta property="og:image:secure_url" content="https://rowsandall.com/{{ graph.filename |spacetohtml }}" />
<meta property="og:image:width" content="{{ graph.width }}" />
<meta property="og:image:height" content="{{ graph.height }}" />
{% endfor %}
{% else %}
<meta property="og:image" content="http://rowsandall.com/static/img/logo_r.png" />
<meta property="og:image:secure_url" content="https://rowsandall.com/static/img/logo_r.png" />
{% endif %}
{% endblock %}
{% block image_src %}
{% for graph in graphs %}
<link rel="image_src" href="/{{ graph.filename |spacetohtml }}" />
{% endfor %}
{% endblock %}
{% endfor %}
{% block main %}
<h1>Do you want to disqualify this result?</h1>
<ul class="main-content">
<li class="grid_4">
<p>
Before you reject this race result, please carefully review it
using the information below. If you still want to reject the entry,
scroll down to the rejection form.
</p>
</li>
<li class="grid_2">
<table width=100%>
<tr>
<th>Rower:</th><td>{{ record.username }}</td>
</tr><tr>
<tr>
<th>Name:</th><td>{{ workout.name }}</td>
</tr><tr>
<tr>
<th>Date:</th><td>{{ workout.date }}</td>
</tr><tr>
<th>Time:</th><td>{{ workout.starttime }}</td>
</tr><tr>
<th>Distance:</th><td>{{ workout.distance }}m</td>
</tr><tr>
<th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td>
</tr><tr>
<th>Type:</th><td>{{ workout.workouttype }}</td>
</tr><tr>
<th>Weight Category:</th><td>{{ workout.weightcategory }}</td>
</tr>
</table>
</li>
<li class="grid_2">
<h1>Workout Summary</h1>
<p>
<pre>
{{ workout.summary }}
</pre>
</p>
</li>
{% for graph in graphs %}
<li>
<a href="/rowers/graph/{{ graph.id }}/">
<img src="/{{ graph.filename }}"
onerror="this.src='/static/img/rowingtimer.gif'"
alt="{{ graph.filename }}" width="120" height="100">
</a>
</li>
{% endfor %}
{% if mapdiv %}
<li class="grid_2">
<div class="mapdiv">
{{ mapdiv|safe }}
{{ mapscript|safe }}
</div>
</li>
{% endif %}
<li class="grid_2">
<script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
{{ the_div|safe }}
</li>
<li class="grid_4">
<h1>
Yes, I want to reject this entry
</h1>
<p>
Please select a reason and add a comment (mandatory).
Pressing 'Reject' disqualifies the submitted result.
An email will be sent to the rower. There is no "undo".
</p>
<p>
<form enctype="multipart/form-date" action="" method="post">
<table>
{{ form.as_table }}
</table>
</p>
<p>
{% csrf_token %}
<input type="submit" value="Reject">
</p>
</form>
</li>
</ul>
{% endblock %}
{% block sidebar %}
{% include 'menu_racing.html' %}
{% endblock %}
@@ -0,0 +1,37 @@
{% extends "emailbase.html" %}
{% block body %}
<p>Dear <strong>{{ username }}</strong>,</p>
<p>
Unfortunately, the result that you have submitted
for the virtual race {{ racename }}
has been rejected by the race organizer.
</p>
<p>
The reason for the rejection was: <i>{{ reason }}</i>.
</p>
<p>
The race organizer added the following explanation:
<p>
<p>
<i>{{ message }}</i>
</p>
<p>
The decision to reject your result is the sole responsibility of
the race organizer. If you disagree with the decision, please do contact
him or her.
</p>
<p>
You are still registered for the race and can submit a result.
</p>
<p>
Best Regards, the Rowsandall Team
</p>
{% endblock %}
+11
View File
@@ -19,6 +19,17 @@
{{ the_script |safe }}
<p>
{% if workout|previousworkout:rower.user %}
<a href="/rowers/workout/{{ workout|previousworkout:rower.user }}/flexchart"
title="Jump to preceding workout"><em>Previous</em></a>&nbsp;
{% endif %}
{% if workout|nextworkout:rower.user %}
<a href="/rowers/workout/{{ workout|nextworkout:rower.user }}/flexchart"
title="Jump to following workout"><em>Next</em></a>
{% endif %}
</p>
<h1>Flexible Chart</h1>
@@ -0,0 +1,71 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}New Virtual Race{% endblock %}
{% block main %}
<h1>New Indoor Virtual Race</h1>
<ul class="main-content">
<li class="grid_4">
<p>With this form, you can create a new virtual race. After you submit
the form, the race is created and will be visible to all users. From
that moment, only the site admin can delete the race
(admin@rowsandall.com). You can still edit the race until
the start of the race window.
</p>
</li>
<li class="grid_3">
<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 %}
<p>
<table>
{{ form.as_table }}
</table>
</p>
<p>
{% csrf_token %}
<input type="submit" value="Save">
</p>
</form>
</li>
<li class="grid_1">
<p>
<ul>
<p>All times are local times in the time zone you select</p>
<p>Adding a contact phone number and email is not mandatory, but we
strongly recommend it.</p>
<p>If your event has a registration closure deadline, participants
have to enter (and can withdraw) before the registration closure time.</p>
<p>Participants can submit results until the evaluation closure time.</p>
<p>Until one hour after evaluation closure time, the race organizer
can review and reject submitted results ("disqualification"). If
you as the race organizer intend to use this functionality, it
is strongly recommended that you fill out a contact email or phone
number.
</p>
<p>
If you require a screenshot of the PM monitor, do mention this
in the comment.
</p>
</ul>
</p>
</li>
</ul>
{% endblock %}
{% block scripts %}
{% endblock %}
{% block sidebar %}
{% include 'menu_racing.html' %}
{% endblock %}
+5
View File
@@ -10,6 +10,11 @@
<i class="far fa-flag fa-fw"></i>&nbsp;New Race
</a>
</li>
<li id="indoor-new">
<a href="/rowers/virtualevent/createindoor">
<i class="far fa-flag fa-fw"></i>&nbsp;New Indoor Race
</a>
</li>
<li id="courses">
<a href="/rowers/list-courses">
<i class="fas fa-map-marked fa-fw"></i>&nbsp;Courses
+1 -1
View File
@@ -1,5 +1,5 @@
{% load rowerfilters %}
<h1><a href="/rowers/workout/{{ workout.id }}/">Workout</a></h1>
<h1><a href="/rowers/list-workouts/">Workout</a></h1>
<ul class="cd-accordion-menu animated">
<li class="has-children" id="workout">
<input type="checkbox" name="group-workout" id="group-workout" checked>
+4 -2
View File
@@ -8,7 +8,8 @@
<th>Event</th>
<th>Country</th>
<th>Course</th>
<th>Distance</th>
<th></th>
<th></th>
<th>Click for Details</th>
</tr>
</thead>
@@ -26,7 +27,8 @@
<td><a href="/rowers/virtualevent/{{ race.id }}">{{ race.name }}</a></td>
<td>{{ race.course.country }}</td>
<td><a href="/rowers/courses/{{ race.course.id }}">{{ race.course.name }}</a></td>
<td>{{ race.sessionvalue }} m</td>
<td>{{ race.sessionvalue }}</td>
<td>{{ race.sessionunit }}</td>
<td>
{% if rower %}
{% if race|can_register:rower %}
+11
View File
@@ -6,6 +6,17 @@
{% block title %}Change Workout {% endblock %}
{% block main %}
<p>
{% if workout|previousworkout:rower.user %}
<a href="/rowers/workout/{{ workout|previousworkout:rower.user }}/editintervals"
title="Jump to preceding workout"><em>Previous</em></a>&nbsp;
{% endif %}
{% if workout|nextworkout:rower.user %}
<a href="/rowers/workout/{{ workout|nextworkout:rower.user }}/editintervals"
title="Jump to following workout"><em>Next</em></a>
{% endif %}
</p>
<h1>Edit Workout Interval Data</h1>
<ul class="main-content">
<li class="grid_2">
+73 -12
View File
@@ -14,6 +14,7 @@
<h1>{{ race.name }}</h1>
<ul class="main-content">
{% if race.sessiontype == 'race' %}
<li class="grid_2">
<p>
<h2>Course</h2>
@@ -24,6 +25,7 @@
{{ coursescript|safe }}
</div>
</li>
{% endif %}
<li class="grid_2">
<div id="raceinfo">
<p>
@@ -32,11 +34,23 @@
<p>
<table class="listtable shortpadded" width="100%">
<tbody>
{% if race.sessiontype == 'race' %}
<tr>
<th>Course</th><td>{{ race.course }}</td>
</tr>
{% else %}
<tr>
<th>Distance</th><td>{{ race.sessionvalue }} m</td>
<th>Indoor Race</th><td>To be rowed on a Concept2 ergometer</td>
</tr>
<tr>
<th>Time Zone</th><td>{{ race.timezone }}</td>
</tr>
{% endif %}
<tr>
<th>
{{ race.sessionmode }} challenge
</th><td>{{ race.sessionvalue }} {{ race.sessionunit }}
</td>
</tr>
<tr>
<th>Registration closure</th>
@@ -81,38 +95,42 @@
{% for button in buttons %}
{% if button == 'registerbutton' %}
<p>
<a href="/rowers/virtualevent/{{ race.id }}/register"
class="blue button">Register</a>
{% if race.sessiontype == 'race' %}
<a href="/rowers/virtualevent/{{ race.id }}/register">Register</a>
{% else %}
<a href="/rowers/virtualevent/{{ race.id }}/registerindoor">Register</a>
{% endif %}
</p>
{% endif %}
{% if button == 'submitbutton' %}
<a href="/rowers/virtualevent/{{ race.id }}/submit" class="blue button">Submit Result</a>
<a href="/rowers/virtualevent/{{ race.id }}/submit">Submit Result</a>
{% endif %}
{% if button == 'resubmitbutton' %}
<p>
<a href="/rowers/virtualevent/{{ race.id }}/submit"
class="blue button">Submit New Result</a>
<a href="/rowers/virtualevent/{{ race.id }}/submit">Submit New Result</a>
</p>
{% endif %}
{% if button == 'withdrawbutton' %}
<p>
<a href="/rowers/virtualevent/{{ race.id }}/withdraw"
class="blue button">Withdraw</a>
<a href="/rowers/virtualevent/{{ race.id }}/withdraw">Withdraw</a>
</p>
{% endif %}
{% if button == 'adddisciplinebutton' %}
<p>
<a href="/rowers/virtualevent/{{ race.id }}/adddiscipline"
class="blue button">
<a href="/rowers/virtualevent/{{ race.id }}/adddiscipline">
Register New Boat
</a>
</p>
{% endif %}
{% if button == 'editbutton' %}
<p>
<a href="/rowers/virtualevent/{{ race.id }}/edit"
class="blue button">Edit Race
{% if race.sessiontype == 'race' %}
<a href="/rowers/virtualevent/{{ race.id }}/edit">Edit Race
</a>
{% else %}
<a href="/rowers/virtualevent/{{ race.id }}/editindoor">Edit Race
</a>
{% endif %}
</p>
{% endif %}
{% endfor %}
@@ -122,7 +140,11 @@
<li class="grid_2">
<div id="results">
<p>
{% if race|is_final %}
<h2>Final Results</h2>
{% else %}
<h2>Results</h2>
{% endif %}
</p>
<p>
{% if results or dns %}
@@ -135,11 +157,14 @@
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
{% if race.sessiontype == 'race' %}
<th>Class</th>
<th>Boat</th>
{% endif %}
<th>Time</th>
<th>Distance</th>
<th>Details</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
@@ -153,13 +178,24 @@
<td>{{ result.age }}</td>
<td>{{ result.sex }}</td>
<td>{{ result.weightcategory }}</td>
{% if race.sessiontype == 'race' %}
<td>{{ result.boatclass }}</td>
<td>{{ result.boattype }}</td>
{% endif %}
<td>{{ result.duration |durationprint:"%H:%M:%S.%f" }}</td>
<td>{{ result.distance }} m</td>
<td>
<a href="/rowers/workout/{{ result.workoutid }}">
Details</a></td>
<td>
{% if race.manager == request.user and not race|is_final %}
<a href="/rowers/virtualevent/{{ race.id }}/disqualify/{{ result.id }}/">
Disqualify
</a>
{% else %}
&nbsp;
{% endif %}
</td>
</tr>
{% endfor %}
{% for result in dns %}
@@ -170,8 +206,10 @@
<td>{{ result.age }}</td>
<td>{{ result.sex }}</td>
<td>{{ result.weightcategory }}</td>
{% if race.sessiontype == 'race' %}
<td>{{ result.boatclass }}</td>
<td>{{ result.boattype }}</td>
{% endif %}
<td>DNS</td>
</tr>
{% endfor %}
@@ -210,8 +248,10 @@
<tr>
<th>Name</th>
<th>Team Name</th>
{% if race.sessiontype == 'race' %}
<th>Class</th>
<th>Boat</th>
{% endif %}
<th>Age</th>
<th>Gender</th>
<th>Weight Category</th>
@@ -221,8 +261,10 @@
<tr>
<td>{{ record.username }}
<td>{{ record.teamname }}</td>
{% if race.sessiontype == 'race' %}
<td>{{ record.boatclass }}</td>
<td>{{ record.boattype }}</td>
{% endif %}
<td>{{ record.age }}</td>
<td>{{ record.sex }}</td>
<td>{{ record.weightcategory }}</td>
@@ -247,6 +289,16 @@
Virtual races are intended as an informal way to add a
competitive element to training and as a quick way to set
up and manage small regattas.
</p>
<p>
On the water races are rowed on the course shown.
You cannot submit results rowed
on other bodies of water.
</p>
<p>
Indoor races are open for all, wherever you live.
However, be aware of the
time zone for the race window.
</p>
<p>
As a rowsandall.com user, you can
@@ -271,6 +323,10 @@
you delete the respective workout or remove your account.
By registering, you agree with this and the race rules.
</p>
<p>
If you use a manually added workout for your indoor race result,
please attach a screenshot of the ergometer display for verification.
</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
@@ -285,6 +341,11 @@
Individual participants are entirely responsible for their
safety while participating in a virtual race.
</p>
<p>
Until the evaluation closure time, the race organizer can
review and reject entries. If you are disqualified in this
way, you will receive an email with the reason.
</p>
</div>
</li>
</ul>
+12 -10
View File
@@ -32,20 +32,22 @@
</p>
<p>
{% csrf_token %}
<input class="button green" type="submit" value="Save">
<input type="submit" value="Save">
</p>
</form>
</li>
<li class="grid_1">
<p>
<ul>
<li>All times are local times in the race course time zone</li>
<li>Adding a contact phone number and email is not mandatory, but we
strongly recommend it.</li>
<li>If your event has a registration closure deadline, participants
have to enter (and can withdraw) before the registration closure time.</li>
<li>Participants can submit results until the evaluation closure time.</li>
</ul>
<p>All times are local times in the race course time zone</p>
<p>Adding a contact phone number and email is not mandatory, but we
strongly recommend it.</p>
<p>If your event has a registration closure deadline, participants
have to enter (and can withdraw) before the registration closure time.</p>
<p>Participants can submit results until the evaluation closure time.</p>
<p>Until one hour after evaluation closure time, the race organizer
can review and reject submitted results ("disqualification"). If
you as the race organizer intend to use this functionality, it
is strongly recommended that you fill out a contact email or phone
number.
</p>
</li>
</ul>
+11
View File
@@ -60,6 +60,17 @@
{% block main %}
<p>
{% if workout|previousworkout:rower.user %}
<a href="/rowers/workout/{{ workout|previousworkout:rower.user }}/workflow"
title="Jump to preceding workout"><em>Previous</em></a>&nbsp;
{% endif %}
{% if workout|nextworkout:rower.user %}
<a href="/rowers/workout/{{ workout|nextworkout:rower.user }}/workflow"
title="Jump to following workout"><em>Next</em></a>
{% endif %}
</p>
<h1>{{ workout.name }}</h1>
{% if workout.user.user != user %}
<h2>{{ workout.user.user.first_name }} {{ workout.user.user.last_name }}</h2>
+11
View File
@@ -10,6 +10,17 @@
{% block title %}Comment Workout {% endblock %}
{% block main %}
<p>
{% if workout|previousworkout:rower.user %}
<a href="/rowers/workout/{{ workout|previousworkout:rower.user }}/comment"
title="Jump to preceding workout"><em>Previous</em></a>&nbsp;
{% endif %}
{% if workout|nextworkout:rower.user %}
<a href="/rowers/workout/{{ workout|nextworkout:rower.user }}/comment"
title="Jump to following workout"><em>Next</em></a>
{% endif %}
</p>
<h1>Comments {{ workout.name }}</h1>
<ul class="main-content">
+11
View File
@@ -5,6 +5,17 @@
{% block title %}Workout Statistics{% endblock %}
{% block main %}
<p>
{% if workout|previousworkout:rower.user %}
<a href="/rowers/workout/{{ workout|previousworkout:rower.user }}/stats"
title="Jump to preceding workout"><em>Previous</em></a>&nbsp;
{% endif %}
{% if workout|nextworkout:rower.user %}
<a href="/rowers/workout/{{ workout|nextworkout:rower.user }}/stats"
title="Jump to following workout"><em>Next</em></a>
{% endif %}
</p>
<h1>Workout Statistics for {{ workout.name }}</h1>
<ul class="main-content">
<li class="grid_4">
+8
View File
@@ -370,6 +370,14 @@ def is_past_due(self):
def is_not_past_due(self):
return datetime.date.today() <= self.date
@register.filter
def is_closed(race):
return race.evaluation_closure < timezone.now()
@register.filter
def is_final(race):
return race.evaluation_closure < timezone.now()-datetime.timedelta(hours=1)
@register.filter
def userurl(path,member):
pattern = re.compile('user\/\d+')
+5
View File
@@ -143,14 +143,19 @@ urlpatterns = [
url(r'^list-workouts/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\d+-\d+-\d+)$',views.workouts_view),
url(r'^virtualevents$',views.virtualevents_view),
url(r'^virtualevent/create$',views.virtualevent_create_view),
url(r'^virtualevent/createindoor$',views.indoorvirtualevent_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+)/editindoor$',views.indoorvirtualevent_edit_view),
url(r'^virtualevent/(?P<id>\d+)/register$',views.virtualevent_register_view),
url(r'^virtualevent/(?P<id>\d+)/registerindoor$',views.indoorvirtualevent_register_view),
url(r'^virtualevent/(?P<id>\d+)/adddiscipline$',views.virtualevent_addboat_view),
url(r'^virtualevent/(?P<id>\d+)/withdraw/(?P<recordid>\d+)$',views.virtualevent_withdraw_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'^virtualevent/(?P<raceid>\d+)/disqualify/(?P<recordid>\d+)/',
views.virtualevent_disqualify_view),
url(r'^list-workouts/$',views.workouts_view),
url(r'^list-courses/$',views.courses_view),
url(r'^courses/upload$',views.course_upload_view),
+611 -14
View File
@@ -49,7 +49,8 @@ from rowers.forms import (
VirtualRaceSelectForm,WorkoutRaceSelectForm,CourseSelectForm,
RaceResultFilterForm,PowerIntervalUpdateForm,FlexAxesForm,
FlexOptionsForm,DataFrameColumnsForm,OteWorkoutTypeForm,
MetricsForm,
MetricsForm,DisqualificationForm,disqualificationreasons,
disqualifiers
)
from django.core.urlresolvers import reverse, reverse_lazy
@@ -90,7 +91,9 @@ from rowers.models import (
WorkoutComment,WorkoutCommentForm,RowerExportForm,
CalcAgePerformance,PowerTimeFitnessMetric,PlannedSessionForm,
PlannedSessionFormSmall,GeoCourseEditForm,VirtualRace,
VirtualRaceForm,VirtualRaceResultForm,RowerImportExportForm
VirtualRaceForm,VirtualRaceResultForm,RowerImportExportForm,
IndoorVirtualRaceResultForm,IndoorVirtualRaceResult,
IndoorVirtualRaceForm,
)
from rowers.models import (
FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet,
@@ -154,6 +157,7 @@ from rowers.tasks import handle_makeplot,handle_otwsetpower,handle_sendemailtcx,
from rowers.tasks import (
handle_sendemail_unrecognized,handle_sendemailnewcomment,
handle_sendemailsummary,
handle_send_disqualification_email,
handle_sendemailfile,
handle_sendemailkml,
handle_sendemailnewresponse, handle_updatedps,
@@ -15765,7 +15769,8 @@ def virtualevents_view(request):
if country == 'All':
countries = VirtualRace.objects.order_by('country').values_list('country').distinct()
else:
countries = [country]
countries = [country,
'Indoor']
if regattatype == 'upcoming':
races1 = VirtualRace.objects.filter(
@@ -15813,14 +15818,146 @@ def virtualevents_view(request):
'rower':r,
})
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
]
return render(request,'virtualevents.html',
{ 'races':races,
'form':form,
'breadcrumbs':breadcrumbs,
'active':'nav-racing',
'rower':r,
}
)
@login_required()
def virtualevent_disqualify_view(request,raceid=0,recordid=0):
r = getrower(request.user)
try:
race = VirtualRace.objects.get(id=raceid)
except VirtualRace.DoesNotExist:
raise Http404("Virtual Race does not exist")
if r.user != race.manager:
raise PermissionDenied("Access denied")
if race.sessiontype == 'race':
recordobj = VirtualRaceResult
else:
recordobj = IndoorVirtualRaceResult
# datum moet voor race evaluation date zijn (ook in template controleren)
try:
record = recordobj.objects.get(id=recordid)
except recordobj.DoesNotExist:
messages.error(request,"We couldn't find the record")
if timezone.now() > race.evaluation_closure+datetime.timedelta(hours=1):
messages.error(request,"The evaluation is already closed and the results are official")
url = reverse(virtualevent_view,kwargs={'id':raceid})
return HttpResponseRedirect(url)
if request.method == 'POST':
form = DisqualificationForm(request.POST)
if form.is_valid():
message = form.cleaned_data['message']
reason = form.cleaned_data['reason']
disqualifier = disqualifiers[reason]
u = User.objects.get(id=record.userid)
name = record.username
job = myqueue(queue,handle_send_disqualification_email,
u.email, name,
disqualifier,message,race.name)
messages.info(request,"We have invalidated the result for: "+str(record))
record.coursecompleted = False
record.save()
url = reverse(virtualevent_view,kwargs={'id':raceid})
return HttpResponseRedirect(url)
else:
form = DisqualificationForm(request.POST)
workout = Workout.objects.get(id=record.workoutid)
g = GraphImage.objects.filter(workout=workout).order_by("-creationdatetime")
for i in g:
try:
width,height = Image.open(i.filename).size
i.width = width
i.height = height
i.save()
except:
pass
script, div = interactive_chart(record.workoutid)
f1 = workout.csvfilename
rowdata = rdata(f1)
hascoordinates = 1
if rowdata != 0:
try:
latitude = rowdata.df[' latitude']
if not latitude.std():
hascoordinates = 0
except KeyError, AttributeError:
hascoordinates = 0
else:
hascoordinates = 0
if hascoordinates:
mapscript, mapdiv = leaflet_chart(rowdata.df[' latitude'],
rowdata.df[' longitude'],
workout.name)
else:
mapscript = ""
mapdiv = ""
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}),
'name': race.name
},
{
'url':reverse(virtualevent_disqualify_view,
kwargs={'raceid':raceid,
'recordid':recordid}),
'name': 'Disqualify Entry'
},
]
return render(request,"disqualification_view.html",
{'workout':workout,
'active':'nav-racing',
'graphs':g,
'interactiveplot':script,
'the_div':div,
'mapscript':mapscript,
'mapdiv':mapdiv,
'form':form,
'race':race,
'record':record,
})
def virtualevent_view(request,id=0):
results = []
@@ -15836,12 +15973,16 @@ def virtualevent_view(request,id=0):
except VirtualRace.DoesNotExist:
raise Http404("Virtual Race does not exist")
if race.sessiontype == 'race':
script,div = course_map(race.course)
resultobj = VirtualRaceResult
else:
script = ''
div = ''
resultobj = IndoorVirtualRaceResult
records = resultobj.objects.filter(race=race)
records = VirtualRaceResult.objects.filter(
race=race
)
buttons = []
@@ -15881,7 +16022,10 @@ def virtualevent_view(request,id=0):
try:
boatclass = cd['boatclass']
except KeyError:
if race.sessiontype == 'race':
boatclass = [t for t in mytypes.otwtypes]
else:
boatclass = [t for t in mytypes.otetypes]
age_min = cd['age_min']
age_max = cd['age_max']
@@ -15891,7 +16035,8 @@ def virtualevent_view(request,id=0):
except KeyError:
weightcategory = ['hwt','lwt']
results = VirtualRaceResult.objects.filter(
if race.sessiontype == 'race':
results = resultobj.objects.filter(
race=race,
workoutid__isnull=False,
boatclass__in=boatclass,
@@ -15901,25 +16046,36 @@ def virtualevent_view(request,id=0):
age__gte=age_min,
age__lte=age_max
).order_by("duration")
else:
results = resultobj.objects.filter(
race=race,
workoutid__isnull=False,
boatclass__in=boatclass,
sex__in=sex,
weightcategory__in=weightcategory,
age__gte=age_min,
age__lte=age_max
).order_by("duration","-distance")
# to-do - add DNS
dns = []
if timezone.now() > race.evaluation_closure:
dns = VirtualRaceResult.objects.filter(
dns = resultobj.objects.filter(
race=race,
workoutid__isnull=True,
boatclass__in=boatclass,
boattype__in=boattype,
sex__in=sex,
weightcategory__in=weightcategory,
age__gte=age_min,
age__lte=age_max
)
else:
results = VirtualRaceResult.objects.filter(
results = resultobj.objects.filter(
race=race,
workoutid__isnull=False,
).order_by("duration")
coursecompleted=True,
).order_by("duration","-distance")
if results:
form = RaceResultFilterForm(records=records)
@@ -15929,18 +16085,32 @@ def virtualevent_view(request,id=0):
# to-do - add DNS
dns = []
if timezone.now() > race.evaluation_closure:
dns = VirtualRaceResult.objects.filter(
dns = resultobj.objects.filter(
race=race,
workoutid__isnull=True,
)
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}
),
'name': race.name
}
]
return render(request,'virtualevent.html',
{
'coursescript':script,
'coursediv':div,
'breadcrumbs':breadcrumbs,
'race':race,
'rower':r,
'results':results,
@@ -16089,9 +16259,31 @@ def virtualevent_addboat_view(request,id=0):
form = VirtualRaceResultForm(initial=initial)
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}
),
'name': race.name
},
{
'url': reverse(virtualevent_addboat_view,
kwargs = {'id':race.id}
),
'name': 'Add Discipline'
}
]
return render(request,'virtualeventregister.html',
{
'form':form,
'breadcrumbs':breadcrumbs,
'race':race,
'userid':r.user.id,
'active': 'nav-racing',
@@ -16182,14 +16374,281 @@ def virtualevent_register_view(request,id=0):
form = VirtualRaceResultForm(initial=initial)
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}
),
'name': race.name
},
{
'url': reverse(virtualevent_register_view,
kwargs = {'id':race.id}
),
'name': 'Register'
}
]
return render(request,'virtualeventregister.html',
{
'form':form,
'breadcrumbs':breadcrumbs,
'race':race,
'userid':r.user.id,
})
@login_required()
def indoorvirtualevent_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 = IndoorVirtualRaceResultForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
teamname = cd['teamname']
weightcategory = cd['weightcategory']
age = cd['age']
boatclass = cd['boatclass']
sex = r.sex
if r.birthdate:
age = calculate_age(r.birthdate)
sex = r.sex
if sex == 'not specified':
sex = 'male'
record = IndoorVirtualRaceResult(
userid=r.id,
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),
boatclass=boatclass,
coursecompleted=False,
sex=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 = IndoorVirtualRaceResultForm(initial=initial)
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}
),
'name': race.name
},
{
'url': reverse(indoorvirtualevent_register_view,
kwargs = {'id':race.id}
),
'name': 'Register'
}
]
return render(request,'virtualeventregister.html',
{
'form':form,
'race':race,
'breadcrumbs':breadcrumbs,
'userid':r.user.id,
})
@login_required()
def indoorvirtualevent_create_view(request):
r = getrower(request.user)
if request.method == 'POST':
racecreateform = IndoorVirtualRaceForm(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']
sessionunit = cd['sessionunit']
sessionvalue = cd['sessionvalue']
name = cd['name']
registration_form = cd['registration_form']
registration_closure = cd['registration_closure']
evaluation_closure = cd['evaluation_closure']
contact_phone = cd['contact_phone']
contact_email = cd['contact_email']
# correct times
timezone_str = cd['timezone']
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)
)
if registration_form == 'manual':
try:
registration_closure = pytz.timezone(
timezone_str
).localize(
registration_closure.replace(tzinfo=None)
)
except AttributeError:
registration_closure = startdatetime
elif registration_form == 'windowstart':
registration_closure = startdatetime
elif registration_form == 'windowend':
registration_closure = enddatetime
else:
registration_closure = evaluation_closure
if sessionunit == 'min':
sessionmode = 'time'
else:
sessionmode = 'distance'
vs = VirtualRace(
name=name,
startdate=startdate,
preferreddate = startdate,
start_time = start_time,
enddate=enddate,
end_time=end_time,
comment=comment,
sessiontype = 'indoorrace',
sessionunit = sessionunit,
sessionmode = sessionmode,
sessionvalue = sessionvalue,
course=None,
timezone=timezone_str,
evaluation_closure=evaluation_closure,
registration_closure=registration_closure,
contact_phone=contact_phone,
contact_email=contact_email,
country = 'Indoor',
manager=request.user,
)
vs.save()
# create Site Announcement & Tweet
if settings.DEBUG:
dotweet = False
elif 'dev' in settings.SITE_URL:
dotweet = False
else:
dotweet = True
try:
sa = SiteAnnouncement(
announcement = "New Virtual Indoor Race on rowsandall.com: {name}".format(
name = name.encode('utf8'),
),
dotweet = dotweet
)
sa.save()
except UnicodeEncodeError:
sa = SiteAnnouncement(
announcement = "New Virtual Indoor Race on rowsandall.com: {name}".format(
name = name,
),
dotweet = dotweet
)
sa.save()
url = reverse(virtualevents_view)
return HttpResponseRedirect(url)
else:
racecreateform = IndoorVirtualRaceForm()
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(indoorvirtualevent_create_view,
),
'name': 'New Indoor Virtual Regatta'
},
]
return render(request,'indoorvirtualeventcreate.html',
{
'form':racecreateform,
'breadcrumbs':breadcrumbs,
'rower':r,
'active':'nav-racing',
})
@login_required()
def virtualevent_create_view(request):
r = getrower(request.user)
@@ -16304,9 +16763,21 @@ def virtualevent_create_view(request):
racecreateform = VirtualRaceForm()
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_create_view,
),
'name': 'New Virtual Regatta'
},
]
return render(request,'virtualeventcreate.html',
{
'form':racecreateform,
'breadcrumbs':breadcrumbs,
'rower':r,
'active':'nav-racing',
@@ -16360,15 +16831,111 @@ def virtualevent_edit_view(request,id=0):
racecreateform = VirtualRaceForm(instance=race)
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}
),
'name': race.name
},
{
'url': reverse(virtualevent_edit_view,
kwargs = {'id':race.id}
),
'name': 'Edit'
}
]
return render(request,'virtualeventedit.html',
{
'form':racecreateform,
'breadcrumbs':breadcrumbs,
'rower':r,
'race':race,
})
@login_required()
def indoorvirtualevent_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 = IndoorVirtualRaceForm(request.POST,instance=race)
if racecreateform.is_valid():
cd = racecreateform.cleaned_data
res, message = update_indoorvirtualrace(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 = IndoorVirtualRaceForm(instance=race)
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}
),
'name': race.name
},
{
'url': reverse(indoorvirtualevent_edit_view,
kwargs = {'id':race.id}
),
'name': 'Edit'
}
]
return render(request,'virtualeventedit.html',
{
'form':racecreateform,
'breadcrumbs':breadcrumbs,
'rower':r,
'race':race,
})
@login_required()
def virtualevent_submit_result_view(request,id=0):
@@ -16391,7 +16958,12 @@ def virtualevent_submit_result_view(request,id=0):
can_submit = race_can_submit(r,race) or race_can_resubmit(r,race)
records = VirtualRaceResult.objects.filter(
if race.sessiontype == 'race':
resultobj = VirtualRaceResult
else:
resultobj = IndoorVirtualRaceResult
records = resultobj.objects.filter(
userid = r.id,
race=race
)
@@ -16462,10 +17034,16 @@ def virtualevent_submit_result_view(request,id=0):
workouts = Workout.objects.filter(id=selectedworkout)
if race.sessiontype == 'race':
result,comments,errors,jobid = add_workout_race(
workouts,race,r,
splitsecond=splitsecond,recordid=recordid)
# if result:
else:
result,comments,errors,jobid = add_workout_indoorrace(
workouts,race,r,recordid=recordid)
# if result:
# for w in ws:
# remove_workout_plannedsession(w,race)
# delete_race_result(w,race)
@@ -16494,10 +17072,29 @@ def virtualevent_submit_result_view(request,id=0):
else:
w_form = WorkoutRaceSelectForm(workoutdata,entries)
breadcrumbs = [
{
'url':reverse(virtualevents_view),
'name': 'Racing'
},
{
'url':reverse(virtualevent_view,
kwargs={'id':race.id}
),
'name': race.name
},
{
'url': reverse(virtualevent_submit_result_view,
kwargs = {'id':race.id}
),
'name': 'Submit Result'
}
]
return render(request,'race_submit.html',
{
'race':race,
'workouts':ws,
'breadcrumbs':breadcrumbs,
'active':'nav-racing',
'rower':r,
'w_form':w_form,
+1
View File
@@ -96,6 +96,7 @@ MIDDLEWARE_CLASSES = [
'tz_detect.middleware.TimezoneMiddleware',
'rowers.middleware.GDPRMiddleWare',
'rowers.middleware.PowerTimeFitnessMetricMiddleWare',
'rowers.middleware.RowerPlanMiddleWare',
]
ROOT_URLCONF = 'rowsandall_app.urls'
+10
View File
@@ -225,6 +225,16 @@
</p>
</li>
{% endif %}
{% if user.rower.planexpires and user.rower.rowerplan != 'basic' and user.rower.paymenttype == 'single'%}
{% if user.rower.planexpires|is_future_date %}
{% if user.rower.planexpires|date_dif|ddays < 4 %}
<li class="grid_4">
<p class="successmessage">
You have {{ user.rower.planexpires|date_dif|ddays }} days left of your one year subscription. Please renew on or before {{ user.rower.planexpires }} or your plan will be reset to Basic. Click <a href="/rowers/promembership">here</a> to renew your membership.</p>
</li>
{% endif %}
{% endif %}
{% endif %}
{% if user.rower.protrialexpires and user.rower.protrialexpires|is_future_date %}
{% if user.rower.plantrialexpires and user.rower.rowerplan != 'plan' %}
<li class="grid_4">
+1 -1
View File
@@ -165,7 +165,7 @@
</li>
{% if user.is_authenticated %}
<li>
<a href="/rowers/me/edit" title="Profile">
<a href="/rowers/me/preferences" title="Profile">
{% if user.rower.rowerplan == 'pro' %}
<i class="fas fa-user-ninja "></i>
{% elif user.rower.rowerplan == 'coach' %}